Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
49 changes: 43 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,54 @@ dotnet build examples/DynamoMapper.SimpleExample/DynamoMapper.SimpleExample.cspr
# Run all tests across all target frameworks (net8.0, net9.0, net10.0)
dotnet test

# Run tests for specific framework
# Run tests for a specific framework
# (recommended when working on a single test failure)
dotnet test --framework net8.0

# Run tests with verbose output
dotnet test --logger "console;verbosity=detailed"

# Run specific test (example)
dotnet test --filter "FullyQualifiedName~Simple_HelloWorld"

# Run tests in specific project
dotnet test test/LayeredCraft.DynamoMapper.Generators.Tests/LayeredCraft.DynamoMapper.Generators.Tests.csproj
# Run tests in a specific project (avoid running other test projects)
dotnet test --project test/LayeredCraft.DynamoMapper.Generators.Tests/LayeredCraft.DynamoMapper.Generators.Tests.csproj

# Discover available tests (xUnit v3 + Microsoft.Testing.Platform)
# Copy the fully-qualified test name from this output.
DOTNET_NOLOGO=1 dotnet test \
--project test/LayeredCraft.DynamoMapper.Generators.Tests/LayeredCraft.DynamoMapper.Generators.Tests.csproj \
-f net10.0 \
-v q \
--list-tests \
--no-progress \
--no-ansi

# Run a single test method (exact fully-qualified name)
DOTNET_NOLOGO=1 dotnet test \
--project test/LayeredCraft.DynamoMapper.Generators.Tests/LayeredCraft.DynamoMapper.Generators.Tests.csproj \
-f net10.0 \
-v q \
--filter-method "MyNamespace.MyTestClass.MyTestMethod" \
--minimum-expected-tests 1 \
--no-progress \
--no-ansi

# Common filter variants
DOTNET_NOLOGO=1 dotnet test \
--project test/LayeredCraft.DynamoMapper.Generators.Tests/LayeredCraft.DynamoMapper.Generators.Tests.csproj \
-f net10.0 \
-v q \
--filter-class "MyNamespace.MyTestClass" \
--minimum-expected-tests 1 \
--no-progress \
--no-ansi

DOTNET_NOLOGO=1 dotnet test \
--project test/LayeredCraft.DynamoMapper.Generators.Tests/LayeredCraft.DynamoMapper.Generators.Tests.csproj \
-f net10.0 \
-v q \
--filter-namespace "MyNamespace.Tests" \
--minimum-expected-tests 1 \
--no-progress \
--no-ansi
```

### Taskfile Commands (if task is installed)
Expand Down
44 changes: 43 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,53 @@ dotnet build
# Run all tests across all target frameworks (net8.0, net9.0, net10.0)
dotnet test

# Run tests for specific framework
# Run tests for a specific framework
# (recommended when working on a single test failure)
dotnet test --framework net8.0

# Run tests with verbose output
dotnet test --logger "console;verbosity=detailed"

# --- xUnit v3 + Microsoft.Testing.Platform (lowest-noise filtered runs) ---

# List available tests (discovery)
# Copy the fully-qualified test name from this output.
DOTNET_NOLOGO=1 dotnet test \
--project test/LayeredCraft.DynamoMapper.Generators.Tests/LayeredCraft.DynamoMapper.Generators.Tests.csproj \
-f net10.0 \
-v q \
--list-tests \
--no-progress \
--no-ansi

# Run a single test method (exact fully-qualified name)
DOTNET_NOLOGO=1 dotnet test \
--project test/LayeredCraft.DynamoMapper.Generators.Tests/LayeredCraft.DynamoMapper.Generators.Tests.csproj \
-f net10.0 \
-v q \
--filter-method "MyNamespace.MyTestClass.MyTestMethod" \
--minimum-expected-tests 1 \
--no-progress \
--no-ansi

# Variants: class / namespace
DOTNET_NOLOGO=1 dotnet test \
--project test/LayeredCraft.DynamoMapper.Generators.Tests/LayeredCraft.DynamoMapper.Generators.Tests.csproj \
-f net10.0 \
-v q \
--filter-class "MyNamespace.MyTestClass" \
--minimum-expected-tests 1 \
--no-progress \
--no-ansi

DOTNET_NOLOGO=1 dotnet test \
--project test/LayeredCraft.DynamoMapper.Generators.Tests/LayeredCraft.DynamoMapper.Generators.Tests.csproj \
-f net10.0 \
-v q \
--filter-namespace "MyNamespace.Tests" \
--minimum-expected-tests 1 \
--no-progress \
--no-ansi
```

### Restore Dependencies
Expand Down
1 change: 1 addition & 0 deletions LayeredCraft.DynamoMapper.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
</Folder>
<Folder Name="/examples/">
<Project Path="examples/DynamoMapper.FieldLevelOverride/DynamoMapper.FieldLevelOverride.csproj" />
<Project Path="examples/DynamoMapper.MapperConstructor/DynamoMapper.MapperConstructor.csproj" />
<Project Path="examples/DynamoMapper.SimpleExample/DynamoMapper.SimpleExample.csproj" />
</Folder>
<Folder Name="/git/">
Expand Down
37 changes: 37 additions & 0 deletions docs/api-reference/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,40 @@ public static partial class OrderMapper
Properties:
- `MemberName` (ctor) - target member name
- `Ignore` - `IgnoreMapping.All`, `IgnoreMapping.FromModel`, or `IgnoreMapping.ToModel`

## DynamoMapperConstructorAttribute

Marks which constructor DynamoMapper should use when generating `FromItem` for a model type.

This attribute is applied to the **model's constructor**, not the mapper class.

```csharp
using DynamoMapper.Runtime;

public class User
{
public User()
{
Id = string.Empty;
Name = string.Empty;
}

[DynamoMapperConstructor]
public User(string id, string name)
{
Id = id;
Name = name;
}

public string Id { get; set; }
public string Name { get; set; }
}
```

Rules:

- Only one constructor can be marked with `[DynamoMapperConstructor]`.
- If multiple are marked, DynamoMapper emits diagnostic `DM0103`.

See [Basic Mapping](../usage/basic-mapping.md#constructor-mapping-rules-fromitem) for the full
constructor selection rules and gotchas.
27 changes: 27 additions & 0 deletions docs/api-reference/generated-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,30 @@ internal static partial class DuplicateResultMapper
Notes:
- Generated code uses `SetX`/`GetX` helpers from `DynamoMapper.Runtime`.
- Class-level `[DynamoField]`/`[DynamoIgnore]` configuration influences argument values.

## Constructor-Based `FromItem`

If your model type uses a constructor (records, get-only properties, or an explicitly attributed
constructor), DynamoMapper will generate `FromItem` with constructor arguments.

See [Basic Mapping](../usage/basic-mapping.md#constructor-mapping-rules-fromitem) for the full
constructor selection rules and gotchas.

```csharp
[global::System.CodeDom.Compiler.GeneratedCode("DynamoMapper", "REPLACED")]
public static partial global::MyNamespace.Product FromItem(
global::System.Collections.Generic.Dictionary<string, global::Amazon.DynamoDBv2.Model.AttributeValue> item
)
{
var product = new global::MyNamespace.Product(
Id: item.GetString("id", Requiredness.InferFromNullability),
Name: item.GetString("name", Requiredness.InferFromNullability)
)
{
Price = item.GetDecimal("price", Requiredness.InferFromNullability),
Quantity = item.GetInt("quantity", Requiredness.InferFromNullability),
};

return product;
}
```
15 changes: 15 additions & 0 deletions docs/core-concepts/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,21 @@ public static partial class ProductMapper
}
```

### Object Construction (`FromItem`)

When generating `FromItem`, DynamoMapper chooses between:

- **Property-based construction**: `new T { Prop = ..., ... }`
- **Constructor-based construction**: `new T(arg1, arg2, ...)` (optionally with an object
initializer)

Constructor-based construction is used for records/record structs with primary constructors and for
classes where read-only properties must be populated through a constructor. You can also explicitly
choose a constructor using `[DynamoMapperConstructor]`.

See [Basic Mapping](../usage/basic-mapping.md#constructor-mapping-rules-fromitem) for the full
selection rules.

### Key Characteristics

- **Direct property access** - No reflection
Expand Down
11 changes: 9 additions & 2 deletions docs/examples/index.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# Index
# Examples

Documentation coming soon. See [Phase 1 Requirements](../roadmap/phase-1.md) for detailed specifications.
These example projects show DynamoMapper features end-to-end.

## Included Examples

- `examples/DynamoMapper.SimpleExample` - Minimal mapper + usage
- `examples/DynamoMapper.FieldLevelOverride` - Field-level overrides via `[DynamoField]`
- `examples/DynamoMapper.MapperConstructor` - Constructor/record support and
`[DynamoMapperConstructor]`
3 changes: 2 additions & 1 deletion docs/roadmap/phase-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ public static partial class JediCharacterMapper
- **records** (with settable properties)
- `init` setters supported (generator uses object initializer)

**Not required for Phase 1:** mapping to constructors / positional records.
Constructor/record-based deserialization is supported via constructor selection and
`[DynamoMapperConstructor]`.

### 4.2 Property Inclusion Rules (Defaults)
By default, generator maps:
Expand Down
Loading