Skip to content

Commit

Permalink
feat: case-insensitive MapProperty configurations matching for constr…
Browse files Browse the repository at this point in the history
…uctor parameters (#957)
  • Loading branch information
trejjam authored Dec 6, 2023
1 parent cec43ad commit b9db799
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,11 @@ public interface INewInstanceBuilderContext<out T> : IMembersBuilderContext<T>
void AddConstructorParameterMapping(ConstructorParameterMapping mapping);

void AddInitMemberMapping(MemberAssignmentMapping mapping);

/// <summary>
/// Maps case insensitive target root member names to their real case sensitive names.
/// For example id => Id. The real name can then be used as key for <see cref="IMembersBuilderContext{T}.MemberConfigsByRootTargetName"/>.
/// This allows resolving case insensitive configuration member names (eg. when mapping to constructor parameters).
/// </summary>
IReadOnlyDictionary<string, string> RootTargetNameCasingMapping { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;
/// An implementation of <see cref="INewInstanceBuilderContext{T}"/>.
/// </summary>
/// <typeparam name="T">The type of the mapping.</typeparam>
public class NewInstanceBuilderContext<T>(MappingBuilderContext builderContext, T mapping)
: MembersMappingBuilderContext<T>(builderContext, mapping),
INewInstanceBuilderContext<T>
public class NewInstanceBuilderContext<T> : MembersMappingBuilderContext<T>, INewInstanceBuilderContext<T>
where T : INewInstanceObjectMemberMapping
{
public IReadOnlyDictionary<string, string> RootTargetNameCasingMapping { get; }

public NewInstanceBuilderContext(MappingBuilderContext builderContext, T mapping)
: base(builderContext, mapping)
{
RootTargetNameCasingMapping = MemberConfigsByRootTargetName.ToDictionary(x => x.Key, x => x.Key, StringComparer.OrdinalIgnoreCase);
}

public void AddInitMemberMapping(MemberAssignmentMapping mapping)
{
SetSourceMemberMapped(mapping.SourcePath);
Expand All @@ -20,7 +26,8 @@ public void AddInitMemberMapping(MemberAssignmentMapping mapping)

public void AddConstructorParameterMapping(ConstructorParameterMapping mapping)
{
MemberConfigsByRootTargetName.Remove(mapping.Parameter.Name);
var paramName = RootTargetNameCasingMapping.GetValueOrDefault(mapping.Parameter.Name, defaultValue: mapping.Parameter.Name);
MemberConfigsByRootTargetName.Remove(paramName);
SetSourceMemberMapped(mapping.DelegateMapping.SourcePath);
Mapping.AddConstructorParameterMapping(mapping);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ namespace Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;
/// which supports containers (<seealso cref="MembersContainerBuilderContext{T}"/>).
/// </summary>
/// <typeparam name="T"></typeparam>
public class NewInstanceContainerBuilderContext<T>(MappingBuilderContext builderContext, T mapping)
: MembersContainerBuilderContext<T>(builderContext, mapping),
INewInstanceBuilderContext<T>
public class NewInstanceContainerBuilderContext<T> : MembersContainerBuilderContext<T>, INewInstanceBuilderContext<T>
where T : INewInstanceObjectMemberMapping, IMemberAssignmentTypeMapping
{
public IReadOnlyDictionary<string, string> RootTargetNameCasingMapping { get; }

public NewInstanceContainerBuilderContext(MappingBuilderContext builderContext, T mapping)
: base(builderContext, mapping)
{
RootTargetNameCasingMapping = MemberConfigsByRootTargetName.ToDictionary(x => x.Key, x => x.Key, StringComparer.OrdinalIgnoreCase);
}

public void AddInitMemberMapping(MemberAssignmentMapping mapping)
{
SetSourceMemberMapped(mapping.SourcePath);
Expand All @@ -21,7 +27,8 @@ public void AddInitMemberMapping(MemberAssignmentMapping mapping)

public void AddConstructorParameterMapping(ConstructorParameterMapping mapping)
{
MemberConfigsByRootTargetName.Remove(mapping.Parameter.Name);
var paramName = RootTargetNameCasingMapping.GetValueOrDefault(mapping.Parameter.Name, defaultValue: mapping.Parameter.Name);
MemberConfigsByRootTargetName.Remove(paramName);
SetSourceMemberMapped(mapping.DelegateMapping.SourcePath);
Mapping.AddConstructorParameterMapping(mapping);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,10 @@ out PropertyMappingConfiguration? memberConfig
sourcePath = null;
memberConfig = null;

if (!ctx.MemberConfigsByRootTargetName.TryGetValue(parameter.Name, out var memberConfigs))
if (
!ctx.RootTargetNameCasingMapping.TryGetValue(parameter.Name, out var parameterName)
|| !ctx.MemberConfigsByRootTargetName.TryGetValue(parameterName, out var memberConfigs)
)
{
return ctx.BuilderContext
.SymbolAccessor
Expand Down
29 changes: 29 additions & 0 deletions test/Riok.Mapperly.Tests/Mapping/CtorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,33 @@ public void DeepCloneRecordShouldNotUseCtorMapping()
"""
);
}

[Fact]
public void MapPropertyCtorMapping()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"""
[MapProperty(nameof(A.BId), nameof(B.Id))]
private partial B Map(A source);
""",
"class A { public long BId { get; } }",
"""
class B
{
public long Id { get; }
public B(long id) { Id = id; }
}
"""
);
TestHelper
.GenerateMapper(source)
.Should()
.HaveSingleMethodBody(
"""
var target = new global::B(source.BId);
return target;
"""
);
}
}

0 comments on commit b9db799

Please sign in to comment.