Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 72 additions & 60 deletions src/AutoMapper/Configuration/ConfigurationValidator.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
using AutoMapper.Internal.Mappers;

namespace AutoMapper.Configuration;
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly record struct ConfigurationValidator(IGlobalConfigurationExpression Expression)
{
private void Validate(ValidationContext context)
{
foreach (var validator in Expression.Validators)
{
validator(context);
}
}
public void AssertConfigurationExpressionIsValid(IGlobalConfiguration config, TypeMap[] typeMaps)
{
var duplicateTypeMapConfigs = Expression.Profiles.Append((Profile)Expression)
Expand All @@ -27,6 +21,7 @@ public void AssertConfigurationExpressionIsValid(IGlobalConfiguration config, Ty
}
public void AssertConfigurationIsValid(IGlobalConfiguration config, TypeMap[] typeMaps)
{
List<Exception> configExceptions = [];
var badTypeMaps =
(from typeMap in typeMaps
where typeMap.ShouldCheckForValid
Expand All @@ -37,20 +32,15 @@ where unmappedPropertyNames.Length > 0 || !canConstruct
).ToArray();
if (badTypeMaps.Length > 0)
{
throw new AutoMapperConfigurationException(badTypeMaps);
configExceptions.Add(new AutoMapperConfigurationException(badTypeMaps));
}
HashSet<TypeMap> typeMapsChecked = [];
List<Exception> configExceptions = [];

foreach (var typeMap in typeMaps)
{
try
{
DryRunTypeMap(config, typeMapsChecked, typeMap.Types, typeMap, null);
}
catch (Exception e)
{
configExceptions.Add(e);
}
var badMemberMaps = GetInvalidMemberMaps(typeMap.Types, typeMap, Expression.Validators)
.Select(memberMap =>
new AutoMapperConfigurationException(memberMap.TypeMap.Types) { MemberMap = memberMap });
configExceptions.AddRange(badMemberMaps);
}
if (configExceptions.Count > 1)
{
Expand All @@ -60,60 +50,82 @@ where unmappedPropertyNames.Length > 0 || !canConstruct
{
throw configExceptions[0];
}
}
private void DryRunTypeMap(IGlobalConfiguration config, HashSet<TypeMap> typeMapsChecked, TypePair types, TypeMap typeMap, MemberMap memberMap)
{
if(typeMap == null)

IEnumerable<MemberMap> GetInvalidMemberMaps(TypePair types, TypeMap typeMap, List<Action<ValidationContext>> validators, MemberMap memberMap = null, HashSet<TypeMap> typeMapsChecked = null)
{
if (types.ContainsGenericParameters)
typeMapsChecked ??= [];

if (typeMap == null)
{
return;
if (types.ContainsGenericParameters)
{
yield break;
}
typeMap = config.ResolveTypeMap(types.SourceType, types.DestinationType);
}
typeMap = config.ResolveTypeMap(types.SourceType, types.DestinationType);
}
if (typeMap != null)
{
if (typeMapsChecked.Contains(typeMap))
{
return;
}
typeMapsChecked.Add(typeMap);
Validate(new(types, memberMap, typeMap));
if(!typeMap.ShouldCheckForValid)
if (typeMap != null)
{
return;
if (typeMapsChecked.Contains(typeMap))
{
yield break;
}
typeMapsChecked.Add(typeMap);
Validate(new(types, memberMap, typeMap));
if (!typeMap.ShouldCheckForValid)
{
yield break;
}

var invalidPropertyMemberMaps = GetPropertyMemberMaps(typeMap)
.SelectMany(p => GetInvalidMemberMaps(new(p.SourceType, p.DestinationType), null, validators, p, typeMapsChecked));
foreach (var invalidMemberMap in invalidPropertyMemberMaps)
{
yield return invalidMemberMap;
}
}
CheckPropertyMaps(config, typeMapsChecked, typeMap);
}
else
{
var mapperToUse = config.FindMapper(types);
if (mapperToUse == null)
else
{
throw new AutoMapperConfigurationException(memberMap.TypeMap.Types) { MemberMap = memberMap };
var mapperToUse = config.FindMapper(types);
if (mapperToUse == null)
{
yield return memberMap;
}
else
{
Validate(new(types, memberMap, ObjectMapper: mapperToUse));
if (mapperToUse.GetAssociatedTypes(types) is TypePair newTypes && newTypes != types)
{
var invalidMemberMaps = GetInvalidMemberMaps(newTypes, null, validators, memberMap, typeMapsChecked);
foreach (var invalidMemberMap in invalidMemberMaps)
{
yield return invalidMemberMap;
};
}
}
}
Validate(new(types, memberMap, ObjectMapper: mapperToUse));
if (mapperToUse.GetAssociatedTypes(types) is TypePair newTypes && newTypes != types)

void Validate(ValidationContext context)
{
DryRunTypeMap(config, typeMapsChecked, newTypes, null, memberMap);
foreach (var validator in validators)
{
validator(context);
}
}
}
}
private void CheckPropertyMaps(IGlobalConfiguration config, HashSet<TypeMap> typeMapsChecked, TypeMap typeMap)
{
foreach (var memberMap in typeMap.MemberMaps)

IEnumerable<MemberMap> GetPropertyMemberMaps(TypeMap typeMap)
{
if(memberMap.Ignored || (memberMap is PropertyMap && typeMap.ConstructorParameterMatches(memberMap.DestinationName)))
return typeMap.MemberMaps.Where(memberMap =>
{
continue;
}
var sourceType = memberMap.SourceType;
// when we don't know what the source type is, bail
if (sourceType.IsGenericParameter || sourceType == typeof(object))
{
continue;
}
DryRunTypeMap(config, typeMapsChecked, new(sourceType, memberMap.DestinationType), null, memberMap);
if (memberMap.Ignored || (memberMap is PropertyMap &&
typeMap.ConstructorParameterMatches(memberMap.DestinationName)))
{
return false;
}
var sourceType = memberMap.SourceType;
// when we don't know what the source type is, bail
return !sourceType.IsGenericParameter && sourceType != typeof(object);
});
}
}
}
Expand Down
37 changes: 37 additions & 0 deletions src/UnitTests/ConfigurationValidation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,43 @@ public void Should_fail_a_configuration_check()
}
}

public class When_testing_a_dto_with_mismatched_member_names_and_mismatched_types : NonValidatingSpecBase
{
public class Source
{
public decimal Foo { get; set; }
}

public class Destination
{
public Type Foo { get; set; }
public string Bar { get; set; }
}

protected override MapperConfiguration CreateConfiguration() =>
new(cfg => { cfg.CreateMap<Source, Destination>(); });

[Fact]
public void Should_throw_unmapped_member_and_mismatched_type_exceptions()
{
new Action(AssertConfigurationIsValid)
.ShouldThrow<AggregateException>()
.ShouldSatisfyAllConditions(
aex => aex.InnerExceptions.ShouldBeOfLength(2),
aex => aex.InnerExceptions[0]
.ShouldBeOfType<AutoMapperConfigurationException>()
.ShouldSatisfyAllConditions(
ex => ex.Errors.ShouldBeOfLength(1),
ex => ex.Errors[0].UnmappedPropertyNames.ShouldContain("Bar")),
aex => aex.InnerExceptions[1]
.ShouldBeOfType<AutoMapperConfigurationException>()
.ShouldSatisfyAllConditions(
ex => ex.MemberMap.ShouldNotBeNull(),
ex => ex.MemberMap.DestinationName.ShouldBe("Foo"))
);
}
}

public class When_testing_a_dto_with_member_type_mapped_mappings : AutoMapperSpecBase
{
private AutoMapperConfigurationException _exception;
Expand Down