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 14, 2023
1 parent cf12bc4 commit 950f834
Show file tree
Hide file tree
Showing 23 changed files with 372 additions and 117 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
1 change: 1 addition & 0 deletions docs/package.json
Expand Up @@ -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",
Expand Down
22 changes: 22 additions & 0 deletions src/Riok.Mapperly.Abstractions/MapperIgnoreSourceValueAttribute.cs
@@ -0,0 +1,22 @@
namespace Riok.Mapperly.Abstractions;

/// <summary>
/// Ignores a source enum value from the mapping.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class MapperIgnoreSourceValueAttribute : Attribute
{
/// <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)
{
SourceValue = (Enum)source;
}

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

/// <summary>
/// Ignores a target enum value from the mapping.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class MapperIgnoreTargetValueAttribute : Attribute
{
/// <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)
{
TargetValue = (Enum)target;
}

/// <summary>
/// Gets the target enum value which should be ignored from the mapping.
/// </summary>
public Enum? TargetValue { get; }
}
6 changes: 6 additions & 0 deletions src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
Expand Up @@ -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?
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 950f834

Please sign in to comment.