-
-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add mapper defaults attribute for assemblies (#657)
- Loading branch information
Showing
15 changed files
with
452 additions
and
122 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
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 mapper default values in the assembly. | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Assembly)] | ||
public sealed class MapperDefaultsAttribute : 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,100 +1,92 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Riok.Mapperly.Abstractions; | ||
using Riok.Mapperly.Helpers; | ||
|
||
namespace Riok.Mapperly.Configuration; | ||
|
||
/// <summary> | ||
/// Contains all the properties of <see cref="MapperAttribute"/> and <see cref="MapperDefaultsAttribute"/> but all of them are nullable. | ||
/// This is needed to evaluate which properties are set in the <see cref="MapperAttribute"/> and <see cref="MapperDefaultsAttribute"/>. | ||
/// <remarks> | ||
/// Newly added properties must also be added to <see cref="Riok.Mapperly.Helpers.MapperConfigurationBuilder"/>. | ||
/// </remarks> | ||
/// </summary> | ||
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(AttributeDataAccessor dataAccessor, ISymbol mapperSymbol) | ||
{ | ||
_dataAccessor = dataAccessor; | ||
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; } | ||
} |
Oops, something went wrong.