Skip to content

Commit

Permalink
feat: add option to ignore enum values
Browse files Browse the repository at this point in the history
  • Loading branch information
latonz committed Jul 13, 2023
1 parent 8c8b5b5 commit 837f765
Show file tree
Hide file tree
Showing 24 changed files with 397 additions and 123 deletions.
17 changes: 17 additions & 0 deletions docs/docs/configuration/enum.mdx
Expand Up @@ -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,
Expand Down
18 changes: 16 additions & 2 deletions src/Riok.Mapperly.Abstractions/MapperIgnoreSourceAttribute.cs
@@ -1,7 +1,7 @@
namespace Riok.Mapperly.Abstractions;

/// <summary>
/// Ignores a source property from the mapping.
/// Ignores a source property or enum value from the mapping.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class MapperIgnoreSourceAttribute : Attribute
Expand All @@ -15,8 +15,22 @@ public MapperIgnoreSourceAttribute(string source)
Source = source;
}

/// <summary>
/// Ignores the specified source enum value from the mapping.
/// </summary>
/// <param name="source">The source enum value to ignore.</param>
public MapperIgnoreSourceAttribute(object source)
{
SourceEnumValue = (Enum)source;
}

/// <summary>
/// Gets the source property name which should be ignored from the mapping.
/// </summary>
public string Source { get; }
public string? Source { get; }

/// <summary>
/// Gets the source enum value which should be ignored from the mapping.
/// </summary>
public Enum? SourceEnumValue { get; }
}
19 changes: 19 additions & 0 deletions src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs
@@ -0,0 +1,19 @@
namespace Riok.Mapperly.Abstractions;

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class MapperIgnoreSourceValueAttribute : Attribute

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Missing XML comment for publicly visible type or member 'MapperIgnoreSourceValueAttribute'

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'MapperIgnoreSourceValueAttribute' is not part of the declared public API

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Missing XML comment for publicly visible type or member 'MapperIgnoreSourceValueAttribute'

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / lint

Symbol 'MapperIgnoreSourceValueAttribute' is not part of the declared public API

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'MapperIgnoreSourceValueAttribute'

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'MapperIgnoreSourceValueAttribute' is not part of the declared public API

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'MapperIgnoreSourceValueAttribute'
{
/// <summary>
/// Ignores the specified source enum value from the mapping.
/// </summary>
/// <param name="source">The source enum value to ignore.</param>
public MapperIgnoreSourceValueAttribute(object source)

Check warning on line 10 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'MapperIgnoreSourceValueAttribute' is not part of the declared public API

Check warning on line 10 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / lint

Symbol 'MapperIgnoreSourceValueAttribute' is not part of the declared public API

Check warning on line 10 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'MapperIgnoreSourceValueAttribute' is not part of the declared public API
{
SourceValue = (Enum)source;
}

/// <summary>
/// Gets the source enum value which should be ignored from the mapping.
/// </summary>
public Enum? SourceValue { get; }

Check warning on line 18 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'SourceValue.get' is not part of the declared public API

Check warning on line 18 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / lint

Symbol 'SourceValue.get' is not part of the declared public API

Check warning on line 18 in src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValue.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'SourceValue.get' is not part of the declared public API
}
18 changes: 16 additions & 2 deletions src/Riok.Mapperly.Abstractions/MapperIgnoreTargetAttribute.cs
@@ -1,7 +1,7 @@
namespace Riok.Mapperly.Abstractions;

/// <summary>
/// Ignores a target property from the mapping.
/// Ignores a target property or enum value from the mapping.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class MapperIgnoreTargetAttribute : Attribute
Expand All @@ -15,8 +15,22 @@ public MapperIgnoreTargetAttribute(string target)
Target = target;
}

/// <summary>
/// Ignores the specified target enum value from the mapping.
/// </summary>
/// <param name="target">The target enum value to ignore.</param>
public MapperIgnoreTargetAttribute(object target)
{
TargetEnumValue = (Enum)target;
}

/// <summary>
/// Gets the target property name which should be ignored from the mapping.
/// </summary>
public string Target { get; }
public string? Target { get; }

/// <summary>
/// Gets the target enum value which should be ignored from the mapping.
/// </summary>
public Enum? TargetEnumValue { get; }
}
19 changes: 19 additions & 0 deletions src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs
@@ -0,0 +1,19 @@
namespace Riok.Mapperly.Abstractions;

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class MapperIgnoreTargetValueAttribute : Attribute

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Missing XML comment for publicly visible type or member 'MapperIgnoreTargetValueAttribute'

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'MapperIgnoreTargetValueAttribute' is not part of the declared public API

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Missing XML comment for publicly visible type or member 'MapperIgnoreTargetValueAttribute'

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / lint

Symbol 'MapperIgnoreTargetValueAttribute' is not part of the declared public API

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'MapperIgnoreTargetValueAttribute'

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'MapperIgnoreTargetValueAttribute' is not part of the declared public API

Check warning on line 4 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'MapperIgnoreTargetValueAttribute'
{
/// <summary>
/// Ignores the specified target enum value from the mapping.
/// </summary>
/// <param name="target">The target enum value to ignore.</param>
public MapperIgnoreTargetValueAttribute(object target)

Check warning on line 10 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'MapperIgnoreTargetValueAttribute' is not part of the declared public API

Check warning on line 10 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / lint

Symbol 'MapperIgnoreTargetValueAttribute' is not part of the declared public API

Check warning on line 10 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'MapperIgnoreTargetValueAttribute' is not part of the declared public API
{
TargetValue = (Enum)target;
}

/// <summary>
/// Gets the target enum value which should be ignored from the mapping.
/// </summary>
public Enum? TargetValue { get; }

Check warning on line 18 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Symbol 'TargetValue.get' is not part of the declared public API

Check warning on line 18 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / lint

Symbol 'TargetValue.get' is not part of the declared public API

Check warning on line 18 in src/Riok.Mapperly.Abstractions/MapperIgnoreTargetValue.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'TargetValue.get' is not part of the declared public API
}
8 changes: 6 additions & 2 deletions src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
Expand Up @@ -35,10 +35,10 @@ Riok.Mapperly.Abstractions.MapperIgnoreAttribute.MapperIgnoreAttribute(string! t
Riok.Mapperly.Abstractions.MapperIgnoreAttribute.Target.get -> string!
Riok.Mapperly.Abstractions.MapperIgnoreSourceAttribute
Riok.Mapperly.Abstractions.MapperIgnoreSourceAttribute.MapperIgnoreSourceAttribute(string! source) -> void
Riok.Mapperly.Abstractions.MapperIgnoreSourceAttribute.Source.get -> string!
Riok.Mapperly.Abstractions.MapperIgnoreSourceAttribute.Source.get -> string?
Riok.Mapperly.Abstractions.MapperIgnoreTargetAttribute
Riok.Mapperly.Abstractions.MapperIgnoreTargetAttribute.MapperIgnoreTargetAttribute(string! target) -> void
Riok.Mapperly.Abstractions.MapperIgnoreTargetAttribute.Target.get -> string!
Riok.Mapperly.Abstractions.MapperIgnoreTargetAttribute.Target.get -> string?
Riok.Mapperly.Abstractions.MapPropertyAttribute
Riok.Mapperly.Abstractions.MapPropertyAttribute.MapPropertyAttribute(string! source, string! target) -> void
Riok.Mapperly.Abstractions.MapPropertyAttribute.MapPropertyAttribute(string![]! source, string![]! target) -> void
Expand Down Expand Up @@ -89,3 +89,7 @@ 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.MapperIgnoreSourceAttribute.MapperIgnoreSourceAttribute(object! source) -> void
Riok.Mapperly.Abstractions.MapperIgnoreSourceAttribute.SourceEnumValue.get -> System.Enum?
Riok.Mapperly.Abstractions.MapperIgnoreTargetAttribute.MapperIgnoreTargetAttribute(object! target) -> void
Riok.Mapperly.Abstractions.MapperIgnoreTargetAttribute.TargetEnumValue.get -> System.Enum?
2 changes: 2 additions & 0 deletions src/Riok.Mapperly/AnalyzerReleases.Shipped.md
Expand Up @@ -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
7 changes: 6 additions & 1 deletion src/Riok.Mapperly/Configuration/EnumMappingConfiguration.cs
Expand Up @@ -7,5 +7,10 @@ public record EnumMappingConfiguration(
EnumMappingStrategy Strategy,
bool IgnoreCase,
IFieldSymbol? FallbackValue,
IReadOnlyCollection<IFieldSymbol> IgnoredSourceMembers,
IReadOnlyCollection<IFieldSymbol> IgnoredTargetMembers,
IReadOnlyCollection<EnumValueMappingConfiguration> ExplicitMappings
);
)
{
public bool HasExplicitConfigurations => ExplicitMappings.Count > 0 || IgnoredSourceMembers.Count > 0 || IgnoredTargetMembers.Count > 0;
}
39 changes: 30 additions & 9 deletions 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;

Expand All @@ -18,6 +19,8 @@ public MapperConfiguration(SymbolAccessor symbolAccessor, ISymbol mapperSymbol)
Mapper.EnumMappingStrategy,
Mapper.EnumMappingIgnoreCase,
null,
Array.Empty<IFieldSymbol>(),
Array.Empty<IFieldSymbol>(),
Array.Empty<EnumValueMappingConfiguration>()
),
new PropertiesMappingConfiguration(Array.Empty<string>(), Array.Empty<string>(), Array.Empty<PropertyMappingConfiguration>()),
Expand All @@ -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);
}

Expand All @@ -48,27 +51,45 @@ private IReadOnlyCollection<DerivedTypeMappingConfiguration> BuildDerivedTypeCon

private PropertiesMappingConfiguration BuildPropertiesConfig(IMethodSymbol method)
{
var ignoredSourceProperties = _dataAccessor.Access<MapperIgnoreSourceAttribute>(method).Select(x => x.Source).ToList();
var ignoredSourceProperties = _dataAccessor
.Access<MapperIgnoreSourceAttribute>(method)
.Select(x => x.Source)
.WhereNotNull()
.ToList();
var ignoredTargetProperties = _dataAccessor
.Access<MapperIgnoreTargetAttribute>(method)
.Select(x => x.Target)
// deprecated MapperIgnoreAttribute, but it is still supported by Mapperly.
#pragma warning disable CS0618
.Concat(_dataAccessor.Access<MapperIgnoreAttribute>(method).Select(x => x.Target))
#pragma warning restore CS0618
.WhereNotNull()
.ToList();
var explicitMappings = _dataAccessor.Access<MapPropertyAttribute, PropertyMappingConfiguration>(method).ToList();
return new PropertiesMappingConfiguration(ignoredSourceProperties, ignoredTargetProperties, explicitMappings);
}

private EnumMappingConfiguration BuildEnumConfig(IMethodSymbol method)
private EnumMappingConfiguration BuildEnumConfig(MappingConfigurationReference configRef)
{
var configData = _dataAccessor.AccessFirstOrDefault<MapEnumAttribute, EnumConfiguration>(method);
var explicitMappings = _dataAccessor.Access<MapEnumValueAttribute, EnumValueMappingConfiguration>(method).ToList();
if (configRef.Method == null || !configRef.Source.IsEnum() && !configRef.Target.IsEnum())
return _defaultConfiguration.Enum;

var configData = _dataAccessor.AccessFirstOrDefault<MapEnumAttribute, EnumConfiguration>(configRef.Method);
var explicitMappings = _dataAccessor.Access<MapEnumValueAttribute, EnumValueMappingConfiguration>(configRef.Method).ToList();
var ignoredSources = _dataAccessor
.Access<MapperIgnoreSourceValueAttribute, MapperIgnoreEnumValueConfiguration>(configRef.Method)
.Select(x => x.Value)
.ToList();
var ignoredTargets = _dataAccessor
.Access<MapperIgnoreTargetValueAttribute, MapperIgnoreEnumValueConfiguration>(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
);
}
Expand Down
@@ -0,0 +1,5 @@
using Microsoft.CodeAnalysis;

namespace Riok.Mapperly.Configuration;

public record MapperIgnoreEnumValueConfiguration(IFieldSymbol Value);
@@ -0,0 +1,5 @@
using Microsoft.CodeAnalysis;

namespace Riok.Mapperly.Configuration;

public record struct MappingConfigurationReference(IMethodSymbol? Method, ITypeSymbol Source, ITypeSymbol Target);
2 changes: 1 addition & 1 deletion src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs
Expand Up @@ -29,7 +29,7 @@ ITypeSymbol target
Source = source;
Target = target;
UserSymbol = userSymbol;
Configuration = ReadConfiguration(UserSymbol);
Configuration = ReadConfiguration(new MappingConfigurationReference(UserSymbol, source, target));
}

protected MappingBuilderContext(
Expand Down

0 comments on commit 837f765

Please sign in to comment.