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
72 changes: 72 additions & 0 deletions AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1161,6 +1161,29 @@ public void ShouldAllowIdAndIdentifierConfiguration()
}
}

// See https://github.com/agileobjects/AgileMapper/issues/146
[Fact]
public void ShouldApplyAConfiguredSourceInterfaceMember()
{
using (var mapper = Mapper.CreateNew())
{
mapper.WhenMapping
.From<Issue146.Source.Container>().To<Issue146.Target.Cont>()
.Map(ctx => ctx.Source.Empty).To(tgt => tgt.Info);

var source = new Issue146.Source.Container("12321") { Name = "input" };
var result = mapper.Map(source).ToANew<Issue146.Target.Cont>();

result.ShouldNotBeNull();
result.Name.ShouldBe("input");
result.Info.ShouldNotBeNull();
result.Info.Id.ShouldBe("12321");

// Source has a .Value member, but we don't runtime-type interfaces
result.Info.Value.ShouldBeNull();
}
}

// See https://github.com/agileobjects/AgileMapper/issues/64
[Fact]
public void ShouldApplyAConfiguredRootSource()
Expand Down Expand Up @@ -1794,5 +1817,54 @@ public class DataTarget
public OtherDataTarget oth;
}
}

internal static class Issue146
{
public static class Source
{
public interface IData
{
string Id { get; set; }
}

public interface IEmpty : IData { }

public class Data : IEmpty
{
public string Id { get; set; }

public string Value => "Data.Value!";
}

public class Container
{
public Container(string infoId)
{
Empty = new Data { Id = infoId };
}

public string Name { get; set; }

public IEmpty Empty { get; }
}
}

public static class Target
{
public class Data
{
public string Id { get; set; }

public string Value { get; set; }
}

public class Cont
{
public Data Info { get; set; }

public string Name { get; set; }
}
}
}
}
}
62 changes: 62 additions & 0 deletions AgileMapper.UnitTests/WhenViewingMappingPlans.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,17 @@ public void ShouldNotAttemptUnnecessaryObjectCreationCallbacks()
}
}

// See https://github.com/agileobjects/AgileMapper/issues/146
[Fact]
public void ShouldUseBaseInterfaceTypeSourceMembersWithoutRuntimeTyping()
{
string plan = Mapper
.GetPlanFor<Issue146.Source.Container>()
.ToANew<Issue146.Target.Cont>();

plan.ShouldContain("data.Id = cToCData.Source.Info.Id;");
}

[Fact]
public void ShouldShowEnumMismatches()
{
Expand Down Expand Up @@ -408,5 +419,56 @@ public void ShouldShowAllCachedMappingPlans()
mapper.RootMapperCountShouldBe(5);
}
}

#region Helper Classes

internal static class Issue146
{
public static class Source
{
public interface IData
{
string Id { get; set; }
}

public interface IEmpty : IData { }

public class Data : IEmpty
{
public string Id { get; set; }
}

public class Container
{
public Container(string infoId)
{
Info = new Data { Id = infoId };
}

public string Name { get; set; }

public IEmpty Info { get; }
}
}

public static class Target
{
public class Data
{
public string Id { get; set; }

public string Value { get; set; }
}

public class Cont
{
public Data Info { get; set; }

public string Name { get; set; }
}
}
}

#endregion
}
}
10 changes: 4 additions & 6 deletions AgileMapper/DataSources/Finders/DataSourceFindContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,11 @@ private IList<IConfiguredDataSource> GetConfiguredDataSources(IMemberMapperData
public IDataSource GetFallbackDataSource()
=> ChildMappingData.RuleSet.FallbackDataSourceFactory.Create(MapperData);

public IDataSource GetFinalDataSource(IDataSource foundDataSource, IChildMemberMappingData mappingData = null)
public IDataSource GetFinalDataSource(IDataSource foundDataSource)
=> GetFinalDataSource(foundDataSource, ChildMappingData);

public IDataSource GetFinalDataSource(IDataSource foundDataSource, IChildMemberMappingData mappingData)
{
if (mappingData == null)
{
mappingData = ChildMappingData;
}

var childTargetMember = mappingData.MapperData.TargetMember;

if (UseComplexTypeDataSource(foundDataSource, childTargetMember))
Expand Down
35 changes: 27 additions & 8 deletions AgileMapper/Members/MemberCache.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace AgileObjects.AgileMapper.Members
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -33,7 +34,31 @@ public IList<Member> GetSourceMembers(Type sourceType)
var properties = GetProperties(key.Type, OnlyGettable);
var methods = GetMethods(key.Type, OnlyRelevantCallable, Member.GetMethod);

return GetMembers(fields, properties, methods);
var members = new[] { fields, properties, methods };

if (!key.Type.IsInterface())
{
return GetMembers(members);
}

var interfaceTypes = key.Type.GetAllInterfaces();

if (interfaceTypes.Length == 0)
{
return GetMembers(members);
}

var interfaceCount = interfaceTypes.Length;

var allMembers = new IEnumerable<Member>[3 + interfaceCount];
allMembers.CopyFrom(members);

for (var i = 0; i < interfaceCount; ++i)
{
allMembers[i + 3] = GetSourceMembers(interfaceTypes[i]);
}

return GetMembers(allMembers);
});
}

Expand Down Expand Up @@ -132,12 +157,6 @@ private static bool OnlyCallableSetters(MethodInfo method)
#endregion

private static IList<Member> GetMembers(params IEnumerable<Member>[] members)
{
var allMembers = members
.SelectMany(m => m)
.ToArray();

return allMembers;
}
=> members.SelectMany(m => m).ToArray();
}
}