Skip to content
Merged
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
37 changes: 37 additions & 0 deletions AgileMapper.UnitTests/Configuration/WhenConfiguringEnumMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,43 @@ public void ShouldPairEnumMembers()
}
}

// See https://github.com/agileobjects/AgileMapper/issues/138
[Fact]
public void ShouldApplyEnumPairsToRootMappings()
{
using (var mapper = Mapper.CreateNew())
{
mapper.WhenMapping
.PairEnum(PaymentTypeUk.Cheque).With(PaymentTypeUs.Check);

var ukChequeResult = mapper.Map(PaymentTypeUk.Cheque).ToANew<PaymentTypeUs>();

ukChequeResult.ShouldBe(PaymentTypeUs.Check);

var usCheckResult = mapper.Map(PaymentTypeUs.Check).ToANew<PaymentTypeUk>();

usCheckResult.ShouldBe(PaymentTypeUk.Cheque);
}
}

[Fact]
public void ShouldApplyEnumPairsToNullableRootMappings()
{
using (var mapper = Mapper.CreateNew())
{
mapper.WhenMapping
.PairEnum(PaymentTypeUk.Cheque).With(PaymentTypeUs.Check);

var ukChequeResult = mapper.Map(PaymentTypeUk.Cheque).ToANew<PaymentTypeUs?>();

ukChequeResult.ShouldBe(PaymentTypeUs.Check);

var usCheckResult = mapper.Map((PaymentTypeUs)1234).ToANew<PaymentTypeUk?>();

usCheckResult.ShouldBeNull();
}
}

[Fact]
public void ShouldAllowMultipleSourceToSingleTargetPairing()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,28 @@ public void ShouldExecuteAPostMappingCallbackForASpecifiedTargetTypeConditionall
}
}

[Fact]
public void ShouldExecuteGlobalPreAndPostMappingCallbacksInARootNullableEnumMapping()
{
using (var mapper = Mapper.CreateNew())
{
var counter = 0;

mapper.Before
.MappingBegins
.Call(ctx => ++counter)
.And
.After
.MappingEnds
.Call(ctx => ++counter);

var result = mapper.Map("Mrs").ToANew<Title?>();

result.ShouldBe(Title.Mrs);
counter.ShouldBe(2);
}
}

[Fact]
public void ShouldRestrictAPostMappingCallbackByTargetType()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ public void ShouldMapAMatchingStringOnToAnEnumCaseInsensitively()
result.Value.ShouldBe(Miss);
}

[Fact]
public void ShouldMapAMatchingStringOnToARootEnum()
{
var result = Mapper.Map(Mrs.ToString()).ToANew<Title>();

result.ShouldBe(Mrs);
}

[Fact]
public void ShouldMapAMatchingNumericStringOverAnEnum()
{
Expand Down Expand Up @@ -176,6 +184,14 @@ public void ShouldMapAnEnumToAnEnum()
result.Value.ShouldBe(Mrs);
}

[Fact]
public void ShouldMapAnEnumToARootEnum()
{
var result = Mapper.Map(TitleShortlist.Mrs).ToANew<Title>();

result.ShouldBe(Mrs);
}

[Fact]
public void ShouldMapANonMatchingEnumToANullableEnum()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes
using Extensions.Internal;
using Members;
using NetStandardPolyfills;
using ReadableExpressions;
using ReadableExpressions.Extensions;

internal class ComplexTypeMappingExpressionFactory : MappingExpressionFactoryBase
Expand All @@ -33,31 +32,28 @@ private ComplexTypeMappingExpressionFactory()

public override bool IsFor(IObjectMappingData mappingData) => true;

protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out Expression nullMappingBlock)
protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out string reason)
{
if (mappingData.MapperData.TargetCouldBePopulated())
{
// If a target complex type is readonly or unconstructable
// we still try to map to it using an existing non-null value:
return base.TargetCannotBeMapped(mappingData, out nullMappingBlock);
return base.TargetCannotBeMapped(mappingData, out reason);
}

if (mappingData.IsTargetConstructable())
{
return base.TargetCannotBeMapped(mappingData, out nullMappingBlock);
return base.TargetCannotBeMapped(mappingData, out reason);
}

var targetType = mappingData.MapperData.TargetType;

if (targetType.IsAbstract() && DerivedTypesExistForTarget(mappingData))
{
return base.TargetCannotBeMapped(mappingData, out nullMappingBlock);
return base.TargetCannotBeMapped(mappingData, out reason);
}

nullMappingBlock = Expression.Block(
ReadableExpression.Comment("Cannot construct an instance of " + targetType.GetFriendlyName()),
targetType.ToDefaultExpression());

reason = "Cannot construct an instance of " + targetType.GetFriendlyName();
return true;
}

Expand Down
25 changes: 12 additions & 13 deletions AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ namespace AgileObjects.AgileMapper.ObjectPopulation
using System;
using System.Collections.Generic;
using System.Linq;
#if NET35
using Microsoft.Scripting.Ast;
#else
using System.Linq.Expressions;
#endif
using System.Reflection;
using ComplexTypes;
using DataSources;
Expand All @@ -13,13 +18,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation
using Members.Dictionaries;
using Members.Population;
using NetStandardPolyfills;
using ReadableExpressions;
using ReadableExpressions.Extensions;
#if NET35
using Microsoft.Scripting.Ast;
#else
using System.Linq.Expressions;
#endif

internal class DictionaryMappingExpressionFactory : MappingExpressionFactoryBase
{
Expand Down Expand Up @@ -255,27 +254,27 @@ public override bool IsFor(IObjectMappingData mappingData)
return dictionaryMember.HasObjectEntries && !mappingData.IsStandalone();
}

protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out Expression nullMappingBlock)
protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out string reason)
{
if (mappingData.MappingTypes.SourceType.IsDictionary())
{
return base.TargetCannotBeMapped(mappingData, out nullMappingBlock);
return base.TargetCannotBeMapped(mappingData, out reason);
}

var targetMember = (DictionaryTargetMember)mappingData.MapperData.TargetMember;

if ((targetMember.KeyType == typeof(string)) || (targetMember.KeyType == typeof(object)))
{
return base.TargetCannotBeMapped(mappingData, out nullMappingBlock);
return base.TargetCannotBeMapped(mappingData, out reason);
}

nullMappingBlock = Expression.Block(
ReadableExpression.Comment("Only string- or object-keyed Dictionaries are supported"),
mappingData.MapperData.GetFallbackCollectionValue());

reason = "Only string- or object-keyed Dictionaries are supported";
return true;
}

protected override Expression GetNullMappingFallbackValue(IMemberMapperData mapperData)
=> mapperData.GetFallbackCollectionValue();

protected override IEnumerable<Expression> GetObjectPopulation(MappingCreationContext context)
{
if (!context.MapperData.TargetMember.IsDictionary)
Expand Down
48 changes: 48 additions & 0 deletions AgileMapper/ObjectPopulation/EnumMappingExpressionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
namespace AgileObjects.AgileMapper.ObjectPopulation
{
using System.Collections.Generic;
using System.Globalization;
using Extensions.Internal;
#if NET35
using Microsoft.Scripting.Ast;
#else
using System.Linq.Expressions;
#endif
using Members;
using NetStandardPolyfills;
using ReadableExpressions.Extensions;

internal class EnumMappingExpressionFactory : MappingExpressionFactoryBase
{
public static readonly EnumMappingExpressionFactory Instance = new EnumMappingExpressionFactory();

public override bool IsFor(IObjectMappingData mappingData)
=> mappingData.MapperData.TargetType.GetNonNullableType().IsEnum();

protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out string reason)
{
var mapperData = mappingData.MapperData;

if (mapperData.CanConvert(mapperData.SourceType, mapperData.TargetType))
{
return base.TargetCannotBeMapped(mappingData, out reason);
}

reason = string.Format(
CultureInfo.InvariantCulture,
"Unable to convert source Type '{0}' to target enum Type '{1}'",
mapperData.SourceType.GetFriendlyName(),
mapperData.TargetType.GetFriendlyName());

return true;
}

protected override IEnumerable<Expression> GetObjectPopulation(MappingCreationContext context)
{
var mapperData = context.MapperData;
var enumMapping = mapperData.GetValueConversion(mapperData.SourceObject, mapperData.TargetType);

yield return context.MapperData.LocalVariable.AssignTo(enumMapping);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables
using System.Collections.Generic;
using Extensions.Internal;
using Members;
using ReadableExpressions;
#if NET35
using Microsoft.Scripting.Ast;
#else
Expand All @@ -17,36 +16,36 @@ internal class EnumerableMappingExpressionFactory : MappingExpressionFactoryBase
public override bool IsFor(IObjectMappingData mappingData)
=> mappingData.MapperData.TargetMember.IsEnumerable;

protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out Expression nullMappingBlock)
protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out string reason)
{
var mapperData = mappingData.MapperData;

if (HasCompatibleSourceMember(mapperData))
{
return base.TargetCannotBeMapped(mappingData, out nullMappingBlock);
return base.TargetCannotBeMapped(mappingData, out reason);
}

if (HasConfiguredToTargetDataSources(mapperData, out var configuredRootDataSources) &&
configuredRootDataSources.Any(ds => ds.SourceMember.IsEnumerable))
{
return base.TargetCannotBeMapped(mappingData, out nullMappingBlock);
return base.TargetCannotBeMapped(mappingData, out reason);
}

nullMappingBlock = Expression.Block(
ReadableExpression.Comment("No source enumerable available"),
mapperData.GetFallbackCollectionValue());

reason = "No source enumerable available";
return true;
}

private static bool HasCompatibleSourceMember(IMemberMapperData mapperData)
{
return mapperData.SourceMember.IsEnumerable &&
mapperData.CanConvert(
mapperData.SourceMember.GetElementMember().Type,
mapperData.SourceMember.GetElementMember().Type,
mapperData.TargetMember.GetElementMember().Type);
}

protected override Expression GetNullMappingFallbackValue(IMemberMapperData mapperData)
=> mapperData.GetFallbackCollectionValue();

protected override IEnumerable<Expression> GetObjectPopulation(MappingCreationContext context)
{
if (!HasCompatibleSourceMember(context.MapperData))
Expand Down
5 changes: 4 additions & 1 deletion AgileMapper/ObjectPopulation/MapperDataContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ namespace AgileObjects.AgileMapper.ObjectPopulation
using System;
using Extensions.Internal;
using Members;
using NetStandardPolyfills;
using ReadableExpressions.Extensions;

internal class MapperDataContext
{
Expand Down Expand Up @@ -41,7 +43,8 @@ private MapperDataContext(

private static bool ShouldUseLocalVariable(IBasicMapperData mapperData)
{
if (mapperData.TargetMember.IsSimple)
if (mapperData.TargetMember.IsSimple &&
!mapperData.TargetType.GetNonNullableType().IsEnum())
{
return false;
}
Expand Down
Loading