Skip to content

Commit 9d80a58

Browse files
authored
feat(source-generators): opt-in base class property mapping (#68)
Add IncludeBaseClassProperties to DynamoMapperAttribute and generator options to include inherited properties for root models, nested inline objects, and dot-notation validation. Update docs and add an inheritance example and generator snapshot tests. Bump package versions.
1 parent df43150 commit 9d80a58

25 files changed

+867
-28
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<VersionPrefix>1.0.1</VersionPrefix>
3+
<VersionPrefix>1.0.2</VersionPrefix>
44
<!-- SPDX license identifier for MIT -->
55
<PackageLicenseExpression>MIT</PackageLicenseExpression>
66
<!-- Other useful metadata -->

Directory.Packages.props

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
<PackageVersion Include="AutoFixture.AutoNSubstitute" Version="4.18.1" />
1010
<PackageVersion Include="AutoFixture.Xunit3" Version="4.19.0" />
1111
<PackageVersion Include="AwesomeAssertions" Version="9.3.0" />
12-
<PackageVersion Include="AWSSDK.DynamoDBv2" Version="4.0.10.9" />
12+
<PackageVersion Include="AWSSDK.DynamoDBv2" Version="4.0.14" />
1313
<PackageVersion Include="Basic.Reference.Assemblies.Net100" Version="1.8.4" />
1414
<PackageVersion Include="Basic.Reference.Assemblies.Net80" Version="1.8.4" />
1515
<PackageVersion Include="Basic.Reference.Assemblies.Net90" Version="1.8.4" />
1616
<PackageVersion Include="Humanizer.Core" Version="3.0.1" />
1717
<PackageVersion Include="LayeredCraft.SourceGeneratorTools" Version="0.1.0-beta.10" />
18-
<PackageVersion Include="Meziantou.Polyfill" Version="1.0.100" />
18+
<PackageVersion Include="Meziantou.Polyfill" Version="1.0.101" />
1919
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
2020
<PackageVersion Include="Microsoft.CodeAnalysis" Version="5.0.0" />
2121
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0" />
@@ -26,7 +26,7 @@
2626
<PackageVersion Include="NSubstitute" Version="5.3.0" />
2727
<PackageVersion Include="Scriban" Version="6.5.2" />
2828
<PackageVersion Include="Verify.SourceGenerators" Version="2.5.0" />
29-
<PackageVersion Include="Verify.XunitV3" Version="31.9.4" />
29+
<PackageVersion Include="Verify.XunitV3" Version="31.12.3" />
3030
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
3131
<PackageVersion Include="xunit.v3.mtp-v2" Version="3.2.2" />
3232
</ItemGroup>

LayeredCraft.DynamoMapper.slnx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
</Folder>
3232
<Folder Name="/docs/examples/">
3333
<File Path="docs\examples\custom-converters.md" />
34+
<File Path="docs\examples\inheritance-mapping.md" />
3435
<File Path="docs\examples\index.md" />
3536
<File Path="docs\examples\lambda-functions.md" />
3637
<File Path="docs\examples\nested-mapping.md" />
@@ -55,6 +56,7 @@
5556
</Folder>
5657
<Folder Name="/examples/">
5758
<Project Path="examples/DynamoMapper.FieldLevelOverride/DynamoMapper.FieldLevelOverride.csproj" />
59+
<Project Path="examples/DynamoMapper.Inheritance/DynamoMapper.Inheritance.csproj" />
5860
<Project Path="examples/DynamoMapper.MapperConstructor/DynamoMapper.MapperConstructor.csproj" />
5961
<Project Path="examples/DynamoMapper.Nested/DynamoMapper.Nested.csproj" />
6062
<Project Path="examples/DynamoMapper.SimpleExample/DynamoMapper.SimpleExample.csproj" />

docs/api-reference/attributes.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Marks a static partial class as a mapper and sets defaults.
1010
[DynamoMapper(
1111
Convention = DynamoNamingConvention.CamelCase,
1212
DefaultRequiredness = Requiredness.InferFromNullability,
13+
IncludeBaseClassProperties = false,
1314
OmitNullStrings = true,
1415
OmitEmptyStrings = false,
1516
DateTimeFormat = "O",
@@ -21,6 +22,25 @@ public static partial class OrderMapper
2122
}
2223
```
2324

25+
Properties:
26+
27+
- `Convention` - key naming convention
28+
- `DefaultRequiredness` - default requiredness
29+
- `IncludeBaseClassProperties` - include properties declared on base classes (opt-in)
30+
- `OmitNullStrings` - omit null string attributes
31+
- `OmitEmptyStrings` - omit empty string attributes
32+
- `DateTimeFormat` - `DateTime`/`DateTimeOffset` format
33+
- `TimeSpanFormat` - `TimeSpan` format
34+
- `EnumFormat` - enum format
35+
- `GuidFormat` - `Guid` format
36+
37+
Notes:
38+
39+
- When `IncludeBaseClassProperties = true`, inherited properties are included for root models and
40+
nested inline objects.
41+
- If a derived type declares a property with the same name as an inherited property, the derived
42+
property wins.
43+
2444
## DynamoFieldAttribute
2545

2646
Configures mapping for a specific member. Apply multiple times to the mapper class.

docs/examples/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ These example projects show DynamoMapper features end-to-end.
99
- `examples/DynamoMapper.MapperConstructor` - Constructor/record support and
1010
`[DynamoMapperConstructor]`
1111
- `examples/DynamoMapper.Nested` - Nested objects and nested collections
12+
- `examples/DynamoMapper.Inheritance` - Base class properties (opt-in)
13+
14+
See [Inheritance Mapping](inheritance-mapping.md) for details.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Inheritance Mapping
2+
3+
By default, DynamoMapper only maps properties declared on the model type being mapped. It does not
4+
map properties declared on base classes.
5+
6+
To include inherited properties, enable the mapper option:
7+
8+
```csharp
9+
using System.Collections.Generic;
10+
using Amazon.DynamoDBv2.Model;
11+
using DynamoMapper.Runtime;
12+
13+
namespace MyApp.Data;
14+
15+
[DynamoMapper(IncludeBaseClassProperties = true)]
16+
public static partial class OrderMapper
17+
{
18+
public static partial Dictionary<string, AttributeValue> ToItem(Order source);
19+
20+
public static partial Order FromItem(Dictionary<string, AttributeValue> item);
21+
}
22+
23+
public class BaseEntity
24+
{
25+
public string Id { get; set; } = string.Empty;
26+
}
27+
28+
public class Order : BaseEntity
29+
{
30+
public string Name { get; set; } = string.Empty;
31+
}
32+
```
33+
34+
Behavior notes:
35+
36+
- When enabled, inherited properties also participate in nested inline mapping.
37+
- If a derived type declares a property with the same name as an inherited property, the derived
38+
property wins.
39+
40+
See `examples/DynamoMapper.Inheritance` for a complete, runnable example showing both
41+
`IncludeBaseClassProperties = false` (default) and `true`.

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ See the [Quick Start Guide](getting-started/quick-start.md) for a complete tutor
6363
- **Clean Domain Models** - No attributes required on your domain classes
6464
- **Convention-First** - Sensible defaults with selective overrides
6565
- **Nested Mapping** - Inline or mapper-based support for nested objects and collections
66+
- **Optional Inheritance Support** - Opt-in mapping for properties declared on base classes
6667
- **Single-Table Friendly** - Built-in support for DynamoDB single-table patterns
6768
- **Comprehensive Diagnostics** - Clear compile-time errors with actionable messages
6869

docs/usage/basic-mapping.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,38 @@ public class Address
8484

8585
See `examples/DynamoMapper.Nested` for a complete example.
8686

87+
## Inheritance (Base Class Properties)
88+
89+
By default, DynamoMapper only considers properties declared on the model type being mapped. It does
90+
not include properties declared on base classes.
91+
92+
If you want inherited properties to participate in mapping, enable it on the mapper:
93+
94+
```csharp
95+
[DynamoMapper(IncludeBaseClassProperties = true)]
96+
public static partial class OrderMapper
97+
{
98+
public static partial Dictionary<string, AttributeValue> ToItem(Order source);
99+
public static partial Order FromItem(Dictionary<string, AttributeValue> item);
100+
}
101+
102+
public class BaseEntity
103+
{
104+
public string Id { get; set; }
105+
}
106+
107+
public class Order : BaseEntity
108+
{
109+
public string Name { get; set; }
110+
}
111+
```
112+
113+
Notes:
114+
115+
- This option also affects nested inline object mapping.
116+
- If a derived type declares a property with the same name as an inherited property, the derived
117+
property wins.
118+
87119
## Constructor Mapping Rules (`FromItem`)
88120

89121
Constructor selection is deterministic and follows these priorities.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<LangVersion>14</LangVersion>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
<PropertyGroup>
10+
<NoWarn>$(NoWarn);CS1591</NoWarn>
11+
</PropertyGroup>
12+
<ItemGroup>
13+
<PackageReference Include="AWSSDK.DynamoDBv2" />
14+
</ItemGroup>
15+
<ItemGroup>
16+
<ProjectReference
17+
Include="..\..\src\LayeredCraft.DynamoMapper.Generators\LayeredCraft.DynamoMapper.Generators.csproj"
18+
ReferenceOutputAssembly="false"
19+
OutputItemType="Analyzer"
20+
/>
21+
<ProjectReference
22+
Include="..\..\src\LayeredCraft.DynamoMapper.Runtime\LayeredCraft.DynamoMapper.Runtime.csproj"
23+
ReferenceOutputAssembly="true"
24+
OutputItemType="Analyzer"
25+
/>
26+
</ItemGroup>
27+
</Project>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Amazon.DynamoDBv2.Model;
4+
using DynamoMapper.Runtime;
5+
6+
namespace DynamoMapper.Inheritance;
7+
8+
[DynamoMapper]
9+
internal static partial class OrderMapper_Default
10+
{
11+
internal static partial Dictionary<string, AttributeValue> ToItem(Order source);
12+
13+
internal static partial Order FromItem(Dictionary<string, AttributeValue> item);
14+
}
15+
16+
[DynamoMapper(IncludeBaseClassProperties = true)]
17+
internal static partial class OrderMapper_WithBaseProps
18+
{
19+
internal static partial Dictionary<string, AttributeValue> ToItem(Order source);
20+
21+
internal static partial Order FromItem(Dictionary<string, AttributeValue> item);
22+
}
23+
24+
internal class BaseEntity
25+
{
26+
internal string Id { get; set; } = string.Empty;
27+
}
28+
29+
internal class Order : BaseEntity
30+
{
31+
internal string Name { get; set; } = string.Empty;
32+
}
33+
34+
internal static class Program
35+
{
36+
private static void Main()
37+
{
38+
var order = new Order { Id = "order-123", Name = "Sample Order" };
39+
40+
var itemDefault = OrderMapper_Default.ToItem(order);
41+
var itemWithBase = OrderMapper_WithBaseProps.ToItem(order);
42+
43+
Console.WriteLine("Default mapper keys: " + string.Join(", ", itemDefault.Keys));
44+
Console.WriteLine("With base props keys: " + string.Join(", ", itemWithBase.Keys));
45+
46+
var roundTripDefault = OrderMapper_Default.FromItem(itemWithBase);
47+
var roundTripWithBase = OrderMapper_WithBaseProps.FromItem(itemWithBase);
48+
49+
Console.WriteLine("Default mapper Id: '" + roundTripDefault.Id + "'");
50+
Console.WriteLine("With base props Id: '" + roundTripWithBase.Id + "'");
51+
}
52+
}

0 commit comments

Comments
 (0)