diff --git a/docs/docs/configuration/enum.mdx b/docs/docs/configuration/enum.mdx
index f8cb6a3b7c..20db9a82c3 100644
--- a/docs/docs/configuration/enum.mdx
+++ b/docs/docs/configuration/enum.mdx
@@ -56,11 +56,68 @@ The `IgnoreCase` property allows to opt in for case insensitive mappings (defaul
+## Enum from/to string naming strategies
+
+Enum from/to strings mappings can be customized by setting the enum naming strategy to be used.
+You can specify the naming strategy using `NamingStrategy` in `MapEnumAttribute` or `EnumNamingStrategy` in `MapperAttribute` and `MapperDefaultsAttribute`.
+Available naming strategies:
+
+| Name | Description |
+| -------------- | ----------------------------------------------- |
+| MemberName | Matches enum values using their name. (default) |
+| CamelCase | Matches enum values using camelCase. |
+| PascalCase | Matches enum values using PascalCase. |
+| SnakeCase | Matches enum values using snake_case. |
+| UpperSnakeCase | Matches enum values using UPPER_SNAKE_CASE. |
+| KebabCase | Matches enum values using kebab-case. |
+| UpperKebabCase | Matches enum values using UPPER-KEBAB-CASE. |
+
+Note that explicit enum mappings (`MapEnumValue`) and fallback values (`FallbackValue` in `MapEnum`)
+are not affected by naming strategies.
+
+
+
+
+ Applied to all enums mapped inside this mapper.
+
+ ```csharp
+ // highlight-start
+ [Mapper(EnumNamingStrategy = EnumNamingStrategy.SnakeCase)]
+ // highlight-end
+ public partial class CarMapper
+ {
+ ...
+ }
+ ```
+
+
+
+
+ Applied to the specific enum mapped by this method.
+ Attribute is only valid on mapping method with enums as parameters.
+
+ ```csharp
+ [Mapper]
+ public partial class CarMapper
+ {
+ // highlight-start
+ [MapEnum(EnumMappingStrategy.ByName, NamingStrategy = EnumNamingStrategy.SnakeCase)]
+ // highlight-end
+ public partial CarMakeDto MapMake(CarMake make);
+ }
+ ```
+
+
+
+
+
## Manually mapped enum values
To explicitly map enum values the `MapEnumValueAttibute` can be used.
Attribute is only valid on enum-to-enum, enum-to-string and string-to-enum mappings.
+Explicit enum mappings are not affected by enum naming strategies.
+
```csharp
[Mapper]
public partial class CarMapper
@@ -106,6 +163,7 @@ To map to a fallback value instead of throwing when encountering an unknown valu
the `FallbackValue` property on the `MapEnum` attribute can be used.
`FallbackValue` is supported by `ByName` and `ByValueCheckDefined`.
+`FallbackValue` is not affected by enum naming strategies.
```csharp
[Mapper]
diff --git a/src/Riok.Mapperly.Abstractions/EnumNamingStrategy.cs b/src/Riok.Mapperly.Abstractions/EnumNamingStrategy.cs
new file mode 100644
index 0000000000..ecbc9711b1
--- /dev/null
+++ b/src/Riok.Mapperly.Abstractions/EnumNamingStrategy.cs
@@ -0,0 +1,42 @@
+namespace Riok.Mapperly.Abstractions;
+
+///
+/// Defines the strategy to use when mapping an enum from/to string.
+///
+public enum EnumNamingStrategy
+{
+ ///
+ /// Matches enum values using their name.
+ ///
+ MemberName,
+
+ ///
+ /// Matches enum values using camelCase.
+ ///
+ CamelCase,
+
+ ///
+ /// Matches enum values using PascalCase.
+ ///
+ PascalCase,
+
+ ///
+ /// Matches enum values using snake_case.
+ ///
+ SnakeCase,
+
+ ///
+ /// Matches enum values using UPPER_SNAKE_CASE.
+ ///
+ UpperSnakeCase,
+
+ ///
+ /// Matches enum values using kebab-case.
+ ///
+ KebabCase,
+
+ ///
+ /// Matches enum values using UPPER-KEBAB-CASE.
+ ///
+ UpperKebabCase,
+}
diff --git a/src/Riok.Mapperly.Abstractions/MapEnumAttribute.cs b/src/Riok.Mapperly.Abstractions/MapEnumAttribute.cs
index c7ad02bb77..0dd4f96dfd 100644
--- a/src/Riok.Mapperly.Abstractions/MapEnumAttribute.cs
+++ b/src/Riok.Mapperly.Abstractions/MapEnumAttribute.cs
@@ -19,7 +19,7 @@ public MapEnumAttribute(EnumMappingStrategy strategy)
}
///
- /// The strategy to be used to map enums.
+ /// The strategy to be used to map enums to enums.
///
public EnumMappingStrategy Strategy { get; }
@@ -32,4 +32,9 @@ public MapEnumAttribute(EnumMappingStrategy strategy)
/// The fallback value if an enum cannot be mapped, used instead of throwing.
///
public object? FallbackValue { get; set; }
+
+ ///
+ /// The strategy to be used to map enums from/to strings.
+ ///
+ public EnumNamingStrategy NamingStrategy { get; set; }
}
diff --git a/src/Riok.Mapperly.Abstractions/MapperAttribute.cs b/src/Riok.Mapperly.Abstractions/MapperAttribute.cs
index 84a269ee33..664bcc5227 100644
--- a/src/Riok.Mapperly.Abstractions/MapperAttribute.cs
+++ b/src/Riok.Mapperly.Abstractions/MapperAttribute.cs
@@ -125,4 +125,10 @@ public class MapperAttribute : Attribute
/// partial methods are discovered.
///
public bool AutoUserMappings { get; set; } = true;
+
+ ///
+ /// The default enum naming strategy.
+ /// Can be overwritten on specific enums via mapping method configurations.
+ ///
+ public EnumNamingStrategy EnumNamingStrategy { get; set; } = EnumNamingStrategy.MemberName;
}
diff --git a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
index f40e11a59d..01a2c66689 100644
--- a/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
+++ b/src/Riok.Mapperly.Abstractions/PublicAPI.Shipped.txt
@@ -188,3 +188,15 @@ Riok.Mapperly.Abstractions.MapperAttribute.IncludedConstructors.get -> Riok.Mapp
Riok.Mapperly.Abstractions.MapperAttribute.IncludedConstructors.set -> void
Riok.Mapperly.Abstractions.MapEnumValueAttribute.Source.get -> object!
Riok.Mapperly.Abstractions.MapEnumValueAttribute.Target.get -> object!
+Riok.Mapperly.Abstractions.EnumNamingStrategy
+Riok.Mapperly.Abstractions.EnumNamingStrategy.MemberName = 0 -> Riok.Mapperly.Abstractions.EnumNamingStrategy
+Riok.Mapperly.Abstractions.EnumNamingStrategy.CamelCase = 1 -> Riok.Mapperly.Abstractions.EnumNamingStrategy
+Riok.Mapperly.Abstractions.EnumNamingStrategy.PascalCase = 2 -> Riok.Mapperly.Abstractions.EnumNamingStrategy
+Riok.Mapperly.Abstractions.EnumNamingStrategy.SnakeCase = 3 -> Riok.Mapperly.Abstractions.EnumNamingStrategy
+Riok.Mapperly.Abstractions.EnumNamingStrategy.UpperSnakeCase = 4 -> Riok.Mapperly.Abstractions.EnumNamingStrategy
+Riok.Mapperly.Abstractions.EnumNamingStrategy.KebabCase = 5 -> Riok.Mapperly.Abstractions.EnumNamingStrategy
+Riok.Mapperly.Abstractions.EnumNamingStrategy.UpperKebabCase = 6 -> Riok.Mapperly.Abstractions.EnumNamingStrategy
+Riok.Mapperly.Abstractions.MapEnumAttribute.NamingStrategy.get -> Riok.Mapperly.Abstractions.EnumNamingStrategy
+Riok.Mapperly.Abstractions.MapEnumAttribute.NamingStrategy.set -> void
+Riok.Mapperly.Abstractions.MapperAttribute.EnumNamingStrategy.get -> Riok.Mapperly.Abstractions.EnumNamingStrategy
+Riok.Mapperly.Abstractions.MapperAttribute.EnumNamingStrategy.set -> void
diff --git a/src/Riok.Mapperly/AnalyzerReleases.Shipped.md b/src/Riok.Mapperly/AnalyzerReleases.Shipped.md
index cdcd8920d0..dd7ae0cd63 100644
--- a/src/Riok.Mapperly/AnalyzerReleases.Shipped.md
+++ b/src/Riok.Mapperly/AnalyzerReleases.Shipped.md
@@ -187,6 +187,7 @@ RMG081 | Mapper | Error | A mapping method with additional parameters cann
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
+RMG085 | Mapper | Error | Invalid usage of Fallback value
### Removed Rules
diff --git a/src/Riok.Mapperly/Configuration/EnumConfiguration.cs b/src/Riok.Mapperly/Configuration/EnumConfiguration.cs
index 8ea51ae569..212c140432 100644
--- a/src/Riok.Mapperly/Configuration/EnumConfiguration.cs
+++ b/src/Riok.Mapperly/Configuration/EnumConfiguration.cs
@@ -1,4 +1,3 @@
-using Microsoft.CodeAnalysis;
using Riok.Mapperly.Abstractions;
namespace Riok.Mapperly.Configuration;
@@ -10,7 +9,7 @@ namespace Riok.Mapperly.Configuration;
public class EnumConfiguration(EnumMappingStrategy strategy)
{
///
- /// The strategy to be used to map enums.
+ /// The strategy to be used to map enums to enums.
///
public EnumMappingStrategy Strategy { get; } = strategy;
@@ -22,5 +21,10 @@ public class EnumConfiguration(EnumMappingStrategy strategy)
///
/// The fallback value if an enum cannot be mapped, used instead of throwing.
///
- public IFieldSymbol? FallbackValue { get; set; }
+ public AttributeValue? FallbackValue { get; set; }
+
+ ///
+ /// The strategy to be used to map enums from/to strings.
+ ///
+ public EnumNamingStrategy NamingStrategy { get; set; }
}
diff --git a/src/Riok.Mapperly/Configuration/EnumMappingConfiguration.cs b/src/Riok.Mapperly/Configuration/EnumMappingConfiguration.cs
index ac2f31fce5..b2af7e1b35 100644
--- a/src/Riok.Mapperly/Configuration/EnumMappingConfiguration.cs
+++ b/src/Riok.Mapperly/Configuration/EnumMappingConfiguration.cs
@@ -6,11 +6,12 @@ namespace Riok.Mapperly.Configuration;
public record EnumMappingConfiguration(
EnumMappingStrategy Strategy,
bool IgnoreCase,
- IFieldSymbol? FallbackValue,
+ AttributeValue? FallbackValue,
IReadOnlyCollection IgnoredSourceMembers,
IReadOnlyCollection IgnoredTargetMembers,
IReadOnlyCollection ExplicitMappings,
- RequiredMappingStrategy RequiredMappingStrategy
+ RequiredMappingStrategy RequiredMappingStrategy,
+ EnumNamingStrategy NamingStrategy
)
{
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 f2b24c7ec8..2bee7dc7ce 100644
--- a/src/Riok.Mapperly/Configuration/MapperConfiguration.cs
+++ b/src/Riok.Mapperly/Configuration/MapperConfiguration.cs
@@ -118,4 +118,10 @@ public record MapperConfiguration
/// Whether to consider non-partial methods in a mapper as user implemented mapping methods.
///
public bool? AutoUserMappings { get; init; }
+
+ ///
+ /// The default enum naming strategy.
+ /// Can be overwritten on specific enums via mapping method configurations.
+ ///
+ public EnumNamingStrategy? EnumNamingStrategy { get; init; }
}
diff --git a/src/Riok.Mapperly/Configuration/MapperConfigurationMerger.cs b/src/Riok.Mapperly/Configuration/MapperConfigurationMerger.cs
index ee10b1db41..fa0e603345 100644
--- a/src/Riok.Mapperly/Configuration/MapperConfigurationMerger.cs
+++ b/src/Riok.Mapperly/Configuration/MapperConfigurationMerger.cs
@@ -65,6 +65,9 @@ public static MapperAttribute Merge(MapperConfiguration mapperConfiguration, Map
mapper.AutoUserMappings =
mapperConfiguration.AutoUserMappings ?? defaultMapperConfiguration.AutoUserMappings ?? mapper.AutoUserMappings;
+ mapper.EnumNamingStrategy =
+ mapperConfiguration.EnumNamingStrategy ?? defaultMapperConfiguration.EnumNamingStrategy ?? mapper.EnumNamingStrategy;
+
return mapper;
}
}
diff --git a/src/Riok.Mapperly/Configuration/MapperConfigurationReader.cs b/src/Riok.Mapperly/Configuration/MapperConfigurationReader.cs
index b99bd14d9d..86a353ea67 100644
--- a/src/Riok.Mapperly/Configuration/MapperConfigurationReader.cs
+++ b/src/Riok.Mapperly/Configuration/MapperConfigurationReader.cs
@@ -33,7 +33,8 @@ MapperConfiguration defaultMapperConfiguration
[],
[],
[],
- mapper.RequiredMappingStrategy
+ mapper.RequiredMappingStrategy,
+ mapper.EnumNamingStrategy
),
new MembersMappingConfiguration([], [], [], [], [], mapper.IgnoreObsoleteMembersStrategy, mapper.RequiredMappingStrategy),
[]
@@ -162,7 +163,8 @@ private EnumMappingConfiguration BuildEnumConfig(MappingConfigurationReference c
ignoredSources,
ignoredTargets,
explicitMappings,
- requiredMapping ?? MapperConfiguration.Enum.RequiredMappingStrategy
+ requiredMapping ?? MapperConfiguration.Enum.RequiredMappingStrategy,
+ configData?.NamingStrategy ?? MapperConfiguration.Enum.NamingStrategy
);
}
}
diff --git a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/EnumMappingDiagnosticReporter.cs b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/EnumMappingDiagnosticReporter.cs
index c4a31174df..4eae55fd78 100644
--- a/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/EnumMappingDiagnosticReporter.cs
+++ b/src/Riok.Mapperly/Descriptors/MappingBodyBuilders/BuilderContext/EnumMappingDiagnosticReporter.cs
@@ -49,8 +49,9 @@ IEnumerable targetMembers
if (!ctx.Configuration.Enum.RequiredMappingStrategy.HasFlag(RequiredMappingStrategy.Target))
return;
+ var fallbackValue = ctx.Configuration.Enum.FallbackValue?.ConstantValue.Value;
var missingTargetMembers = targetMembers.Where(field =>
- !mappings.Contains(field) && ctx.Configuration.Enum.FallbackValue?.ConstantValue?.Equals(field.ConstantValue) != true
+ !mappings.Contains(field) && fallbackValue?.Equals(field.ConstantValue) is not true
);
foreach (var member in missingTargetMembers)
{
diff --git a/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumToEnumMappingBuilder.cs b/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumToEnumMappingBuilder.cs
index ae8f1e0a80..a532739fe4 100644
--- a/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumToEnumMappingBuilder.cs
+++ b/src/Riok.Mapperly/Descriptors/MappingBuilders/EnumToEnumMappingBuilder.cs
@@ -1,10 +1,14 @@
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
using Riok.Mapperly.Abstractions;
using Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Descriptors.Mappings.Enums;
using Riok.Mapperly.Diagnostics;
using Riok.Mapperly.Helpers;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+using static Riok.Mapperly.Emit.Syntax.SyntaxFactoryHelper;
namespace Riok.Mapperly.Descriptors.MappingBuilders;
@@ -68,7 +72,7 @@ private static INewInstanceMapping BuildEnumToEnumCastMapping(
EqualityComparer