Skip to content

Commit

Permalink
Separate RequiredMappingStrategy configuration for enums (#1591)
Browse files Browse the repository at this point in the history
Add MapperAttribute.RequiredEnumMappingStrategy / MapperDefaultsAttribute.RequiredEnumMappingStrategy
Resolve RequiredMappingStrategy for each enum mapping in the following order, fall back to the next one if the previous one is not set:
1.MapperRequiredMappingAttribute on the mapping method
2. MapperAttribute.RequiredEnumMappingStrategy
3. MapperDefaultsAttribute.RequiredEnumMappingStrategy
4. MapperAttribute.RequiredMappingStrategy
5. MapperDefaultsAttribute.RequiredMappingStrategy
  • Loading branch information
jacob-buckaroo authored Nov 20, 2024
1 parent 8727f10 commit add55fb
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 4 deletions.
6 changes: 3 additions & 3 deletions docs/docs/configuration/enum.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -182,18 +182,18 @@ public partial class CarMapper

By default Mapperly emits diagnostics with a severity of warning if there are unmapped source or target enum members.
To enforce strict enum member mappings on only either the source or the target,
the `RequiredMappingStrategy` can be used.
the `RequiredEnumMappingStrategy` can be used. By default this property will inherit the value from `RequiredMappingStrategy`.

<Tabs>
<TabItem value="global" label="Global (mapper level)" default>

Sets the `RequiredMappingStrategy` for all methods within the mapper,
Sets the `RequiredEnumMappingStrategy` for all methods within the mapper,
by default it is `Both` requiring all members to be mapped.
This can be overriden by individual mapping methods using `MapperRequiredMappingAttribute`.

```csharp
// highlight-start
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Source)]
[Mapper(RequiredEnumMappingStrategy = RequiredMappingStrategy.Source)]
// highlight-end
public partial class CarMapper
{
Expand Down
6 changes: 6 additions & 0 deletions src/Riok.Mapperly.Abstractions/MapperAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ public class MapperAttribute : Attribute
/// </summary>
public RequiredMappingStrategy RequiredMappingStrategy { get; set; } = RequiredMappingStrategy.Both;

/// <summary>
/// Defines the strategy used when emitting warnings for unmapped enum members.
/// By default this is <see cref="RequiredMappingStrategy.Both"/>, inheriting the strategy from <see cref="RequiredMappingStrategy"/>.
/// </summary>
public RequiredMappingStrategy RequiredEnumMappingStrategy { get; set; } = RequiredMappingStrategy.Both;

/// <summary>
/// Determines the access level of members that Mapperly will map.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/Riok.Mapperly/Configuration/MapperConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ public record MapperConfiguration
/// </summary>
public RequiredMappingStrategy? RequiredMappingStrategy { get; init; }

/// <summary>
/// Defines the strategy used when emitting warnings for unmapped enum members.
/// By default this is <see cref="RequiredEnumMappingStrategy.Inherit"/>, inheriting the strategy from <see cref="RequiredMappingStrategy"/>.
/// </summary>
public RequiredMappingStrategy? RequiredEnumMappingStrategy { get; init; }

/// <summary>
/// Determines the access level of members that Mapperly will map.
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/Riok.Mapperly/Configuration/MapperConfigurationMerger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ public static MapperAttribute Merge(MapperConfiguration mapperConfiguration, Map
?? defaultMapperConfiguration.RequiredMappingStrategy
?? mapper.RequiredMappingStrategy;

mapper.RequiredEnumMappingStrategy =
mapperConfiguration.RequiredEnumMappingStrategy
?? defaultMapperConfiguration.RequiredEnumMappingStrategy
?? mapperConfiguration.RequiredMappingStrategy
?? defaultMapperConfiguration.RequiredMappingStrategy
?? mapper.RequiredMappingStrategy;

mapper.IncludedMembers =
mapperConfiguration.IncludedMembers ?? defaultMapperConfiguration.IncludedMembers ?? mapper.IncludedMembers;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ MapperConfiguration defaultMapperConfiguration
[],
[],
[],
mapper.RequiredMappingStrategy,
mapper.RequiredEnumMappingStrategy,
mapper.EnumNamingStrategy
),
new MembersMappingConfiguration([], [], [], [], [], mapper.IgnoreObsoleteMembersStrategy, mapper.RequiredMappingStrategy),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public MapperAttribute() { }
public Riok.Mapperly.Abstractions.MemberVisibility IncludedMembers { get; set; }
public bool PreferParameterlessConstructors { get; set; }
public Riok.Mapperly.Abstractions.PropertyNameMappingStrategy PropertyNameMappingStrategy { get; set; }
public Riok.Mapperly.Abstractions.RequiredMappingStrategy RequiredEnumMappingStrategy { get; set; }
public Riok.Mapperly.Abstractions.RequiredMappingStrategy RequiredMappingStrategy { get; set; }
public bool ThrowOnMappingNullMismatch { get; set; }
public bool ThrowOnPropertyMappingNullMismatch { get; set; }
Expand Down
38 changes: 38 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/EnumRequiredMappingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,25 @@ public void MapperAttributeRequiredMappingNoneWithUnmappedMembersShouldNotDiagno
TestHelper.GenerateMapper(source).Should().HaveSingleMethodBody("return (global::E2)source;");
}

[Fact]
public void MapperAttributeRequiredEnumMappingSourceAndRequiredMappingNoneWithUnmappedMember()
{
var source = TestSourceBuilder.Mapping(
"E1",
"E2",
TestSourceBuilderOptions.WithRequiredEnumMappingStrategy(RequiredMappingStrategy.Source, RequiredMappingStrategy.None),
"enum E1 { V1, V2, V3 }",
"enum E2 { V1, V2 }"
);

TestHelper
.GenerateMapper(source, TestHelperOptions.AllowDiagnostics)
.Should()
.HaveDiagnostic(DiagnosticDescriptors.SourceEnumValueNotMapped, "Enum member V3 (2) on E1 not found on target enum E2")
.HaveAssertedAllDiagnostics()
.HaveSingleMethodBody("return (global::E2)source;");
}

[Fact]
public void MapperAttributeRequiredMappingSourceWithUnmappedMember()
{
Expand Down Expand Up @@ -70,6 +89,25 @@ TestSourceBuilderOptions.Default with
);
}

[Fact]
public void MapperAttributeRequiredEnumMappingTargetAndRequiredMappingNoneWithUnmappedMember()
{
var source = TestSourceBuilder.Mapping(
"E1",
"E2",
TestSourceBuilderOptions.WithRequiredEnumMappingStrategy(RequiredMappingStrategy.Target, RequiredMappingStrategy.None),
"enum E1 { V1, V2 }",
"enum E2 { V1, V2, V3 }"
);

TestHelper
.GenerateMapper(source, TestHelperOptions.AllowDiagnostics)
.Should()
.HaveDiagnostic(DiagnosticDescriptors.TargetEnumValueNotMapped, "Enum member V3 (2) on E2 not found on source enum E1")
.HaveAssertedAllDiagnostics()
.HaveSingleMethodBody("return (global::E2)source;");
}

[Fact]
public void MapperAttributeRequiredMappingTargetWithUnmappedMember()
{
Expand Down
1 change: 1 addition & 0 deletions test/Riok.Mapperly.Tests/TestSourceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ private static string BuildAttribute(TestSourceBuilderOptions options)
Attribute(options.EnumMappingIgnoreCase),
Attribute(options.IgnoreObsoleteMembersStrategy),
Attribute(options.RequiredMappingStrategy),
Attribute(options.RequiredEnumMappingStrategy),
Attribute(options.IncludedMembers),
Attribute(options.IncludedConstructors),
Attribute(options.PreferParameterlessConstructors),
Expand Down
6 changes: 6 additions & 0 deletions test/Riok.Mapperly.Tests/TestSourceBuilderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public record TestSourceBuilderOptions(
bool? EnumMappingIgnoreCase = null,
IgnoreObsoleteMembersStrategy? IgnoreObsoleteMembersStrategy = null,
RequiredMappingStrategy? RequiredMappingStrategy = null,
RequiredMappingStrategy? RequiredEnumMappingStrategy = null,
MemberVisibility? IncludedMembers = null,
MemberVisibility? IncludedConstructors = null,
bool Static = false,
Expand All @@ -42,6 +43,11 @@ public static TestSourceBuilderOptions WithIgnoreObsolete(IgnoreObsoleteMembersS
public static TestSourceBuilderOptions WithRequiredMappingStrategy(RequiredMappingStrategy requiredMappingStrategy) =>
new(RequiredMappingStrategy: requiredMappingStrategy);

public static TestSourceBuilderOptions WithRequiredEnumMappingStrategy(
RequiredMappingStrategy requiredEnumMappingStrategy,
RequiredMappingStrategy requiredMappingStrategy
) => new(RequiredEnumMappingStrategy: requiredEnumMappingStrategy, RequiredMappingStrategy: requiredMappingStrategy);

public static TestSourceBuilderOptions WithMemberVisibility(MemberVisibility memberVisibility) =>
new(IncludedMembers: memberVisibility);

Expand Down

0 comments on commit add55fb

Please sign in to comment.