Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support MapProperty with nameof for CtorMapping #957

Merged
merged 1 commit into from Dec 6, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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; }
}
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
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
Expand Up @@ -320,7 +320,10 @@ private static void BuildConstructorMapping(INewInstanceBuilderContext<IMapping>
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
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;
"""
);
}
}