diff --git a/docs/docs/configuration/enum.mdx b/docs/docs/configuration/enum.mdx
index 0529403358..9d7c579780 100644
--- a/docs/docs/configuration/enum.mdx
+++ b/docs/docs/configuration/enum.mdx
@@ -75,6 +75,23 @@ public partial class CarMapper
}
```
+## Ignore enum values
+
+To ignore an enum value the `MapperIgnoreSourceValue` or `MapperIgnoreTargetValue` attributes can be used.
+This is especially useful when applying [strict enum mappings](#strict-enum-mappings).
+
+```csharp
+[Mapper]
+public partial class CarMapper
+{
+ // highlight-start
+ [MapperIgnoreSourceValue(Fruit.Apple)]
+ [MapperIgnoreTargetValue(FruitDto.Pineapple)]
+ // highlight-end
+ public partial FruitDto Map(Fruit source);
+}
+```
+
## Fallback value
To map to a fallback value instead of throwing when encountering an unknown value,
diff --git a/docs/package.json b/docs/package.json
index 2baeff32c6..d3fe2827eb 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -4,6 +4,7 @@
"private": true,
"scripts": {
"prebuild": "ts-node prebuild.ts",
+ "prestart": "ts-node prebuild.ts",
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
diff --git a/src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValueAttribute.cs b/src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValueAttribute.cs
new file mode 100644
index 0000000000..ad677427a9
--- /dev/null
+++ b/src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValueAttribute.cs
@@ -0,0 +1,22 @@
+namespace Riok.Mapperly.Abstractions;
+
+///
+/// Ignores a source enum value from the mapping.
+///
+[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+public class MapperIgnoreSourceValueAttribute : Attribute
+{
+ ///
+ /// Ignores the specified source enum value from the mapping.
+ ///
+ /// The source enum value to ignore.
+ public MapperIgnoreSourceValueAttribute(object source)
+ {
+ SourceValue = (Enum)source;
+ }
+
+ ///
+ /// Gets the source enum value which should be ignored from the mapping.
+ ///
+ public Enum? SourceValue { get; }
+}
diff --git a/src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValueAttribute.cs b/src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValueAttribute.cs
new file mode 100644
index 0000000000..a0cb7daa78
--- /dev/null
+++ b/src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValueAttribute.cs
@@ -0,0 +1,22 @@
+namespace Riok.Mapperly.Abstractions;
+
+///
+/// Ignores a target enum value from the mapping.
+///
+[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+public class MapperIgnoreTargetValueAttribute : Attribute
+{
+ ///
+ /// Ignores the specified target enum value from the mapping.
+ ///
+ /// The target enum value to ignore.
+ public MapperIgnoreTargetValueAttribute(object target)
+ {
+ TargetValue = (Enum)target;
+ }
+
+ ///
+ /// Gets the target enum value which should be ignored from the mapping.
+ ///
+ public Enum? TargetValue { get; }
+}
diff --git a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
index 193f4726f1..2c2d6862f5 100644
--- a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
+++ b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
@@ -89,3 +89,9 @@ Riok.Mapperly.Abstractions.MapDerivedTypeAttribute.TargetType.get -> System.Type
Riok.Mapperly.Abstractions.EnumMappingStrategy.ByValueCheckDefined = 2 -> Riok.Mapperly.Abstractions.EnumMappingStrategy
Riok.Mapperly.Abstractions.MapEnumAttribute.FallbackValue.get -> object?
Riok.Mapperly.Abstractions.MapEnumAttribute.FallbackValue.set -> void
+Riok.Mapperly.Abstractions.MapperIgnoreSourceValueAttribute
+Riok.Mapperly.Abstractions.MapperIgnoreSourceValueAttribute.MapperIgnoreSourceValueAttribute(object! source) -> void
+Riok.Mapperly.Abstractions.MapperIgnoreTargetValueAttribute
+Riok.Mapperly.Abstractions.MapperIgnoreTargetValueAttribute.MapperIgnoreTargetValueAttribute(object! target) -> void
+Riok.Mapperly.Abstractions.MapperIgnoreSourceValueAttribute.SourceValue.get -> System.Enum?
+Riok.Mapperly.Abstractions.MapperIgnoreTargetValueAttribute.TargetValue.get -> System.Enum?
diff --git a/src/Riok.Mapperly/AnalyzerReleases.Shipped.md b/src/Riok.Mapperly/AnalyzerReleases.Shipped.md
index a3902060f4..52f90a4fd4 100644
--- a/src/Riok.Mapperly/AnalyzerReleases.Shipped.md
+++ b/src/Riok.Mapperly/AnalyzerReleases.Shipped.md
@@ -102,3 +102,5 @@ RMG040 | Mapper | Error | A target enum member value does not match the ta
RMG041 | Mapper | Error | A source enum member value does not match the source enum type
RMG042 | Mapper | Error | The type of the enum fallback value does not match the target enum type
RMG043 | Mapper | Warning | Enum fallback values are only supported for the ByName and ByValueCheckDefined strategies, but not for the ByValue strategy
+RMG044 | Mapper | Warning | An ignored enum member can not be found on the source enum
+RMG045 | Mapper | Warning | An ignored enum member can not be found on the target enum
diff --git a/src/Riok.Mapperly/Configuration/EnumMappingConfiguration.cs b/src/Riok.Mapperly/Configuration/EnumMappingConfiguration.cs
index 01ab4e5985..0ad52b8d8d 100644
--- a/src/Riok.Mapperly/Configuration/EnumMappingConfiguration.cs
+++ b/src/Riok.Mapperly/Configuration/EnumMappingConfiguration.cs
@@ -7,5 +7,10 @@ public record EnumMappingConfiguration(
EnumMappingStrategy Strategy,
bool IgnoreCase,
IFieldSymbol? FallbackValue,
+ IReadOnlyCollection IgnoredSourceMembers,
+ IReadOnlyCollection IgnoredTargetMembers,
IReadOnlyCollection ExplicitMappings
-);
+)
+{
+ public bool HasExplicitConfigurations => ExplicitMappings.Count > 0 || IgnoredSourceMembers.Count > 0 || IgnoredTargetMembers.Count > 0;
+}
diff --git a/src/Riok.Mapperly/Configuration/MapperConfiguration.cs b/src/Riok.Mapperly/Configuration/MapperConfiguration.cs
index 9f220ae1a7..5bab50920f 100644
--- a/src/Riok.Mapperly/Configuration/MapperConfiguration.cs
+++ b/src/Riok.Mapperly/Configuration/MapperConfiguration.cs
@@ -1,6 +1,7 @@
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Descriptors;
+using Riok.Mapperly.Helpers;
namespace Riok.Mapperly.Configuration;
@@ -18,6 +19,8 @@ public MapperConfiguration(SymbolAccessor symbolAccessor, ISymbol mapperSymbol)
Mapper.EnumMappingStrategy,
Mapper.EnumMappingIgnoreCase,
null,
+ Array.Empty(),
+ Array.Empty(),
Array.Empty()
),
new PropertiesMappingConfiguration(Array.Empty(), Array.Empty(), Array.Empty()),
@@ -27,14 +30,14 @@ public MapperConfiguration(SymbolAccessor symbolAccessor, ISymbol mapperSymbol)
public MapperAttribute Mapper { get; }
- public MappingConfiguration ForMethod(IMethodSymbol? method)
+ public MappingConfiguration BuildFor(MappingConfigurationReference reference)
{
- if (method == null)
+ if (reference.Method == null)
return _defaultConfiguration;
- var enumConfig = BuildEnumConfig(method);
- var propertiesConfig = BuildPropertiesConfig(method);
- var derivedTypesConfig = BuildDerivedTypeConfigs(method);
+ var enumConfig = BuildEnumConfig(reference);
+ var propertiesConfig = BuildPropertiesConfig(reference.Method);
+ var derivedTypesConfig = BuildDerivedTypeConfigs(reference.Method);
return new MappingConfiguration(enumConfig, propertiesConfig, derivedTypesConfig);
}
@@ -48,7 +51,11 @@ private IReadOnlyCollection BuildDerivedTypeCon
private PropertiesMappingConfiguration BuildPropertiesConfig(IMethodSymbol method)
{
- var ignoredSourceProperties = _dataAccessor.Access(method).Select(x => x.Source).ToList();
+ var ignoredSourceProperties = _dataAccessor
+ .Access(method)
+ .Select(x => x.Source)
+ .WhereNotNull()
+ .ToList();
var ignoredTargetProperties = _dataAccessor
.Access(method)
.Select(x => x.Target)
@@ -56,19 +63,33 @@ private PropertiesMappingConfiguration BuildPropertiesConfig(IMethodSymbol metho
#pragma warning disable CS0618
.Concat(_dataAccessor.Access(method).Select(x => x.Target))
#pragma warning restore CS0618
+ .WhereNotNull()
.ToList();
var explicitMappings = _dataAccessor.Access(method).ToList();
return new PropertiesMappingConfiguration(ignoredSourceProperties, ignoredTargetProperties, explicitMappings);
}
- private EnumMappingConfiguration BuildEnumConfig(IMethodSymbol method)
+ private EnumMappingConfiguration BuildEnumConfig(MappingConfigurationReference configRef)
{
- var configData = _dataAccessor.AccessFirstOrDefault(method);
- var explicitMappings = _dataAccessor.Access(method).ToList();
+ if (configRef.Method == null || !configRef.Source.IsEnum() && !configRef.Target.IsEnum())
+ return _defaultConfiguration.Enum;
+
+ var configData = _dataAccessor.AccessFirstOrDefault(configRef.Method);
+ var explicitMappings = _dataAccessor.Access(configRef.Method).ToList();
+ var ignoredSources = _dataAccessor
+ .Access(configRef.Method)
+ .Select(x => x.Value)
+ .ToList();
+ var ignoredTargets = _dataAccessor
+ .Access(configRef.Method)
+ .Select(x => x.Value)
+ .ToList();
return new EnumMappingConfiguration(
configData?.Strategy ?? _defaultConfiguration.Enum.Strategy,
configData?.IgnoreCase ?? _defaultConfiguration.Enum.IgnoreCase,
configData?.FallbackValue,
+ ignoredSources,
+ ignoredTargets,
explicitMappings
);
}
diff --git a/src/Riok.Mapperly/Configuration/MapperIgnoreEnumValueConfiguration.cs b/src/Riok.Mapperly/Configuration/MapperIgnoreEnumValueConfiguration.cs
new file mode 100644
index 0000000000..4d9dc75556
--- /dev/null
+++ b/src/Riok.Mapperly/Configuration/MapperIgnoreEnumValueConfiguration.cs
@@ -0,0 +1,5 @@
+using Microsoft.CodeAnalysis;
+
+namespace Riok.Mapperly.Configuration;
+
+public record MapperIgnoreEnumValueConfiguration(IFieldSymbol Value);
diff --git a/src/Riok.Mapperly/Configuration/MappingConfigurationReference.cs b/src/Riok.Mapperly/Configuration/MappingConfigurationReference.cs
new file mode 100644
index 0000000000..9404129d76
--- /dev/null
+++ b/src/Riok.Mapperly/Configuration/MappingConfigurationReference.cs
@@ -0,0 +1,5 @@
+using Microsoft.CodeAnalysis;
+
+namespace Riok.Mapperly.Configuration;
+
+public record struct MappingConfigurationReference(IMethodSymbol? Method, ITypeSymbol Source, ITypeSymbol Target);
diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs b/src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs
index 542d04214c..28cfc8a47c 100644
--- a/src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs
+++ b/src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs
@@ -29,7 +29,7 @@ ITypeSymbol target
Source = source;
Target = target;
UserSymbol = userSymbol;
- Configuration = ReadConfiguration(UserSymbol);
+ Configuration = ReadConfiguration(new MappingConfigurationReference(UserSymbol, source, target));
}
protected MappingBuilderContext(
diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumMappingBuilder.cs
index d3763d050c..5762172a7e 100644
--- a/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumMappingBuilder.cs
+++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumMappingBuilder.cs
@@ -32,61 +32,40 @@ public static class EnumMappingBuilder
return null;
// map enums by strategy
- var explicitMappings = BuildExplicitValueMapping(ctx);
return ctx.Configuration.Enum.Strategy switch
{
EnumMappingStrategy.ByName when ctx.IsExpression => BuildCastMappingAndDiagnostic(ctx),
- EnumMappingStrategy.ByValue when ctx.IsExpression && explicitMappings.Count > 0 => BuildCastMappingAndDiagnostic(ctx),
+ EnumMappingStrategy.ByValue when ctx is { IsExpression: true, Configuration.Enum.HasExplicitConfigurations: true }
+ => BuildCastMappingAndDiagnostic(ctx),
EnumMappingStrategy.ByValueCheckDefined when ctx.IsExpression => BuildCastMappingAndDiagnostic(ctx),
- EnumMappingStrategy.ByName => BuildNameMapping(ctx, explicitMappings),
- EnumMappingStrategy.ByValueCheckDefined => BuildEnumToEnumCastMapping(ctx, explicitMappings, true),
- _ => BuildEnumToEnumCastMapping(ctx, explicitMappings),
+ EnumMappingStrategy.ByName => BuildNameMapping(ctx),
+ EnumMappingStrategy.ByValueCheckDefined => BuildEnumToEnumCastMapping(ctx, checkTargetDefined: true),
+ _ => BuildEnumToEnumCastMapping(ctx),
};
}
private static TypeMapping BuildCastMappingAndDiagnostic(MappingBuilderContext ctx)
{
ctx.ReportDiagnostic(
- DiagnosticDescriptors.EnumMappingStrategyByNameNotSupportedInProjectionMappings,
+ DiagnosticDescriptors.EnumMappingNotSupportedInProjectionMappings,
ctx.Source.ToDisplayString(),
ctx.Target.ToDisplayString()
);
- return BuildEnumToEnumCastMapping(ctx, new Dictionary(SymbolEqualityComparer.Default));
+ return BuildEnumToEnumCastMapping(ctx, true);
}
private static TypeMapping BuildEnumToEnumCastMapping(
MappingBuilderContext ctx,
- IReadOnlyDictionary explicitMappings,
+ bool ignoreExplicitAndIgnoredMappings = false,
bool checkTargetDefined = false
)
{
- var explicitMappingSourceNames = explicitMappings.Keys.Select(x => x.Name).ToHashSet();
- var explicitMappingTargetNames = explicitMappings.Values.Select(x => x.Name).ToHashSet();
- var sourceValues = ctx.SymbolAccessor
- .GetAllFields(ctx.Source)
- .Where(x => !explicitMappingSourceNames.Contains(x.Name))
- .ToDictionary(field => field.Name, field => field.ConstantValue);
- var targetValues = ctx.SymbolAccessor
- .GetAllFields(ctx.Target)
- .Where(x => !explicitMappingTargetNames.Contains(x.Name))
- .ToDictionary(field => field.Name, field => field.ConstantValue);
- var targetMemberNames = ctx.SymbolAccessor.GetAllFields(ctx.Target).Select(x => x.Name).ToHashSet();
-
- var missingTargetValues = targetValues.Where(
- field =>
- !sourceValues.ContainsValue(field.Value) && ctx.Configuration.Enum.FallbackValue?.ConstantValue?.Equals(field.Value) != true
+ var enumMemberMappings = BuildEnumMemberMappings(
+ ctx,
+ ignoreExplicitAndIgnoredMappings,
+ static x => x.ConstantValue!,
+ EqualityComparer