Skip to content

Commit 9ec1f97

Browse files
authored
Extra configured data source checks (#100)
* Erroring if configured value has enumerable - non-enumerable mismatch * Clearer errors if invalid target members are specified in configuration
1 parent d3bfe11 commit 9ec1f97

File tree

4 files changed

+134
-11
lines changed

4 files changed

+134
-11
lines changed

AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
namespace AgileObjects.AgileMapper.UnitTests.Configuration
22
{
33
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
46
using AgileMapper.Configuration;
57
using Common;
68
using TestClasses;
@@ -351,9 +353,81 @@ public void ShouldErrorIfRootTargetSimpleTypeMemberDataSourceConfigured()
351353
});
352354

353355
configurationException.Message.ShouldContain("PublicProperty<int>.Value");
356+
configurationException.Message.ShouldContain("'int' cannot be mapped to target type 'PublicField<long>'");
357+
}
354358

355-
configurationException.Message.ShouldContain(
356-
"'int' cannot be mapped to target type 'PublicField<long>'");
359+
[Fact]
360+
public void ShouldErrorIfRootEnumerableTargetNonEnumerableTypeMemberDataSourceConfigured()
361+
{
362+
var configurationException = Should.Throw<MappingConfigurationException>(() =>
363+
{
364+
using (var mapper = Mapper.CreateNew())
365+
{
366+
mapper.WhenMapping
367+
.From<PublicProperty<Address>>()
368+
.To<List<Address>>()
369+
.Map(ctx => ctx.Source.Value)
370+
.ToTarget();
371+
}
372+
});
373+
374+
configurationException.Message.ShouldContain("Non-enumerable PublicProperty<Address>.Value");
375+
configurationException.Message.ShouldContain("cannot be mapped to enumerable target type 'List<Address>'");
376+
}
377+
378+
[Fact]
379+
public void ShouldErrorIfRootNonEnumerableTargetEnumerableTypeMemberDataSourceConfigured()
380+
{
381+
var configurationException = Should.Throw<MappingConfigurationException>(() =>
382+
{
383+
using (var mapper = Mapper.CreateNew())
384+
{
385+
mapper.WhenMapping
386+
.From<PublicField<List<Customer>>>()
387+
.To<PublicProperty<Customer>>()
388+
.Map(ctx => ctx.Source.Value)
389+
.ToTarget();
390+
}
391+
});
392+
393+
configurationException.Message.ShouldContain("Enumerable PublicField<List<Customer>>.Value");
394+
configurationException.Message.ShouldContain("cannot be mapped to non-enumerable target type 'PublicProperty<Customer>'");
395+
}
396+
397+
[Fact]
398+
public void ShouldErrorIfConstantSpecifiedForTargetMember()
399+
{
400+
var configurationException = Should.Throw<MappingConfigurationException>(() =>
401+
{
402+
using (var mapper = Mapper.CreateNew())
403+
{
404+
mapper.WhenMapping
405+
.From<PublicField<Customer>>()
406+
.To<PublicProperty<Customer>>()
407+
.Map(ctx => "Number!")
408+
.To(ppc => "Number?");
409+
}
410+
});
411+
412+
configurationException.Message.ShouldContain("Unable to determine target member from '\"Number?\"'");
413+
}
414+
415+
[Fact]
416+
public void ShouldErrorIfProjectionSpecifiedForTargetMember()
417+
{
418+
var configurationException = Should.Throw<MappingConfigurationException>(() =>
419+
{
420+
using (var mapper = Mapper.CreateNew())
421+
{
422+
mapper.WhenMapping
423+
.From<PublicField<IEnumerable<Customer>>>()
424+
.To<PublicProperty<IEnumerable<Customer>>>()
425+
.Map(ctx => new[] { "One", "Two", "Three" })
426+
.To(ppc => ppc.Value.Select(c => c.Name));
427+
}
428+
});
429+
430+
configurationException.Message.ShouldContain("not writeable");
357431
}
358432
}
359433
}

AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ private ConfiguredDataSourceFactory CreateForCtorParam<TParam>(ParameterInfo par
283283
public IMappingConfigContinuation<TSource, TTarget> ToTarget()
284284
{
285285
ThrowIfSimpleSource(typeof(TTarget));
286+
ThrowIfEnumerableSourceAndTargetMismatch(typeof(TTarget));
286287

287288
return RegisterDataSource<TTarget>(() => new ConfiguredDataSourceFactory(
288289
_configInfo,
@@ -299,27 +300,65 @@ private void ThrowIfSimpleSource(Type targetMemberType)
299300
return;
300301
}
301302

302-
string sourceValue;
303+
var sourceValue = GetSourceValue(customValue);
303304

304-
if (customValue.NodeType == ExpressionType.MemberAccess)
305+
throw new MappingConfigurationException(string.Format(
306+
CultureInfo.InvariantCulture,
307+
"{0}'{1}' cannot be mapped to target type '{2}'",
308+
sourceValue,
309+
customValue.Type.GetFriendlyName(),
310+
targetMemberType.GetFriendlyName()));
311+
}
312+
313+
private void ThrowIfEnumerableSourceAndTargetMismatch(Type targetMemberType)
314+
{
315+
var customValue = _customValueLambda.Body;
316+
317+
if ((targetMemberType.IsDictionary() || customValue.Type.IsDictionary()) ||
318+
(targetMemberType.IsEnumerable() == customValue.Type.IsEnumerable()))
305319
{
306-
var rootSourceMember = _configInfo.MapperContext.QualifiedMemberFactory.RootSource<TSource, TTarget>();
307-
var sourceMember = customValue.ToSourceMember(_configInfo.MapperContext);
308-
sourceValue = sourceMember.GetFriendlyMemberPath(rootSourceMember) + " of type ";
320+
return;
321+
}
322+
323+
string sourceEnumerableState, targetEnumerableState;
324+
325+
if (targetMemberType.IsEnumerable())
326+
{
327+
sourceEnumerableState = "Non-enumerable";
328+
targetEnumerableState = "enumerable";
309329
}
310330
else
311331
{
312-
sourceValue = "Source type ";
332+
sourceEnumerableState = "Enumerable";
333+
targetEnumerableState = "non-enumerable";
313334
}
314335

336+
var sourceValue = GetSourceValue(customValue);
337+
315338
throw new MappingConfigurationException(string.Format(
316339
CultureInfo.InvariantCulture,
317-
"{0}'{1}' cannot be mapped to target type '{2}'",
340+
"{0} {1}'{2}' cannot be mapped to {3} target type '{4}'",
341+
sourceEnumerableState,
318342
sourceValue,
319343
customValue.Type.GetFriendlyName(),
344+
targetEnumerableState,
320345
targetMemberType.GetFriendlyName()));
321346
}
322347

348+
private string GetSourceValue(Expression customValue)
349+
{
350+
if (customValue.NodeType != ExpressionType.MemberAccess)
351+
{
352+
return "Source type ";
353+
}
354+
355+
var rootSourceMember = _configInfo.MapperContext.QualifiedMemberFactory.RootSource<TSource, TTarget>();
356+
var sourceMember = customValue.ToSourceMember(_configInfo.MapperContext);
357+
var sourceValue = sourceMember.GetFriendlyMemberPath(rootSourceMember) + " of type ";
358+
359+
return sourceValue;
360+
}
361+
323362
private MappingConfigContinuation<TSource, TTarget> RegisterDataSource<TTargetValue>(
324363
Func<ConfiguredDataSourceFactory> factoryFactory)
325364
{

AgileMapper/Configuration/UserConfiguredItemBase.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,17 @@ protected UserConfiguredItemBase(MappingConfigInfo configInfo, LambdaExpression
2626

2727
private static QualifiedMember GetTargetMemberOrThrow(LambdaExpression lambda, MappingConfigInfo configInfo)
2828
{
29-
var targetMember = lambda.ToTargetMember(configInfo.MapperContext);
29+
QualifiedMember targetMember;
30+
31+
try
32+
{
33+
targetMember = lambda.ToTargetMember(configInfo.MapperContext);
34+
}
35+
catch (NotSupportedException)
36+
{
37+
throw new MappingConfigurationException(
38+
$"Unable to determine target member from '{lambda.Body.ToReadableString()}'");
39+
}
3040

3141
if (targetMember == null)
3242
{

AgileMapper/Members/MemberExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ public static QualifiedMember ToTargetMember(this LambdaExpression memberAccess,
282282
mapperContext);
283283
}
284284

285-
internal static QualifiedMember CreateMember(
285+
private static QualifiedMember CreateMember(
286286
Expression memberAccessExpression,
287287
Func<Type, Member> rootMemberFactory,
288288
Func<Type, IList<Member>> membersFactory,

0 commit comments

Comments
 (0)