-
-
Notifications
You must be signed in to change notification settings - Fork 141
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add default mapper attribute for assemblies
- Loading branch information
Showing
17 changed files
with
476 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace Riok.Mapperly.Abstractions; | ||
|
||
/// <summary> | ||
/// Used to set default mapper values in the assembly. | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Assembly)] | ||
public sealed class DefaultMapperAttribute : MapperAttribute { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,101 +1,85 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Riok.Mapperly.Abstractions; | ||
using Riok.Mapperly.Descriptors; | ||
using Riok.Mapperly.Helpers; | ||
|
||
namespace Riok.Mapperly.Configuration; | ||
|
||
public class MapperConfiguration | ||
{ | ||
private readonly MappingConfiguration _defaultConfiguration; | ||
private readonly AttributeDataAccessor _dataAccessor; | ||
/// <summary> | ||
/// Strategy on how to match mapping property names. | ||
/// </summary> | ||
public PropertyNameMappingStrategy? PropertyNameMappingStrategy { get; set; } | ||
|
||
public MapperConfiguration(SymbolAccessor symbolAccessor, ISymbol mapperSymbol) | ||
{ | ||
_dataAccessor = new AttributeDataAccessor(symbolAccessor); | ||
Mapper = _dataAccessor.AccessSingle<MapperAttribute>(mapperSymbol); | ||
_defaultConfiguration = new MappingConfiguration( | ||
new EnumMappingConfiguration( | ||
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>(), | ||
Mapper.IgnoreObsoleteMembersStrategy | ||
), | ||
Array.Empty<DerivedTypeMappingConfiguration>() | ||
); | ||
} | ||
/// <summary> | ||
/// The default enum mapping strategy. | ||
/// Can be overwritten on specific enums via mapping method configurations. | ||
/// </summary> | ||
public EnumMappingStrategy? EnumMappingStrategy { get; set; } | ||
|
||
public MapperAttribute Mapper { get; } | ||
/// <summary> | ||
/// Whether the case should be ignored for enum mappings. | ||
/// </summary> | ||
public bool? EnumMappingIgnoreCase { get; set; } | ||
|
||
public MappingConfiguration BuildFor(MappingConfigurationReference reference) | ||
{ | ||
if (reference.Method == null) | ||
return _defaultConfiguration; | ||
/// <summary> | ||
/// Specifies the behaviour in the case when the mapper tries to return <c>null</c> in a mapping method with a non-nullable return type. | ||
/// If set to <c>true</c> an <see cref="ArgumentNullException"/> is thrown. | ||
/// If set to <c>false</c> the mapper tries to return a default value. | ||
/// For a <see cref="string"/> this is <see cref="string.Empty"/>, | ||
/// for value types <c>default</c> | ||
/// and for reference types <c>new()</c> if a parameterless constructor exists or else an <see cref="ArgumentNullException"/> is thrown. | ||
/// </summary> | ||
public bool? ThrowOnMappingNullMismatch { get; set; } | ||
|
||
var enumConfig = BuildEnumConfig(reference); | ||
var propertiesConfig = BuildPropertiesConfig(reference.Method); | ||
var derivedTypesConfig = BuildDerivedTypeConfigs(reference.Method); | ||
return new MappingConfiguration(enumConfig, propertiesConfig, derivedTypesConfig); | ||
} | ||
/// <summary> | ||
/// Specifies the behaviour in the case when the mapper tries to set a non-nullable property to a <c>null</c> value. | ||
/// If set to <c>true</c> an <see cref="ArgumentNullException"/> is thrown. | ||
/// If set to <c>false</c> the property assignment is ignored. | ||
/// This is ignored for required init properties and <see cref="IQueryable{T}"/> projection mappings. | ||
/// </summary> | ||
public bool? ThrowOnPropertyMappingNullMismatch { get; set; } | ||
|
||
private IReadOnlyCollection<DerivedTypeMappingConfiguration> BuildDerivedTypeConfigs(IMethodSymbol method) | ||
{ | ||
return _dataAccessor | ||
.Access<MapDerivedTypeAttribute, DerivedTypeMappingConfiguration>(method) | ||
.Concat(_dataAccessor.Access<MapDerivedTypeAttribute<object, object>, DerivedTypeMappingConfiguration>(method)) | ||
.ToList(); | ||
} | ||
/// <summary> | ||
/// Specifies whether <c>null</c> values are assigned to the target. | ||
/// If <c>true</c> (default), the source is <c>null</c>, and the target does allow <c>null</c> values, | ||
/// <c>null</c> is assigned. | ||
/// If <c>false</c>, <c>null</c> values are never assigned to the target property. | ||
/// This is ignored for required init properties and <see cref="IQueryable{T}"/> projection mappings. | ||
/// </summary> | ||
public bool? AllowNullPropertyAssignment { get; set; } | ||
|
||
private PropertiesMappingConfiguration BuildPropertiesConfig(IMethodSymbol method) | ||
{ | ||
var ignoredSourceProperties = _dataAccessor | ||
.Access<MapperIgnoreSourceAttribute>(method) | ||
.Select(x => x.Source) | ||
.WhereNotNull() | ||
.ToList(); | ||
var ignoredTargetProperties = _dataAccessor | ||
.Access<MapperIgnoreTargetAttribute>(method) | ||
.Select(x => x.Target) | ||
.WhereNotNull() | ||
.ToList(); | ||
var explicitMappings = _dataAccessor.Access<MapPropertyAttribute, PropertyMappingConfiguration>(method).ToList(); | ||
var ignoreObsolete = _dataAccessor.Access<MapperIgnoreObsoleteMembersAttribute>(method).FirstOrDefault() is not { } methodIgnore | ||
? _defaultConfiguration.Properties.IgnoreObsoleteMembersStrategy | ||
: methodIgnore.IgnoreObsoleteStrategy; | ||
/// <summary> | ||
/// Whether to always deep copy objects. | ||
/// Eg. when the type <c>Person[]</c> should be mapped to the same type <c>Person[]</c>, | ||
/// when <c>false</c>, the same array is reused. | ||
/// when <c>true</c>, the array and each person is cloned. | ||
/// </summary> | ||
public bool? UseDeepCloning { get; set; } | ||
|
||
return new PropertiesMappingConfiguration(ignoredSourceProperties, ignoredTargetProperties, explicitMappings, ignoreObsolete); | ||
} | ||
/// <summary> | ||
/// Enabled conversions which Mapperly automatically implements. | ||
/// By default all supported type conversions are enabled. | ||
/// <example> | ||
/// Eg. to disable all automatically implemented conversions:<br /> | ||
/// <c>EnabledConversions = MappingConversionType.None</c> | ||
/// </example> | ||
/// <example> | ||
/// Eg. to disable <c>ToString()</c> method calls:<br /> | ||
/// <c>EnabledConversions = MappingConversionType.All & ~MappingConversionType.ToStringMethod</c> | ||
/// </example> | ||
/// </summary> | ||
public MappingConversionType? EnabledConversions { get; set; } | ||
|
||
private EnumMappingConfiguration BuildEnumConfig(MappingConfigurationReference configRef) | ||
{ | ||
if (configRef.Method == null || !configRef.Source.IsEnum() && !configRef.Target.IsEnum()) | ||
return _defaultConfiguration.Enum; | ||
/// <summary> | ||
/// Enables the reference handling feature. | ||
/// Disabled by default for performance reasons. | ||
/// When enabled, an <see cref="IReferenceHandler"/> instance is passed through the mapping methods | ||
/// to keep track of and reuse existing target object instances. | ||
/// </summary> | ||
public bool? UseReferenceHandling { get; set; } | ||
|
||
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 | ||
); | ||
} | ||
/// <summary> | ||
/// The ignore obsolete attribute strategy. Determines how <see cref="ObsoleteAttribute"/> marked members are mapped. | ||
/// Defaults to <see cref="IgnoreObsoleteMembersStrategy.None"/>. | ||
/// </summary> | ||
public IgnoreObsoleteMembersStrategy? IgnoreObsoleteMembersStrategy { get; set; } | ||
} |
104 changes: 104 additions & 0 deletions
104
src/Riok.Mapperly/Configuration/MergedMapperConfiguration.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Riok.Mapperly.Abstractions; | ||
using Riok.Mapperly.Descriptors; | ||
using Riok.Mapperly.Helpers; | ||
|
||
namespace Riok.Mapperly.Configuration; | ||
|
||
public class MergedMapperConfiguration | ||
{ | ||
private readonly MappingConfiguration _defaultConfiguration; | ||
private readonly AttributeDataAccessor _dataAccessor; | ||
|
||
public MergedMapperConfiguration(SymbolAccessor symbolAccessor, ISymbol mapperSymbol) | ||
{ | ||
_dataAccessor = new AttributeDataAccessor(symbolAccessor); | ||
var mapperConfiguration = _dataAccessor.AccessSingle<MapperAttribute, MapperConfiguration>(mapperSymbol); | ||
var defaultMapperConfiguration = _dataAccessor.AccessAssemblyFirstOrDefault<DefaultMapperAttribute, MapperConfiguration>(); | ||
Mapper = MapperConfigurationBuilder.Merge(mapperConfiguration, defaultMapperConfiguration); | ||
|
||
_defaultConfiguration = new MappingConfiguration( | ||
new EnumMappingConfiguration( | ||
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>(), | ||
Mapper.IgnoreObsoleteMembersStrategy | ||
), | ||
Array.Empty<DerivedTypeMappingConfiguration>() | ||
); | ||
} | ||
|
||
public MapperAttribute Mapper { get; } | ||
|
||
public MappingConfiguration BuildFor(MappingConfigurationReference reference) | ||
{ | ||
if (reference.Method == null) | ||
return _defaultConfiguration; | ||
|
||
var enumConfig = BuildEnumConfig(reference); | ||
var propertiesConfig = BuildPropertiesConfig(reference.Method); | ||
var derivedTypesConfig = BuildDerivedTypeConfigs(reference.Method); | ||
return new MappingConfiguration(enumConfig, propertiesConfig, derivedTypesConfig); | ||
} | ||
|
||
private IReadOnlyCollection<DerivedTypeMappingConfiguration> BuildDerivedTypeConfigs(IMethodSymbol method) | ||
{ | ||
return _dataAccessor | ||
.Access<MapDerivedTypeAttribute, DerivedTypeMappingConfiguration>(method) | ||
.Concat(_dataAccessor.Access<MapDerivedTypeAttribute<object, object>, DerivedTypeMappingConfiguration>(method)) | ||
.ToList(); | ||
} | ||
|
||
private PropertiesMappingConfiguration BuildPropertiesConfig(IMethodSymbol method) | ||
{ | ||
var ignoredSourceProperties = _dataAccessor | ||
.Access<MapperIgnoreSourceAttribute>(method) | ||
.Select(x => x.Source) | ||
.WhereNotNull() | ||
.ToList(); | ||
var ignoredTargetProperties = _dataAccessor | ||
.Access<MapperIgnoreTargetAttribute>(method) | ||
.Select(x => x.Target) | ||
.WhereNotNull() | ||
.ToList(); | ||
var explicitMappings = _dataAccessor.Access<MapPropertyAttribute, PropertyMappingConfiguration>(method).ToList(); | ||
var ignoreObsolete = _dataAccessor.Access<MapperIgnoreObsoleteMembersAttribute>(method).FirstOrDefault() is not { } methodIgnore | ||
? _defaultConfiguration.Properties.IgnoreObsoleteMembersStrategy | ||
: methodIgnore.IgnoreObsoleteStrategy; | ||
|
||
return new PropertiesMappingConfiguration(ignoredSourceProperties, ignoredTargetProperties, explicitMappings, ignoreObsolete); | ||
} | ||
|
||
private EnumMappingConfiguration BuildEnumConfig(MappingConfigurationReference configRef) | ||
{ | ||
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 | ||
); | ||
} | ||
} |
Oops, something went wrong.