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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ The target type lets MergePatchDto generate typed mapping:
patch.ApplyTo(person);
```

If the same patch shape should apply to multiple concrete types, target a
shared interface. Generated `ApplyTo` accepts the interface and maps only
members declared on that interface.

It does not mean every property on `Person` is patchable. The DTO remains the boundary.

## Manual Updates
Expand Down Expand Up @@ -174,9 +178,7 @@ If a target property should not be patchable, leave it off the DTO.
deserialized and presence-tracked, but excluded from generated `ApplyTo`.
- `[PatchUsing(nameof(Method))]` calls custom domain logic when the patch
property was provided.
- `[MergePatchTarget(typeof(OtherTarget))]` adds another typed `ApplyTo`
overload.
- Targets can be interfaces. In that case, generated `ApplyTo` accepts the interface and maps only members declared on that interface.
- Targets can be interfaces.

```csharp
[MergePatch(typeof(Person))]
Expand Down
12 changes: 3 additions & 9 deletions src/MergePatchDto.Generators/MergePatchGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace MergePatchDto.Generators
public sealed class MergePatchGenerator : IIncrementalGenerator
{
private const string MergePatchAttributeName = "MergePatch.MergePatchAttribute";
private const string MergePatchTargetAttributeName = "MergePatch.MergePatchTargetAttribute";
private const string PatchIgnoreAttributeName = "MergePatch.PatchIgnoreAttribute";
private const string PatchToAttributeName = "MergePatch.PatchToAttribute";
private const string PatchUsingAttributeName = "MergePatch.PatchUsingAttribute";
Expand Down Expand Up @@ -382,14 +381,9 @@ private static EffectiveVisibility Min(EffectiveVisibility left, EffectiveVisibi

var patchDtoAttribute = context.Attributes.FirstOrDefault(attribute => IsAttribute(attribute, MergePatchAttributeName));

var targetAttributes = typeSymbol
.GetAttributes()
.Where(attribute => IsAttribute(attribute, MergePatchTargetAttributeName))
.ToArray();

var targetDeclarations = targetAttributes
.Concat(patchDtoAttribute == null ? Enumerable.Empty<AttributeData>() : new[] { patchDtoAttribute })
.ToArray();
var targetDeclarations = patchDtoAttribute == null
? Enumerable.Empty<AttributeData>()
: new[] { patchDtoAttribute };

var targets = targetDeclarations
.Select(BuildTargetModel)
Expand Down
25 changes: 0 additions & 25 deletions src/MergePatchDto/MergePatchTargetAttribute.cs

This file was deleted.

14 changes: 0 additions & 14 deletions tests/MergePatchDto.Tests/ApplyToConventionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,6 @@ public void ApplyToDoesNotCallSetterForMissingProperty()
Assert.Equal(before, target.NameSetCount);
}

[Fact]
public void PatchDtoCanGenerateMultipleTargetOverloads()
{
var patch = JsonSerializer.Deserialize<MultiTargetPatch>("""{ "Name": "Draft" }""")!;
var documentTarget = new Document();
var draftTarget = new DocumentDraft();

patch.ApplyTo(documentTarget);
patch.ApplyTo(draftTarget);

Assert.Equal("Draft", documentTarget.Name);
Assert.Equal("Draft", draftTarget.Name);
}

[Fact]
public void PatchDtoCanTargetInterfaceImplementedByMultipleClasses()
{
Expand Down
29 changes: 1 addition & 28 deletions tests/MergePatchDto.Tests/DiagnosticsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ public void ReportsErrorWhenPatchTargetCannotBeResolved()
"""
using MergePatch;

[MergePatch]
[MergePatchTarget(typeof(MissingTarget))]
[MergePatch(typeof(MissingTarget))]
public partial class Patch
{
public string? Name { get; set; }
Expand Down Expand Up @@ -132,32 +131,6 @@ public partial class Patch
!diagnostic.Id.StartsWith("MPD", StringComparison.Ordinal));
}

[Fact]
public void ReportsErrorWhenAdditionalPatchTargetIsOpenGeneric()
{
var diagnostics = DiagnosticTestHelper.GetAllDiagnostics(
"""
using MergePatch;

public class Box<T>
{
public string? Name { get; set; }
}

[MergePatch]
[MergePatchTarget(typeof(Box<>))]
public partial class Patch
{
public string? Name { get; set; }
}
""");

Assert.Contains(diagnostics, diagnostic => diagnostic.Id == "MPD018" && diagnostic.Severity == DiagnosticSeverity.Error);
Assert.DoesNotContain(diagnostics, diagnostic =>
diagnostic.Severity == DiagnosticSeverity.Error &&
!diagnostic.Id.StartsWith("MPD", StringComparison.Ordinal));
}

[Fact]
public void ReportsErrorWhenPatchTargetIsLessAccessibleThanPublicPatchDto()
{
Expand Down
13 changes: 0 additions & 13 deletions tests/MergePatchDto.Tests/TestModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,6 @@ public void SetPriority(int? value)
}
}

public sealed class DocumentDraft
{
public string? Name { get; set; }
}

public interface INamedResource
{
string? Name { get; set; }
Expand Down Expand Up @@ -120,14 +115,6 @@ private static void ApplyPriority(Document target, int? value)
}
}

[MergePatch]
[MergePatchTarget(typeof(Document))]
[MergePatchTarget(typeof(DocumentDraft))]
public partial class MultiTargetPatch
{
public string? Name { get; set; }
}

[MergePatch(typeof(INamedResource))]
public partial class InterfaceTargetPatch
{
Expand Down