Skip to content

Commit

Permalink
support for enum from/to string mapping in MapEnumValueAttribute
Browse files Browse the repository at this point in the history
Added support for enum from/to string explicit mappings using MapEnumValueAttribute

---------

Co-authored-by: latonz <lars@riok.ch>
  • Loading branch information
BeeTwin and latonz authored Sep 11, 2024
1 parent 3ed555b commit 6a1e415
Show file tree
Hide file tree
Showing 17 changed files with 670 additions and 148 deletions.
14 changes: 12 additions & 2 deletions docs/docs/configuration/enum.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ The `IgnoreCase` property allows to opt in for case insensitive mappings (defaul
## Manually mapped enum values

To explicitly map enum values the `MapEnumValueAttibute` can be used.
Attribute is only valid on mapping methods with enums as parameters.
Attribute is only valid on enum-to-enum, enum-to-string and string-to-enum mappings.

```csharp
[Mapper]
Expand All @@ -70,6 +70,16 @@ public partial class CarMapper
[MapEnumValue(CarFeature.AWD, CarFeatureDto.AllWheelDrive)]
// highlight-end
public partial CarFeatureDto MapFeature(CarFeature feature);

// highlight-start
[MapEnumValue("AWD", CarFeatureDto.AllWheelDrive)]
// highlight-end
public partial CarFeatureDto MapFeatureFromString(string feature);

// highlight-start
[MapEnumValue(CarFeatureDto.AllWheelDrive, "AWD")]
// highlight-end
public partial string MapFeatureToString(CarFeatureDto feature);
}
```

Expand All @@ -80,7 +90,7 @@ This is especially useful when applying [strict enum mappings](#strict-enum-mapp

```csharp
[Mapper]
public partial class CarMapper
public partial class FruitMapper
{
// highlight-start
[MapperIgnoreSourceValue(Fruit.Apple)]
Expand Down
12 changes: 6 additions & 6 deletions src/Riok.Mapperly.Abstractions/MapEnumValueAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ public sealed class MapEnumValueAttribute : Attribute
/// <summary>
/// Customizes how enum values are mapped
/// </summary>
/// <param name="source">The enum value to map from</param>
/// <param name="target">The enum value to map to</param>
/// <param name="source">The value to map from</param>
/// <param name="target">The value to map to</param>
public MapEnumValueAttribute(object source, object target)
{
Source = (Enum)source;
Target = (Enum)target;
Source = source;
Target = target;
}

/// <summary>
/// What to map to
/// </summary>
public Enum Target { get; }
public object Target { get; }

/// <summary>
/// What to map from
/// </summary>
public Enum Source { get; }
public object Source { get; }
}
4 changes: 2 additions & 2 deletions src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ Riok.Mapperly.Abstractions.MapEnumAttribute.MapEnumAttribute(Riok.Mapperly.Abstr
Riok.Mapperly.Abstractions.MapEnumAttribute.Strategy.get -> Riok.Mapperly.Abstractions.EnumMappingStrategy
Riok.Mapperly.Abstractions.MapEnumValueAttribute
Riok.Mapperly.Abstractions.MapEnumValueAttribute.MapEnumValueAttribute(object! source, object! target) -> void
Riok.Mapperly.Abstractions.MapEnumValueAttribute.Source.get -> System.Enum!
Riok.Mapperly.Abstractions.MapEnumValueAttribute.Target.get -> System.Enum!
Riok.Mapperly.Abstractions.MapperAttribute
Riok.Mapperly.Abstractions.MapperAttribute.EnabledConversions.get -> Riok.Mapperly.Abstractions.MappingConversionType
Riok.Mapperly.Abstractions.MapperAttribute.EnabledConversions.set -> void
Expand Down Expand Up @@ -188,3 +186,5 @@ Riok.Mapperly.Abstractions.MapPropertyAttribute.MapPropertyAttribute(string! sou
Riok.Mapperly.Abstractions.MapPropertyAttribute.MapPropertyAttribute(string![]! source, string! target) -> void
Riok.Mapperly.Abstractions.MapperAttribute.IncludedConstructors.get -> Riok.Mapperly.Abstractions.MemberVisibility
Riok.Mapperly.Abstractions.MapperAttribute.IncludedConstructors.set -> void
Riok.Mapperly.Abstractions.MapEnumValueAttribute.Source.get -> object!
Riok.Mapperly.Abstractions.MapEnumValueAttribute.Target.get -> object!
2 changes: 1 addition & 1 deletion src/Riok.Mapperly/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ RMG038 | Mapper | Warning | An enum member could not be found on the target
RMG081 | Mapper | Error | A mapping method with additional parameters cannot be a default mapping
RMG082 | Mapper | Warning | An additional mapping method parameter is not mapped
RMG083 | Mapper | Info | Cannot map to read only type
RMG084 | Mapper | Error | Multiple mappings are configured for the same source string

### Removed Rules

Expand All @@ -195,4 +196,3 @@ RMG017 | Mapper | Warning | An init only member can have one configuration a
RMG026 | Mapper | Info | Cannot map from indexed member
RMG027 | Mapper | Warning | A constructor parameter can have one configuration at max
RMG028 | Mapper | Warning | Constructor parameter cannot handle target paths

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Abstractions;

namespace Riok.Mapperly.Configuration;
Expand All @@ -9,4 +8,4 @@ namespace Riok.Mapperly.Configuration;
/// </summary>
/// <param name="Source">The source constant of the enum value mapping.</param>
/// <param name="Target">The target constant of the enum value mapping.</param>
public record EnumValueMappingConfiguration(IFieldSymbol Source, IFieldSymbol Target);
public record EnumValueMappingConfiguration(AttributeValue Source, AttributeValue Target);
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using Microsoft.CodeAnalysis;
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Diagnostics;

namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;

internal static class EnumMappingDiagnosticReporter
{
public static void AddUnmatchedSourceIgnoredMembers(MappingBuilderContext ctx, ISet<IFieldSymbol> ignoredSourceMembers)
{
var sourceFields = ctx.SymbolAccessor.GetAllFields(ctx.Source).ToHashSet();
var unmatchedSourceMembers = ignoredSourceMembers.Where(m => !sourceFields.Contains(m));

foreach (var member in unmatchedSourceMembers)
{
ctx.ReportDiagnostic(
DiagnosticDescriptors.IgnoredEnumSourceMemberNotFound,
member.Name,
member.ConstantValue!,
ctx.Source,
ctx.Target
);
}
}

public static void AddUnmatchedTargetIgnoredMembers(MappingBuilderContext ctx, ISet<IFieldSymbol> ignoredTargetMembers)
{
var targetFields = ctx.SymbolAccessor.GetAllFields(ctx.Target).ToHashSet();
var unmatchedTargetMembers = ignoredTargetMembers.Where(m => !targetFields.Contains(m));

foreach (var member in unmatchedTargetMembers)
{
ctx.ReportDiagnostic(
DiagnosticDescriptors.IgnoredEnumTargetMemberNotFound,
member.Name,
member.ConstantValue!,
ctx.Source,
ctx.Target
);
}
}

public static void AddUnmappedTargetMembersDiagnostics(
MappingBuilderContext ctx,
ISet<IFieldSymbol> mappings,
IEnumerable<IFieldSymbol> targetMembers
)
{
if (!ctx.Configuration.Enum.RequiredMappingStrategy.HasFlag(RequiredMappingStrategy.Target))
return;

var missingTargetMembers = targetMembers.Where(field =>
!mappings.Contains(field) && ctx.Configuration.Enum.FallbackValue?.ConstantValue?.Equals(field.ConstantValue) != true
);
foreach (var member in missingTargetMembers)
{
ctx.ReportDiagnostic(
DiagnosticDescriptors.TargetEnumValueNotMapped,
member.Name,
member.ConstantValue!,
ctx.Target,
ctx.Source
);
}
}

public static void AddUnmappedSourceMembersDiagnostics(
MappingBuilderContext ctx,
ISet<IFieldSymbol> mappings,
IEnumerable<IFieldSymbol> sourceMembers
)
{
if (!ctx.Configuration.Enum.RequiredMappingStrategy.HasFlag(RequiredMappingStrategy.Source))
return;

var missingSourceMembers = sourceMembers.Where(field => !mappings.Contains(field));
foreach (var member in missingSourceMembers)
{
ctx.ReportDiagnostic(
DiagnosticDescriptors.SourceEnumValueNotMapped,
member.Name,
member.ConstantValue!,
ctx.Source,
ctx.Target
);
}
}
}
Loading

0 comments on commit 6a1e415

Please sign in to comment.