Skip to content

Commit a384948

Browse files
committed
don't throw and catch on validation
1 parent b521096 commit a384948

File tree

5 files changed

+115
-80
lines changed

5 files changed

+115
-80
lines changed

.editorconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ end_of_line = lf
66
indent_style = space
77
indent_size = 4
88

9+
csharp_space_around_binary_operators = before_and_after
10+
911
#### Naming styles ####
1012

1113
# Constants are PascalCase

src/AutoMapper/ApiCompatBaseline.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.NullableAttri
3131
MembersMustExist : Member 'public void AutoMapper.Configuration.MappingExpression..ctor(AutoMapper.Internal.TypePair, AutoMapper.MemberList)' does not exist in the implementation but it does exist in the contract.
3232
MembersMustExist : Member 'public void AutoMapper.Configuration.MappingExpression<TSource, TDestination>..ctor(AutoMapper.MemberList, System.Type, System.Type)' does not exist in the implementation but it does exist in the contract.
3333
MembersMustExist : Member 'protected void AutoMapper.Configuration.MappingExpressionBase<TSource, TDestination, TMappingExpression>..ctor(AutoMapper.MemberList, System.Type, System.Type)' does not exist in the implementation but it does exist in the contract.
34+
MembersMustExist : Member 'public void AutoMapper.Configuration.ValidationContext..ctor(AutoMapper.Internal.TypePair, AutoMapper.MemberMap, AutoMapper.TypeMap, AutoMapper.Internal.Mappers.IObjectMapper)' does not exist in the implementation but it does exist in the contract.
3435
CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.IgnoreAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation.
3536
CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.MapAtRuntimeAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation.
3637
CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.MappingOrderAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation.
@@ -69,4 +70,4 @@ CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.NullableAttri
6970
CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.NullableContextAttribute' exists on 'AutoMapper.QueryableExtensions.Extensions.ProjectTo<TDestination>(System.Linq.IQueryable, AutoMapper.IConfigurationProvider, System.Object, System.Linq.Expressions.Expression<System.Func<TDestination, System.Object>>[])' in the contract but not the implementation.
7071
CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.NullableAttribute' exists on parameter 'parameters' on member 'AutoMapper.QueryableExtensions.Extensions.ProjectTo<TDestination>(System.Linq.IQueryable, AutoMapper.IConfigurationProvider, System.Object, System.Linq.Expressions.Expression<System.Func<TDestination, System.Object>>[])' in the contract but not the implementation.
7172
CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.NullableAttribute' exists on generic param 'TDestination' on member 'AutoMapper.QueryableExtensions.Extensions.ProjectTo<TDestination>(System.Linq.IQueryable, AutoMapper.IConfigurationProvider, System.Object, System.Linq.Expressions.Expression<System.Func<TDestination, System.Object>>[])' in the contract but not the implementation.
72-
Total Issues: 70
73+
Total Issues: 71
Lines changed: 69 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,116 @@
11
using AutoMapper.Internal.Mappers;
22
namespace AutoMapper.Configuration;
33
[EditorBrowsable(EditorBrowsableState.Never)]
4-
public readonly record struct ConfigurationValidator(IGlobalConfigurationExpression Expression)
4+
public class ConfigurationValidator(IGlobalConfiguration config)
55
{
6-
private void Validate(ValidationContext context)
7-
{
8-
foreach (var validator in Expression.Validators)
9-
{
10-
validator(context);
11-
}
12-
}
13-
public void AssertConfigurationExpressionIsValid(IGlobalConfiguration config, TypeMap[] typeMaps)
6+
IGlobalConfigurationExpression Expression => ((MapperConfiguration)config).ConfigurationExpression;
7+
public void AssertConfigurationExpressionIsValid(TypeMap[] typeMaps)
148
{
159
var duplicateTypeMapConfigs = Expression.Profiles.Append((Profile)Expression)
1610
.SelectMany(p => p.TypeMapConfigs, (profile, typeMap) => (profile, typeMap))
1711
.GroupBy(x => x.typeMap.Types)
1812
.Where(g => g.Count() > 1)
19-
.Select(g => (TypePair : g.Key, ProfileNames : g.Select(tmc => tmc.profile.ProfileName).ToArray()))
13+
.Select(g => (TypePair: g.Key, ProfileNames: g.Select(tmc => tmc.profile.ProfileName).ToArray()))
2014
.Select(g => new DuplicateTypeMapConfigurationException.TypeMapConfigErrors(g.TypePair, g.ProfileNames))
2115
.ToArray();
22-
if (duplicateTypeMapConfigs.Any())
16+
if(duplicateTypeMapConfigs.Length != 0)
2317
{
2418
throw new DuplicateTypeMapConfigurationException(duplicateTypeMapConfigs);
2519
}
26-
AssertConfigurationIsValid(config, typeMaps);
20+
AssertConfigurationIsValid(typeMaps);
2721
}
28-
public void AssertConfigurationIsValid(IGlobalConfiguration config, TypeMap[] typeMaps)
22+
public void AssertConfigurationIsValid(TypeMap[] typeMaps)
2923
{
24+
List<Exception> configExceptions = [];
3025
var badTypeMaps =
3126
(from typeMap in typeMaps
32-
where typeMap.ShouldCheckForValid
33-
let unmappedPropertyNames = typeMap.GetUnmappedPropertyNames()
34-
let canConstruct = typeMap.PassesCtorValidation
35-
where unmappedPropertyNames.Length > 0 || !canConstruct
36-
select new AutoMapperConfigurationException.TypeMapConfigErrors(typeMap, unmappedPropertyNames, canConstruct)
37-
).ToArray();
38-
if (badTypeMaps.Length > 0)
27+
where typeMap.ShouldCheckForValid
28+
let unmappedPropertyNames = typeMap.GetUnmappedPropertyNames()
29+
let canConstruct = typeMap.PassesCtorValidation
30+
where unmappedPropertyNames.Length > 0 || !canConstruct
31+
select new AutoMapperConfigurationException.TypeMapConfigErrors(typeMap, unmappedPropertyNames, canConstruct)).ToArray();
32+
if(badTypeMaps.Length > 0)
3933
{
40-
throw new AutoMapperConfigurationException(badTypeMaps);
34+
configExceptions.Add(new AutoMapperConfigurationException(badTypeMaps));
4135
}
4236
HashSet<TypeMap> typeMapsChecked = [];
43-
List<Exception> configExceptions = [];
44-
foreach (var typeMap in typeMaps)
37+
foreach(var typeMap in typeMaps)
4538
{
46-
try
47-
{
48-
DryRunTypeMap(config, typeMapsChecked, typeMap.Types, typeMap, null);
49-
}
50-
catch (Exception e)
51-
{
52-
configExceptions.Add(e);
53-
}
39+
DryRunTypeMap(typeMap.Types, typeMap, null);
5440
}
55-
if (configExceptions.Count > 1)
41+
if(configExceptions.Count > 1)
5642
{
5743
throw new AggregateException(configExceptions);
5844
}
59-
if (configExceptions.Count > 0)
45+
if(configExceptions.Count > 0)
6046
{
6147
throw configExceptions[0];
6248
}
63-
}
64-
private void DryRunTypeMap(IGlobalConfiguration config, HashSet<TypeMap> typeMapsChecked, TypePair types, TypeMap typeMap, MemberMap memberMap)
65-
{
66-
if(typeMap == null)
49+
void DryRunTypeMap(TypePair types, TypeMap typeMap, MemberMap memberMap)
6750
{
68-
if (types.ContainsGenericParameters)
51+
if(typeMap == null)
6952
{
70-
return;
53+
if(types.ContainsGenericParameters)
54+
{
55+
return;
56+
}
57+
typeMap = config.ResolveTypeMap(types.SourceType, types.DestinationType);
7158
}
72-
typeMap = config.ResolveTypeMap(types.SourceType, types.DestinationType);
73-
}
74-
if (typeMap != null)
75-
{
76-
if (typeMapsChecked.Contains(typeMap))
59+
if(typeMap != null)
7760
{
78-
return;
61+
if(typeMapsChecked.Add(typeMap) && Validate(new(types, memberMap, configExceptions, typeMap)) && typeMap.ShouldCheckForValid)
62+
{
63+
CheckPropertyMaps(typeMap);
64+
}
7965
}
80-
typeMapsChecked.Add(typeMap);
81-
Validate(new(types, memberMap, typeMap));
82-
if(!typeMap.ShouldCheckForValid)
66+
else
8367
{
84-
return;
68+
var mapperToUse = config.FindMapper(types);
69+
if(mapperToUse == null)
70+
{
71+
configExceptions.Add(new AutoMapperConfigurationException(memberMap.TypeMap.Types) { MemberMap = memberMap });
72+
return;
73+
}
74+
if(Validate(new(types, memberMap, configExceptions, ObjectMapper: mapperToUse)) && mapperToUse.GetAssociatedTypes(types) is TypePair newTypes &&
75+
newTypes != types)
76+
{
77+
DryRunTypeMap(newTypes, null, memberMap);
78+
}
8579
}
86-
CheckPropertyMaps(config, typeMapsChecked, typeMap);
8780
}
88-
else
81+
void CheckPropertyMaps(TypeMap typeMap)
8982
{
90-
var mapperToUse = config.FindMapper(types);
91-
if (mapperToUse == null)
92-
{
93-
throw new AutoMapperConfigurationException(memberMap.TypeMap.Types) { MemberMap = memberMap };
94-
}
95-
Validate(new(types, memberMap, ObjectMapper: mapperToUse));
96-
if (mapperToUse.GetAssociatedTypes(types) is TypePair newTypes && newTypes != types)
83+
foreach(var memberMap in typeMap.MemberMaps)
9784
{
98-
DryRunTypeMap(config, typeMapsChecked, newTypes, null, memberMap);
85+
if(memberMap.Ignored || (memberMap is PropertyMap && typeMap.ConstructorParameterMatches(memberMap.DestinationName)))
86+
{
87+
continue;
88+
}
89+
var sourceType = memberMap.SourceType;
90+
// when we don't know what the source type is, bail
91+
if(sourceType.IsGenericParameter || sourceType == typeof(object))
92+
{
93+
continue;
94+
}
95+
DryRunTypeMap(new(sourceType, memberMap.DestinationType), null, memberMap);
9996
}
10097
}
101-
}
102-
private void CheckPropertyMaps(IGlobalConfiguration config, HashSet<TypeMap> typeMapsChecked, TypeMap typeMap)
103-
{
104-
foreach (var memberMap in typeMap.MemberMaps)
98+
bool Validate(ValidationContext context)
10599
{
106-
if(memberMap.Ignored || (memberMap is PropertyMap && typeMap.ConstructorParameterMatches(memberMap.DestinationName)))
107-
{
108-
continue;
109-
}
110-
var sourceType = memberMap.SourceType;
111-
// when we don't know what the source type is, bail
112-
if (sourceType.IsGenericParameter || sourceType == typeof(object))
100+
foreach(var validator in Expression.Validators)
113101
{
114-
continue;
102+
try
103+
{
104+
validator(context);
105+
}
106+
catch(Exception e)
107+
{
108+
configExceptions.Add(e);
109+
return false;
110+
}
115111
}
116-
DryRunTypeMap(config, typeMapsChecked, new(sourceType, memberMap.DestinationType), null, memberMap);
112+
return true;
117113
}
118114
}
119115
}
120-
public readonly record struct ValidationContext(TypePair Types, MemberMap MemberMap, TypeMap TypeMap = null, IObjectMapper ObjectMapper = null);
116+
public readonly record struct ValidationContext(TypePair Types, MemberMap MemberMap, List<Exception> Exceptions, TypeMap TypeMap = null, IObjectMapper ObjectMapper = null);

src/AutoMapper/Configuration/MapperConfiguration.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public sealed class MapperConfiguration : IGlobalConfiguration
4343
private readonly LockingConcurrentDictionary<TypePair, TypeMap> _runtimeMaps;
4444
private LazyValue<ProjectionBuilder> _projectionBuilder;
4545
private readonly LockingConcurrentDictionary<MapRequest, Delegate> _executionPlans;
46-
private readonly ConfigurationValidator _validator;
46+
private readonly MapperConfigurationExpression _configurationExpression;
4747
private readonly Features<IRuntimeFeature> _features = new();
4848
private readonly bool _hasOpenMaps;
4949
private readonly HashSet<TypeMap> _typeMapsPath = [];
@@ -58,14 +58,14 @@ public sealed class MapperConfiguration : IGlobalConfiguration
5858
private readonly List<Type> _typesInheritance = [];
5959
public MapperConfiguration(MapperConfigurationExpression configurationExpression)
6060
{
61+
_configurationExpression=configurationExpression;
6162
var configuration = (IGlobalConfigurationExpression)configurationExpression;
6263
if (configuration.MethodMappingEnabled != false)
6364
{
6465
configuration.IncludeSourceExtensionMethods(typeof(Enumerable));
6566
}
6667
_mappers = [..configuration.Mappers];
6768
_executionPlans = new(CompileExecutionPlan);
68-
_validator = new(configuration);
6969
_projectionBuilder = new(CreateProjectionBuilder);
7070
Configuration = new((IProfileConfiguration)configuration);
7171
int typeMapsCount = Configuration.TypeMapsCount;
@@ -155,7 +155,8 @@ static MapperConfigurationExpression Build(Action<IMapperConfigurationExpression
155155
configure(expr);
156156
return expr;
157157
}
158-
public void AssertConfigurationIsValid() => _validator.AssertConfigurationExpressionIsValid(this, [.._configuredMaps.Values]);
158+
public void AssertConfigurationIsValid() => Validator().AssertConfigurationExpressionIsValid([.._configuredMaps.Values]);
159+
ConfigurationValidator Validator() => new(this);
159160
public IMapper CreateMapper() => new Mapper(this);
160161
public IMapper CreateMapper(Func<Type, object> serviceCtor) => new Mapper(this, serviceCtor);
161162
public void CompileMappings()
@@ -218,7 +219,7 @@ LambdaExpression GenerateObjectMapperExpression(in MapRequest mapRequest, IObjec
218219
return Lambda(fullExpression, source, destination, ContextParameter);
219220
}
220221
}
221-
IGlobalConfigurationExpression ConfigurationExpression => _validator.Expression;
222+
internal IGlobalConfigurationExpression ConfigurationExpression => _configurationExpression;
222223
ProjectionBuilder CreateProjectionBuilder() => new(this, [..ConfigurationExpression.ProjectionMappers]);
223224
IProjectionBuilder IGlobalConfiguration.ProjectionBuilder => _projectionBuilder.Value;
224225
Func<Type, object> IGlobalConfiguration.ServiceCtor => ConfigurationExpression.ServiceCtor;
@@ -471,14 +472,14 @@ IObjectMapper FindMapper(TypePair types)
471472
return null;
472473
}
473474
void IGlobalConfiguration.RegisterTypeMap(TypeMap typeMap) => _configuredMaps[typeMap.Types] = typeMap;
474-
void IGlobalConfiguration.AssertConfigurationIsValid(TypeMap typeMap) => _validator.AssertConfigurationIsValid(this, [typeMap]);
475+
void IGlobalConfiguration.AssertConfigurationIsValid(TypeMap typeMap) => Validator().AssertConfigurationIsValid([typeMap]);
475476
void IGlobalConfiguration.AssertConfigurationIsValid(string profileName)
476477
{
477478
if (Array.TrueForAll(Profiles, x => x.Name != profileName))
478479
{
479480
throw new ArgumentOutOfRangeException(nameof(profileName), $"Cannot find any profiles with the name '{profileName}'.");
480481
}
481-
_validator.AssertConfigurationIsValid(this, _configuredMaps.Values.Where(typeMap => typeMap.Profile.Name == profileName).ToArray());
482+
Validator().AssertConfigurationIsValid(_configuredMaps.Values.Where(typeMap => typeMap.Profile.Name == profileName).ToArray());
482483
}
483484
void IGlobalConfiguration.AssertConfigurationIsValid<TProfile>() => this.Internal().AssertConfigurationIsValid(typeof(TProfile).FullName);
484485
void IGlobalConfiguration.RegisterAsMap(TypeMapConfiguration typeMapConfiguration) =>

src/UnitTests/ConfigurationValidation.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
namespace AutoMapper.UnitTests.ConfigurationValidation;
2+
public class When_testing_a_dto_with_mismatched_member_names_and_mismatched_types : AutoMapperSpecBase
3+
{
4+
public class Source
5+
{
6+
public decimal Foo { get; set; }
7+
}
8+
9+
public class Destination
10+
{
11+
public Type Foo { get; set; }
12+
public string Bar { get; set; }
13+
}
214

15+
protected override MapperConfiguration CreateConfiguration() =>
16+
new(cfg => { cfg.CreateMap<Source, Destination>(); });
17+
18+
[Fact]
19+
public void Should_throw_unmapped_member_and_mismatched_type_exceptions()
20+
{
21+
new Action(AssertConfigurationIsValid)
22+
.ShouldThrow<AggregateException>()
23+
.ShouldSatisfyAllConditions(
24+
aex => aex.InnerExceptions.ShouldBeOfLength(2),
25+
aex => aex.InnerExceptions[0]
26+
.ShouldBeOfType<AutoMapperConfigurationException>()
27+
.ShouldSatisfyAllConditions(
28+
ex => ex.Errors.ShouldBeOfLength(1),
29+
ex => ex.Errors[0].UnmappedPropertyNames.ShouldContain("Bar")),
30+
aex => aex.InnerExceptions[1]
31+
.ShouldBeOfType<AutoMapperConfigurationException>()
32+
.ShouldSatisfyAllConditions(
33+
ex => ex.MemberMap.ShouldNotBeNull(),
34+
ex => ex.MemberMap.DestinationName.ShouldBe("Foo"))
35+
);
36+
}
37+
}
338
public class ConstructorMappingValidation : NonValidatingSpecBase
439
{
540
public class Destination

0 commit comments

Comments
 (0)