Skip to content

Commit

Permalink
feat: support named mappings for existing target objects
Browse files Browse the repository at this point in the history
  • Loading branch information
clegoz committed Feb 15, 2025
1 parent ca3080e commit 76e329e
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using Riok.Mapperly.Descriptors.MappingBodyBuilders.BuilderContext;
using Riok.Mapperly.Descriptors.Mappings;
using Riok.Mapperly.Descriptors.Mappings.ExistingTarget;
using Riok.Mapperly.Descriptors.Mappings.MemberMappings;
using Riok.Mapperly.Diagnostics;
using Riok.Mapperly.Symbols.Members;
Expand Down Expand Up @@ -217,19 +218,28 @@ MemberMappingInfo memberMappingInfo
var sourceMemberPath = memberMappingInfo.SourceMember;
var targetMemberPath = memberMappingInfo.TargetMember;

// if the member is readonly
// and the target and source path is readable,
// we try to create an existing target mapping
if (
targetMemberPath.Member is { CanSet: true, IsInitOnly: false }
|| !targetMemberPath.Path.All(op => op.CanGet)
|| !sourceMemberPath.MemberPath.Path.All(op => op.CanGet)
)
IExistingTargetMapping? existingTargetMapping;
if (memberMappingInfo.Configuration?.Use is not null)
{
return false;
existingTargetMapping = ctx.BuilderContext.FindExistingTargetNamedMapping(memberMappingInfo.Configuration.Use);
}
else
{
// if the member is readonly
// and the target and source path is readable,
// we try to create an existing target mapping
if (
targetMemberPath.Member is { CanSet: true, IsInitOnly: false }
|| !targetMemberPath.Path.All(op => op.CanGet)
|| !sourceMemberPath.MemberPath.Path.All(op => op.CanGet)
)
{
return false;
}

existingTargetMapping = ctx.BuilderContext.FindOrBuildExistingTargetMapping(memberMappingInfo.ToTypeMappingKey());
}

var existingTargetMapping = ctx.BuilderContext.FindOrBuildExistingTargetMapping(memberMappingInfo.ToTypeMappingKey());
if (existingTargetMapping == null)
return false;

Expand Down
11 changes: 11 additions & 0 deletions src/Riok.Mapperly/Descriptors/MappingBuilderContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,17 @@ protected MappingBuilderContext(
return ExistingTargetMappingBuilder.Find(mappingKey) ?? BuildExistingTargetMapping(mappingKey, options);
}

public virtual IExistingTargetMapping? FindExistingTargetNamedMapping(string mappingName)
{
var mapping = ExistingTargetMappingBuilder.FindNamed(mappingName, out bool ambiguousName);
if (ambiguousName)
{
ReportDiagnostic(DiagnosticDescriptors.ReferencedMappingAmbiguous, mappingName);
}

return mapping;
}

/// <summary>
/// Tries to build an existing target instance mapping.
/// If no mapping is possible for the provided types,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public class ExistingTargetMappingBuilder(MappingCollection mappings)
return mappings.FindExistingInstanceMapping(mappingKey);
}

public IExistingTargetMapping? FindNamed(string name, out bool ambiguousName)
{
return mappings.FindExistingInstanceNamedMapping(name, out ambiguousName);
}

public IExistingTargetMapping? Build(MappingBuilderContext ctx, bool resultIsReusable)
{
foreach (var mappingBuilder in _builders)
Expand Down
3 changes: 3 additions & 0 deletions src/Riok.Mapperly/Descriptors/MappingCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ public class MappingCollection

public IExistingTargetMapping? FindExistingInstanceMapping(TypeMappingKey mappingKey) => _existingTargetMappings.Find(mappingKey);

public IExistingTargetMapping? FindExistingInstanceNamedMapping(string name, out bool ambiguousName) =>
_existingTargetMappings.FindNamed(name, out ambiguousName);

public IEnumerable<(IMapping, MappingBuilderContext)> DequeueMappingsToBuildBody() => _mappingsToBuildBody.DequeueAll();

public void EnqueueToBuildBody(ITypeMapping mapping, MappingBuilderContext ctx) => _mappingsToBuildBody.Enqueue((mapping, ctx));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,4 +411,100 @@ class B
);
return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task UserDefinedExistingTargetMapping()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"""
[MapProperty(nameof(A.Value), nameof(B.Value), Use = "MapValue")]
public partial void Map(A source, B target);
private partial void MapValue(C source, D target);
""",
"""
public class A
{
public C Value { get; set; }
}
""",
"""
public class B
{
public D Value { get; set; }
}
""",
"""
public class C
{
public string StringValue { get; set; }
}
""",
"""
public class D
{
public string StringValue { get; set; }
}
"""
);
return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task UserImplementedExistingTargetMapping()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"""
[MapProperty(nameof(A.Values), nameof(B.Values), Use = "MapValues")]
public partial void Map(A source, B target);
private void MapValues(List<string> source, List<string> target) { }
""",
"""
public class A
{
public List<string> Values { get; set; }
}
""",
"""
public class B
{
public List<string> Values { get; set; }
}
"""
);
return TestHelper.VerifyGenerator(source);
}

[Fact]
public Task ShouldUseReferencedMappingOnSelectedPropertiesWithExistingInstance()
{
var source = TestSourceBuilder.MapperWithBodyAndTypes(
"""
[MapProperty(nameof(A.Values1), nameof(B.Values1), Use = nameof(MapV1)]
[MapProperty(nameof(A.Values2), nameof(B.Values2), Use = nameof(MapV2)]
public partial B Map(A source);
[UserMapping(Default = true)]
private void DefaultMapping(List<string> source, List<string> target) {}
private void MapV1(List<string> source, List<string> target) {}
private void MapV2(List<string> source, List<string> target) {}
""",
"""
class A
{
public List<string> Values { get; } = [];
public List<string> Values1 { get; } = [];
public List<string> Values2 { get; } = [];
}
""",
"""
class B
{
public List<string> Values { get; } = [];
public List<string> Values1 { get; } = [];
public List<string> Values2 { get; } = [];
}
"""
);
return TestHelper.VerifyGenerator(source);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//HintName: Mapper.g.cs
// <auto-generated />
#nullable enable
public partial class Mapper
{
[global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")]
public partial global::B Map(global::A source)
{
var target = new global::B();
DefaultMapping(source.Values, target.Values);
MapV1(source.Values1, target.Values1);
MapV2(source.Values2, target.Values2);
return target;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//HintName: Mapper.g.cs
// <auto-generated />
#nullable enable
public partial class Mapper
{
[global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")]
public partial void Map(global::A source, global::B target)
{
MapValue(source.Value, target.Value);
}

[global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")]
private partial void MapValue(global::C source, global::D target)
{
target.StringValue = source.StringValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//HintName: Mapper.g.cs
// <auto-generated />
#nullable enable
public partial class Mapper
{
[global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "0.0.1.0")]
public partial void Map(global::A source, global::B target)
{
MapValues(source.Values, target.Values);
}
}

0 comments on commit 76e329e

Please sign in to comment.