From 45619e96ea17312f62b51b901ac1b2ed5f665eb3 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 14 Sep 2018 19:26:39 +0100 Subject: [PATCH 1/2] Erroring if configured value has enumerable - non-enumerable mismatch --- .../WhenConfiguringDataSourcesIncorrectly.cs | 41 +++++++++++++- .../CustomDataSourceTargetMemberSpecifier.cs | 53 ++++++++++++++++--- 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs index 12f6ff9b7..8603142dc 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests.Configuration { using System; + using System.Collections.Generic; using AgileMapper.Configuration; using Common; using TestClasses; @@ -351,9 +352,45 @@ public void ShouldErrorIfRootTargetSimpleTypeMemberDataSourceConfigured() }); configurationException.Message.ShouldContain("PublicProperty.Value"); + configurationException.Message.ShouldContain("'int' cannot be mapped to target type 'PublicField'"); + } - configurationException.Message.ShouldContain( - "'int' cannot be mapped to target type 'PublicField'"); + [Fact] + public void ShouldErrorIfRootEnumerableTargetNonEnumerableTypeMemberDataSourceConfigured() + { + var configurationException = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .To>() + .Map(ctx => ctx.Source.Value) + .ToTarget(); + } + }); + + configurationException.Message.ShouldContain("Non-enumerable PublicProperty
.Value"); + configurationException.Message.ShouldContain("cannot be mapped to enumerable target type 'List
'"); + } + + [Fact] + public void ShouldErrorIfRootNonEnumerableTargetEnumerableTypeMemberDataSourceConfigured() + { + var configurationException = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>>() + .To>() + .Map(ctx => ctx.Source.Value) + .ToTarget(); + } + }); + + configurationException.Message.ShouldContain("Enumerable PublicField>.Value"); + configurationException.Message.ShouldContain("cannot be mapped to non-enumerable target type 'PublicProperty'"); } } } \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index af91ad55b..1c48e26de 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -283,6 +283,7 @@ private ConfiguredDataSourceFactory CreateForCtorParam(ParameterInfo par public IMappingConfigContinuation ToTarget() { ThrowIfSimpleSource(typeof(TTarget)); + ThrowIfEnumerableSourceAndTargetMismatch(typeof(TTarget)); return RegisterDataSource(() => new ConfiguredDataSourceFactory( _configInfo, @@ -299,27 +300,65 @@ private void ThrowIfSimpleSource(Type targetMemberType) return; } - string sourceValue; + var sourceValue = GetSourceValue(customValue); - if (customValue.NodeType == ExpressionType.MemberAccess) + throw new MappingConfigurationException(string.Format( + CultureInfo.InvariantCulture, + "{0}'{1}' cannot be mapped to target type '{2}'", + sourceValue, + customValue.Type.GetFriendlyName(), + targetMemberType.GetFriendlyName())); + } + + private void ThrowIfEnumerableSourceAndTargetMismatch(Type targetMemberType) + { + var customValue = _customValueLambda.Body; + + if ((targetMemberType.IsDictionary() || customValue.Type.IsDictionary()) || + (targetMemberType.IsEnumerable() == customValue.Type.IsEnumerable())) { - var rootSourceMember = _configInfo.MapperContext.QualifiedMemberFactory.RootSource(); - var sourceMember = customValue.ToSourceMember(_configInfo.MapperContext); - sourceValue = sourceMember.GetFriendlyMemberPath(rootSourceMember) + " of type "; + return; + } + + string sourceEnumerableState, targetEnumerableState; + + if (targetMemberType.IsEnumerable()) + { + sourceEnumerableState = "Non-enumerable"; + targetEnumerableState = "enumerable"; } else { - sourceValue = "Source type "; + sourceEnumerableState = "Enumerable"; + targetEnumerableState = "non-enumerable"; } + var sourceValue = GetSourceValue(customValue); + throw new MappingConfigurationException(string.Format( CultureInfo.InvariantCulture, - "{0}'{1}' cannot be mapped to target type '{2}'", + "{0} {1}'{2}' cannot be mapped to {3} target type '{4}'", + sourceEnumerableState, sourceValue, customValue.Type.GetFriendlyName(), + targetEnumerableState, targetMemberType.GetFriendlyName())); } + private string GetSourceValue(Expression customValue) + { + if (customValue.NodeType != ExpressionType.MemberAccess) + { + return "Source type "; + } + + var rootSourceMember = _configInfo.MapperContext.QualifiedMemberFactory.RootSource(); + var sourceMember = customValue.ToSourceMember(_configInfo.MapperContext); + var sourceValue = sourceMember.GetFriendlyMemberPath(rootSourceMember) + " of type "; + + return sourceValue; + } + private MappingConfigContinuation RegisterDataSource( Func factoryFactory) { From 44c8d6fabfbdd1913bca0e052d6524e49cdcbe60 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 14 Sep 2018 19:38:01 +0100 Subject: [PATCH 2/2] Clearer errors if invalid target members are specified in configuration --- .../WhenConfiguringDataSourcesIncorrectly.cs | 37 +++++++++++++++++++ .../Configuration/UserConfiguredItemBase.cs | 12 +++++- AgileMapper/Members/MemberExtensions.cs | 2 +- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs index 8603142dc..922f0e2fa 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Linq; using AgileMapper.Configuration; using Common; using TestClasses; @@ -392,5 +393,41 @@ public void ShouldErrorIfRootNonEnumerableTargetEnumerableTypeMemberDataSourceCo configurationException.Message.ShouldContain("Enumerable PublicField>.Value"); configurationException.Message.ShouldContain("cannot be mapped to non-enumerable target type 'PublicProperty'"); } + + [Fact] + public void ShouldErrorIfConstantSpecifiedForTargetMember() + { + var configurationException = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .To>() + .Map(ctx => "Number!") + .To(ppc => "Number?"); + } + }); + + configurationException.Message.ShouldContain("Unable to determine target member from '\"Number?\"'"); + } + + [Fact] + public void ShouldErrorIfProjectionSpecifiedForTargetMember() + { + var configurationException = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>>() + .To>>() + .Map(ctx => new[] { "One", "Two", "Three" }) + .To(ppc => ppc.Value.Select(c => c.Name)); + } + }); + + configurationException.Message.ShouldContain("not writeable"); + } } } \ No newline at end of file diff --git a/AgileMapper/Configuration/UserConfiguredItemBase.cs b/AgileMapper/Configuration/UserConfiguredItemBase.cs index 394c22bcc..bec60761d 100644 --- a/AgileMapper/Configuration/UserConfiguredItemBase.cs +++ b/AgileMapper/Configuration/UserConfiguredItemBase.cs @@ -26,7 +26,17 @@ protected UserConfiguredItemBase(MappingConfigInfo configInfo, LambdaExpression private static QualifiedMember GetTargetMemberOrThrow(LambdaExpression lambda, MappingConfigInfo configInfo) { - var targetMember = lambda.ToTargetMember(configInfo.MapperContext); + QualifiedMember targetMember; + + try + { + targetMember = lambda.ToTargetMember(configInfo.MapperContext); + } + catch (NotSupportedException) + { + throw new MappingConfigurationException( + $"Unable to determine target member from '{lambda.Body.ToReadableString()}'"); + } if (targetMember == null) { diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index d0ec83143..670ad61f7 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -282,7 +282,7 @@ public static QualifiedMember ToTargetMember(this LambdaExpression memberAccess, mapperContext); } - internal static QualifiedMember CreateMember( + private static QualifiedMember CreateMember( Expression memberAccessExpression, Func rootMemberFactory, Func> membersFactory,