Skip to content

Commit

Permalink
feat: Add MapperIgnoreMemberAttribute to ignore members at declaration (
Browse files Browse the repository at this point in the history
#1143)

* feat: Added a MapperIgnoreMemberAttribute to allow mapping to be ignored from within a source/target class. Works the same as the ObsoleteAttribute without the configuration due to being explicitly specified.
  • Loading branch information
jjr2000 authored Mar 11, 2024
1 parent d303198 commit ebce68e
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 2 deletions.
26 changes: 26 additions & 0 deletions docs/docs/configuration/mapper.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,32 @@ public partial class CarMapper
}
```

#### Ignore a memeber at definition

To ignore a property or field at definition, the `MapperIgnoreAttribute` can be used.

```csharp
public partial class CarMapper
{
public partial CarDto ToDto(Car car);
}

public class Car
{
// highlight-start
[MapperIgnore]
// highlight-end
public int Id { get; set; }

public string ModelName { get; set; }
}

public class CarDto
{
public string ModelName { get; set; }
}
```

#### Ignore obsolete members

By default, Mapperly will map source/target members marked with `ObsoleteAttribute`.
Expand Down
10 changes: 10 additions & 0 deletions src/Riok.Mapperly.Abstractions/MapperIgnoreAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Diagnostics;

namespace Riok.Mapperly.Abstractions;

/// <summary>
/// Ignores a member from the mapping.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
[Conditional("MAPPERLY_ABSTRACTIONS_SCOPE_RUNTIME")]
public sealed class MapperIgnoreAttribute : Attribute;
2 changes: 2 additions & 0 deletions src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Riok.Mapperly.Abstractions.MapperConstructorAttribute.MapperConstructorAttribute
Riok.Mapperly.Abstractions.MapperIgnoreObsoleteMembersAttribute
Riok.Mapperly.Abstractions.MapperIgnoreObsoleteMembersAttribute.IgnoreObsoleteStrategy.get -> Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy
Riok.Mapperly.Abstractions.MapperIgnoreObsoleteMembersAttribute.MapperIgnoreObsoleteMembersAttribute(Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy ignoreObsoleteStrategy = (Riok.Mapperly.Abstractions.IgnoreObsoleteMembersStrategy)-1) -> void
Riok.Mapperly.Abstractions.MapperIgnoreAttribute
Riok.Mapperly.Abstractions.MapperIgnoreAttribute.MapperIgnoreAttribute() -> void
Riok.Mapperly.Abstractions.MapperIgnoreSourceAttribute
Riok.Mapperly.Abstractions.MapperIgnoreSourceAttribute.MapperIgnoreSourceAttribute(string! source) -> void
Riok.Mapperly.Abstractions.MapperIgnoreSourceAttribute.Source.get -> string!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ protected MembersMappingBuilderContext(MappingBuilderContext builderContext, T m
TargetMembers = GetTargetMembers();

IgnoredSourceMemberNames = builderContext
.Configuration.Members.IgnoredSources.Concat(GetIgnoredObsoleteSourceMembers())
.Configuration.Members.IgnoredSources.Concat(GetIgnoredSourceMembers())
.Concat(GetIgnoredObsoleteSourceMembers())
.ToHashSet();
var ignoredTargetMemberNames = builderContext
.Configuration.Members.IgnoredTargets.Concat(GetIgnoredObsoleteTargetMembers())
.Configuration.Members.IgnoredTargets.Concat(GetIgnoredTargetMembers())
.Concat(GetIgnoredObsoleteTargetMembers())
.ToHashSet();

_ignoredUnmatchedSourceMemberNames = InitIgnoredUnmatchedProperties(IgnoredSourceMemberNames, _unmappedSourceMemberNames);
Expand Down Expand Up @@ -143,6 +145,22 @@ private IEnumerable<string> GetIgnoredObsoleteSourceMembers()
.Select(x => x.Name);
}

private IEnumerable<string> GetIgnoredTargetMembers()
{
return BuilderContext
.SymbolAccessor.GetAllAccessibleMappableMembers(Mapping.TargetType)
.Where(x => BuilderContext.SymbolAccessor.HasAttribute<MapperIgnoreAttribute>(x.MemberSymbol))
.Select(x => x.Name);
}

private IEnumerable<string> GetIgnoredSourceMembers()
{
return BuilderContext
.SymbolAccessor.GetAllAccessibleMappableMembers(Mapping.SourceType)
.Where(x => BuilderContext.SymbolAccessor.HasAttribute<MapperIgnoreAttribute>(x.MemberSymbol))
.Select(x => x.Name);
}

private HashSet<string> GetSourceMemberNames()
{
return BuilderContext.SymbolAccessor.GetAllAccessibleMappableMembers(Mapping.SourceType).Select(x => x.Name).ToHashSet();
Expand Down
4 changes: 4 additions & 0 deletions test/Riok.Mapperly.IntegrationTests/Dto/TestObjectDto.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.IntegrationTests.Models;

namespace Riok.Mapperly.IntegrationTests.Dto
Expand Down Expand Up @@ -119,6 +120,9 @@ public TestObjectDto(int ctorValue, int unknownValue = 10, int ctorValue2 = 100)
[Obsolete]
public int IgnoredObsoleteValue { get; set; }

[MapperIgnore]
public int IgnoredMemberValue { get; set; }

public DateOnly DateTimeValueTargetDateOnly { get; set; }

public TimeOnly DateTimeValueTargetTimeOnly { get; set; }
Expand Down
82 changes: 82 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/IgnoreAttributeTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using Riok.Mapperly.Diagnostics;

namespace Riok.Mapperly.Tests.Mapping;

[UsesVerify]
public class IgnoreAttributeTest
{
private readonly string _classA = TestSourceBuilder.CSharp(
"""
class A
{
public int Value { get; set; }
[MapperIgnore]
public int Ignored { get; set; }
}
"""
);

private readonly string _classB = TestSourceBuilder.CSharp(
"""
class B
{
public int Value { get; set; }
[MapperIgnore]
public int Ignored { get; set; }
}
"""
);

[Fact]
public void ClassAttributeIgnoreMember()
{
var source = TestSourceBuilder.Mapping("A", "B", TestSourceBuilderOptions.Default, _classA, _classB);

TestHelper
.GenerateMapper(source)
.Should()
.HaveSingleMethodBody(
"""
var target = new global::B();
target.Value = source.Value;
return target;
"""
);
}

[Fact]
public void MapPropertyOverridesIgnoreMember()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"""
[MapProperty("Ignored", "Ignored")]
partial B Map(A source);
""",
_classA,
_classB
);

TestHelper
.GenerateMapper(source, TestHelperOptions.AllowDiagnostics)
.Should()
.HaveSingleMethodBody(
"""
var target = new global::B();
target.Value = source.Value;
target.Ignored = source.Ignored;
return target;
"""
)
.HaveDiagnostic(
DiagnosticDescriptors.IgnoredSourceMemberExplicitlyMapped,
"The source member Ignored on A is ignored, but is also mapped by the MapPropertyAttribute"
)
.HaveDiagnostic(
DiagnosticDescriptors.IgnoredTargetMemberExplicitlyMapped,
"The target member Ignored on B is ignored, but is also mapped by the MapPropertyAttribute"
)
.HaveAssertedAllDiagnostics();
}
}

0 comments on commit ebce68e

Please sign in to comment.