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
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ trim_trailing_whitespace = true
indent_size = 4
max_line_length = 120

[*HostFactoryResolver.cs]
[{*HostFactoryResolver.cs,AnalyzerReleases.Shipped.md,AnalyzerReleases.Unshipped.md}]
ij_formatter_enabled = false
resharper_disable_formatter = true
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>1.2.0</VersionPrefix>
<VersionPrefix>1.3.0</VersionPrefix>
<!-- SPDX license identifier for MIT -->
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!-- Other useful metadata -->
Expand Down
24 changes: 14 additions & 10 deletions docs/core-concepts/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,16 +248,20 @@ static partial void AfterToItem(Product source, Dictionary<string, AttributeValu

DynamoMapper natively supports:

| .NET Type | DynamoDB Type | Notes |
|------------------------------------------|---------------|--------------------------------------------------|
| `string` | S (String) | |
| `int`, `long`, `decimal`, `double` | N (Number) | Culture-invariant |
| `bool` | BOOL | |
| `Guid` | S | ToString/Parse |
| `DateTime`, `DateTimeOffset`, `TimeSpan` | S | ISO-8601 / constant format |
| `enum` | S | String name |
| Nullable variants | S/N/BOOL | Null checks generated |
| Collections | L/M/SS/NS/BS | Lists, maps, and sets of supported element types |
| .NET Type | DynamoDB Type | Notes |
|------------------------------------------|---------------|--------------------------------------|
| `string` | S (String) | |
| `int`, `long`, `decimal`, `double` | N (Number) | Culture-invariant |
| `bool` | BOOL | |
| `Guid` | S | ToString/Parse |
| `DateTime`, `DateTimeOffset`, `TimeSpan` | S | ISO-8601 / constant format |
| `enum` | S | String name |
| Nullable variants | S/N/BOOL | Null checks generated |
| Collections | L/M/SS/NS/BS | Lists, maps, and set-like CLR shapes |

Set-like CLR collections use DynamoDB native set types (`SS`, `NS`, `BS`) when the element type
supports them. Other supported set element types, such as `Guid`, `DateTimeOffset`, `TimeSpan`,
and enums, are stored as `L` and materialized back into the declared CLR set shape during reads.

### Custom Types

Expand Down
6 changes: 6 additions & 0 deletions skills/dynamo-mapper/references/type-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Supported root collection shapes:
Important note:

- `byte[]` is supported as a byte collection, not as a special binary scalar type
- set-like CLR shapes preserve their declared shape on round-trip, but DynamoDB storage depends on
the element type

## Supported collection elements

Expand All @@ -34,6 +36,10 @@ Set families:
- numeric -> `NS`
- `byte[]` -> `BS`

When a set-like CLR collection uses another supported element type such as `Guid`,
`DateTimeOffset`, `TimeSpan`, or enums, DynamoMapper stores it as `L` and materializes it back
into the declared CLR set shape on read.

## Nested shapes

Supported:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
; Shipped analyzer releases
; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

## Release 1.2.0

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|------
DM0001 | DynamoMapper.Usage | Error |
DM0003 | DynamoMapper.Usage | Error |
DM0004 | DynamoMapper.Usage | Error |
DM0005 | DynamoMapper.Usage | Error |
DM0006 | DynamoMapper.Usage | Error |
DM0007 | DynamoMapper.Usage | Error |
DM0008 | DynamoMapper.Usage | Error |
DM0009 | DynamoMapper.Usage | Error |
DM0101 | DynamoMapper.Usage | Error |
DM0102 | DynamoMapper.Usage | Error |
DM0103 | DynamoMapper.Usage | Error |
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

### New Rules
### Changed Rules

Rule ID | Category | Severity | Notes
---------|--------------------|----------|-----------------------
DM0001 | DynamoMapper.Usage | Error | DiagnosticDescriptors
DM0003 | DynamoMapper.Usage | Error | DiagnosticDescriptors
DM0004 | DynamoMapper.Usage | Error | DiagnosticDescriptors
DM0005 | DynamoMapper.Usage | Error | DiagnosticDescriptors
DM0006 | DynamoMapper.Usage | Error | DiagnosticDescriptors
DM0007 | DynamoMapper.Usage | Error | DiagnosticDescriptors
DM0008 | DynamoMapper.Usage | Error | DiagnosticDescriptors
DM0009 | DynamoMapper.Usage | Error | DiagnosticDescriptors
DM0101 | DynamoMapper.Usage | Error | DiagnosticDescriptors
DM0102 | DynamoMapper.Usage | Error | DiagnosticDescriptors
DM0103 | DynamoMapper.Usage | Error | DiagnosticDescriptors
Rule ID | New Category | New Severity | Old Category | Old Severity | Notes
--------|--------------|--------------|--------------|--------------|------
DM0001 | LayeredCraft.DynamoMapper.Usage | Error | DynamoMapper.Usage | Error | Category changed
DM0003 | LayeredCraft.DynamoMapper.Usage | Error | DynamoMapper.Usage | Error | Category changed
DM0004 | LayeredCraft.DynamoMapper.Usage | Error | DynamoMapper.Usage | Error | Category changed
DM0005 | LayeredCraft.DynamoMapper.Usage | Error | DynamoMapper.Usage | Error | Category changed
DM0006 | LayeredCraft.DynamoMapper.Usage | Error | DynamoMapper.Usage | Error | Category changed
DM0007 | LayeredCraft.DynamoMapper.Usage | Error | DynamoMapper.Usage | Error | Category changed
DM0008 | LayeredCraft.DynamoMapper.Usage | Error | DynamoMapper.Usage | Error | Category changed
DM0009 | LayeredCraft.DynamoMapper.Usage | Error | DynamoMapper.Usage | Error | Category changed
DM0101 | LayeredCraft.DynamoMapper.Usage | Error | DynamoMapper.Usage | Error | Category changed
DM0102 | LayeredCraft.DynamoMapper.Usage | Error | DynamoMapper.Usage | Error | Category changed
DM0103 | LayeredCraft.DynamoMapper.Usage | Error | DynamoMapper.Usage | Error | Category changed
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal readonly record struct ElementTypeValidationResult(
ElementType: elementType,
TargetKind: DynamoKind.L,
KeyType: null,
IsArray: true
CollectionReadMaterialization.Array
);
}

Expand All @@ -66,8 +66,7 @@ internal readonly record struct ElementTypeValidationResult(
Category: CollectionCategory.Map,
ElementType: valueType,
TargetKind: DynamoKind.M,
KeyType: keyType,
IsArray: false
keyType
);
}
}
Expand All @@ -92,9 +91,17 @@ internal readonly record struct ElementTypeValidationResult(
ElementType: elementType,
TargetKind: setKind.Value,
KeyType: null,
IsArray: false
CollectionReadMaterialization.HashSet
);
}

return new CollectionInfo(
CollectionCategory.List,
elementType,
DynamoKind.L,
null,
CollectionReadMaterialization.HashSet
);
}
}

Expand All @@ -120,8 +127,7 @@ internal readonly record struct ElementTypeValidationResult(
Category: CollectionCategory.List,
ElementType: elementType,
TargetKind: DynamoKind.L,
KeyType: null,
IsArray: false
null
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ namespace LayeredCraft.DynamoMapper.Generator.PropertyMapping.Models;
/// <param name="KeyType">
/// For map types only, the key type (must be string).
/// </param>
/// <param name="IsArray">
/// True if the original property type is an array (T[]), false otherwise.
/// <param name="ReadMaterialization">
/// Describes any additional CLR-shape materialization required after deserializing the
/// DynamoDB representation.
/// </param>
/// <param name="ElementNestedMapping">
/// For collections of nested objects, contains the nested mapping info for the element type.
Expand All @@ -28,10 +29,21 @@ internal sealed record CollectionInfo(
ITypeSymbol ElementType,
DynamoKind TargetKind,
ITypeSymbol? KeyType = null,
bool IsArray = false,
CollectionReadMaterialization ReadMaterialization = CollectionReadMaterialization.None,
NestedMappingInfo? ElementNestedMapping = null
);

/// <summary>
/// Describes how a deserialized collection result should be materialized back into the
/// declared CLR shape.
/// </summary>
internal enum CollectionReadMaterialization
{
None,
Array,
HashSet,
}

/// <summary>
/// Categorizes collection types by their DynamoDB mapping behavior.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,13 @@ private static IPropertySymbol[] GetMappableProperties(
}

// Create collection strategy (simplified for nested objects)
var collectionStrategy = CreateCollectionStrategy(collectionInfo, propertyType);
var collectionStrategy =
CreateCollectionStrategy(
collectionInfo,
propertyType,
fieldOptions,
nestedContext.Context
);
propertySpecs.Add(
new NestedPropertySpec(
property.Name,
Expand Down Expand Up @@ -532,7 +538,8 @@ GeneratorContext context
/// Creates a collection type mapping strategy.
/// </summary>
private static TypeMappingStrategy CreateCollectionStrategy(
CollectionInfo collectionInfo, ITypeSymbol originalType
CollectionInfo collectionInfo, ITypeSymbol originalType, DynamoFieldOptions? fieldOptions,
GeneratorContext context
)
{
var isNullable = originalType.NullableAnnotation == NullableAnnotation.Annotated;
Expand Down Expand Up @@ -560,16 +567,61 @@ private static TypeMappingStrategy CreateCollectionStrategy(
),
};

var (fromArgs, toArgs) =
GetCollectionElementTypeSpecificArgs(collectionInfo.ElementType, fieldOptions, context);

return new TypeMappingStrategy(
typeName,
genericArg,
nullableModifier,
[],
[],
fromArgs,
toArgs,
KindOverride: collectionInfo.TargetKind
);
}

private static (string[] FromArgs, string[] ToArgs) GetCollectionElementTypeSpecificArgs(
ITypeSymbol elementType, DynamoFieldOptions? fieldOptions, GeneratorContext context
)
{
var underlyingType = UnwrapNullable(elementType);

return underlyingType switch
{
{ SpecialType: SpecialType.System_DateTime } => CreateCollectionFormatArgs(
fieldOptions?.Format ?? context.MapperOptions.DateTimeFormat
),
INamedTypeSymbol t when context.WellKnownTypes.IsType(
t,
WellKnownTypeData.WellKnownType.System_DateTimeOffset
) => CreateCollectionFormatArgs(
fieldOptions?.Format ?? context.MapperOptions.DateTimeFormat
),
INamedTypeSymbol t when context.WellKnownTypes.IsType(
t,
WellKnownTypeData.WellKnownType.System_Guid
) => CreateCollectionFormatArgs(
fieldOptions?.Format ?? context.MapperOptions.GuidFormat
),
INamedTypeSymbol t when context.WellKnownTypes.IsType(
t,
WellKnownTypeData.WellKnownType.System_TimeSpan
) => CreateCollectionFormatArgs(
fieldOptions?.Format ?? context.MapperOptions.TimeSpanFormat
),
INamedTypeSymbol { TypeKind: TypeKind.Enum } => CreateCollectionFormatArgs(
fieldOptions?.Format ?? context.MapperOptions.EnumFormat
),
_ => ([], []),
};
}

private static (string[] FromArgs, string[] ToArgs) CreateCollectionFormatArgs(string format)
{
var arg = $"\"{format}\"";
return ([arg], [arg]);
}

/// <summary>
/// Unwraps Nullable{T} to get the underlying type.
/// </summary>
Expand Down
Loading
Loading