Skip to content

Commit 56965c2

Browse files
committed
Use MarshalHelper when Unmarshalling & Marshalling sets
1 parent 26b1711 commit 56965c2

31 files changed

Lines changed: 568 additions & 62 deletions

src/DynamoDBGenerator.SourceGenerator/Constants.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public static class Marshaller
8080

8181
public static class AttributeValueUtilityFactory
8282
{
83-
private const string ClassName = "MarshallHelper";
83+
public const string ClassName = "MarshallHelper";
8484
public const string ToAttributeValue = $"{ClassName}.ToAttributeValue";
8585
public const string Null = $"{ClassName}.Null";
8686
public const string ToList = $"{ClassName}.ToList";
@@ -93,6 +93,10 @@ public static class AttributeValueUtilityFactory
9393
public const string ToDictionary = $"{ClassName}.ToDictionary";
9494
public const string ToLookup = $"{ClassName}.ToLookup";
9595
public const string FromLookup = $"{ClassName}.FromLookup";
96+
public const string FromStringSet = $"{ClassName}.FromStringSet";
97+
public const string FromNullableStringSet = $"{ClassName}.FromNullableStringSet";
98+
public const string FromNumberSet = $"{ClassName}.FromNumberSet";
99+
public const string FromNullableNumberSet = $"{ClassName}.FromNullableNumberSet";
96100
}
97101
public static class ExceptionHelper
98102
{

src/DynamoDBGenerator.SourceGenerator/Generations/Marshalling/Marshaller.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,15 @@ SingleGeneric.SupportedType.Set when singleGeneric.T.TypeSymbol.SpecialType is S
125125
.CreateScope(
126126
$"if ({ParamReference} is null)"
127127
.CreateScope(singleGeneric.ReturnNullOrThrow(DataMember))
128-
.Append($"return new {Constants.AWSSDK_DynamoDBv2.AttributeValue} {{ SS = new List<{(singleGeneric.T.IsSupposedToBeNull ? "string?" : "string")}>({(singleGeneric.T.IsSupposedToBeNull ? ParamReference : $"{ParamReference}.Select((y,i) => y ?? throw {ExceptionHelper.NullExceptionMethod}($\"{{{DataMember}}}[UNKNOWN]\"))")})}};")
128+
.Append($"return {(singleGeneric.T.IsSupposedToBeNull ? AttributeValueUtilityFactory.FromNullableStringSet : AttributeValueUtilityFactory.FromStringSet)}({ParamReference}, {DataMember});")
129129
)
130130
.ToConversion(singleGeneric.T),
131131
SingleGeneric.SupportedType.Set when singleGeneric.T.IsNumeric
132132
=> signature
133133
.CreateScope(
134134
$"if ({ParamReference} is null)"
135135
.CreateScope(singleGeneric.ReturnNullOrThrow(DataMember))
136-
.Append($"return new {Constants.AWSSDK_DynamoDBv2.AttributeValue} {{ NS = new List<string>({ParamReference}.Select(y => y.ToString())) }};")
136+
.Append($"return {(singleGeneric.T.IsSupposedToBeNull ? AttributeValueUtilityFactory.FromNullableNumberSet : AttributeValueUtilityFactory.FromNumberSet)}({ParamReference}, {DataMember});")
137137
)
138138
.ToConversion(singleGeneric.T),
139139
SingleGeneric.SupportedType.Set => throw new ArgumentException("Only string and integers are supported for sets", UncoveredConversionException(singleGeneric, nameof(CreateMethod))),

src/DynamoDBGenerator.SourceGenerator/Generations/UnMarshaller.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ private static CodeFactory CreateMethod(TypeIdentifier typeIdentifier, Func<ITyp
139139
.CreateScope(
140140
$"if ({Value} is null || {Value}.NS is null)"
141141
.CreateScope(singleGeneric.ReturnNullOrThrow(DataMember))
142-
.Append($"return new {(singleGeneric.TypeSymbol.TypeKind is TypeKind.Interface ? $"HashSet<{singleGeneric.T.UnannotatedString}>" : null)}({Value}.NS.Select(y => {singleGeneric.T.UnannotatedString}.Parse(y)));")
142+
.Append($"return {AttributeValueUtilityFactory.ClassName}.{(singleGeneric.T.IsSupposedToBeNull ? $"ToNullableNumber{typeIdentifier.TypeSymbol.Name}" : $"ToNumber{typeIdentifier.TypeSymbol.Name}")}<{singleGeneric.T.UnannotatedString}>({Value}.NS, {DataMember});")
143143
)
144144
.ToConversion(singleGeneric),
145145
SingleGeneric.SupportedType.Set => throw new ArgumentException("Only string and integers are supported for sets", UncoveredConversionException(singleGeneric, nameof(CreateMethod))),

src/DynamoDBGenerator.SourceGenerator/Types/TypeIdentifier.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ protected TypeIdentifier(ITypeSymbol typeSymbol)
3131
var (annotated, original) = Representation(typeSymbol);
3232
UnannotatedString = original;
3333
AnnotatedString = annotated;
34-
IsNumeric = IsNumericMethod(typeSymbol);
3534
CanBeNull = typeSymbol is { IsReferenceType: true } or { IsValueType: true, OriginalDefinition.SpecialType: SpecialType.System_Nullable_T };
35+
IsNumeric = IsNumericMethod(typeSymbol);
3636
}
3737

3838
/// <summary>
@@ -54,7 +54,12 @@ private static (string annotated, string original) Representation(ITypeSymbol ty
5454
return RepresentationDictionary.GetOrAdd(typeSymbol, x =>
5555
{
5656
var displayString = x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
57-
return RepresentationDictionary[typeSymbol] = (ToString(typeSymbol, displayString), displayString);
57+
58+
var unAnnotated = x.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T
59+
? displayString.Substring(0, displayString.Length - 1)
60+
: displayString;
61+
62+
return RepresentationDictionary[typeSymbol] = (ToString(typeSymbol, displayString), unAnnotated);
5863
});
5964

6065
static string ToString(ITypeSymbol x, string displayString)
@@ -118,6 +123,9 @@ static string ExceptionMessage(ISymbol typeSymbol)
118123

119124
private static bool IsNumericMethod(ITypeSymbol typeSymbol)
120125
{
126+
if (typeSymbol.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
127+
typeSymbol = ((INamedTypeSymbol)typeSymbol).TypeArguments[0];
128+
121129
return typeSymbol.SpecialType
122130
is SpecialType.System_Int16
123131
or SpecialType.System_Byte
@@ -130,6 +138,7 @@ or SpecialType.System_UInt64
130138
or SpecialType.System_Decimal
131139
or SpecialType.System_Double
132140
or SpecialType.System_Single;
141+
133142
}
134143

135144
public string ReturnNullOrThrow(string dataMember)

src/DynamoDBGenerator/DynamoDBGenerator.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
<TargetFramework>net8.0</TargetFramework>
55
<Nullable>enable</Nullable>
66
<IsPackable>true</IsPackable>
7-
<LangVersion>10.0</LangVersion>
87
<PackageVersion>0.0.0</PackageVersion>
98
<Title>DynamoDBGenerator</Title>
109
<Authors>Robert Andersson</Authors>

src/DynamoDBGenerator/Internal/MarshallHelper.cs

Lines changed: 216 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using System;
2-
using System.Buffers;
32
using System.Collections.Generic;
43
using System.Diagnostics.CodeAnalysis;
54
using System.Linq;
5+
using System.Numerics;
6+
using System.Runtime.CompilerServices;
67
using Amazon.DynamoDBv2.Model;
78
using static System.Runtime.InteropServices.CollectionsMarshal;
89

@@ -22,9 +23,12 @@ public static class MarshallHelper
2223
[return: NotNullIfNotNull(nameof(dict))]
2324
public static AttributeValue? ToAttributeValue(
2425
[NotNullIfNotNull(nameof(dict))] Dictionary<string, AttributeValue>? dict
25-
) => dict is null
26-
? null
27-
: new AttributeValue { M = dict };
26+
)
27+
{
28+
return dict is null
29+
? null
30+
: new AttributeValue { M = dict };
31+
}
2832

2933
public static AttributeValue FromDictionary<T, TArgument>(
3034
IEnumerable<KeyValuePair<string, T>> dictionary,
@@ -45,6 +49,203 @@ public static AttributeValue FromDictionary<T, TArgument>(
4549
return new AttributeValue { M = elements };
4650
}
4751

52+
public static AttributeValue FromNullableNumberSet<T>(IEnumerable<T?> numbers, string? _)
53+
where T : struct, INumber<T>
54+
{
55+
if (numbers.TryGetNonEnumeratedCount(out var count) is false)
56+
return new AttributeValue
57+
{
58+
NS = numbers.Select(number => number?.ToString()).ToList()
59+
};
60+
61+
if (count is 0)
62+
return new AttributeValue { NS = [] };
63+
64+
var list = new List<string?>(count);
65+
list.AddRange(numbers.Select(number => number?.ToString()));
66+
67+
return new AttributeValue { NS = list };
68+
}
69+
70+
public static AttributeValue FromNumberSet<T>(IEnumerable<T> numbers, string? dataMember)
71+
where T : struct, INumber<T>
72+
{
73+
if (numbers.TryGetNonEnumeratedCount(out var count) is false)
74+
{
75+
var noCapacity = new List<string>();
76+
77+
foreach (var number in numbers)
78+
{
79+
var @string = number.ToString();
80+
if (string.IsNullOrEmpty(@string))
81+
throw ExceptionHelper.NotNull($"{dataMember}[UNKNOWN]");
82+
83+
noCapacity.Add(@string);
84+
}
85+
86+
return new AttributeValue { NS = noCapacity };
87+
}
88+
89+
if (count is 0)
90+
return new AttributeValue { NS = [] };
91+
92+
var list = new List<string>(count);
93+
94+
foreach (var number in numbers)
95+
{
96+
var @string = number.ToString();
97+
if (string.IsNullOrEmpty(@string))
98+
throw ExceptionHelper.NotNull($"{dataMember}[UNKNOWN]");
99+
100+
list.Add(@string);
101+
}
102+
103+
return new AttributeValue { NS = list };
104+
}
105+
106+
public static AttributeValue FromNullableStringSet(IEnumerable<string?> strings, string? _)
107+
{
108+
if (strings.TryGetNonEnumeratedCount(out var count) is false)
109+
return new AttributeValue { SS = strings.ToList() };
110+
111+
if (count is 0)
112+
return new AttributeValue { SS = [] };
113+
114+
var list = new List<string?>(count);
115+
list.AddRange(strings);
116+
117+
return new AttributeValue { SS = list };
118+
}
119+
120+
public static AttributeValue FromStringSet(IEnumerable<string> strings, string? dataMember)
121+
{
122+
if (strings.TryGetNonEnumeratedCount(out var count) is false)
123+
{
124+
var list = new List<string>();
125+
foreach (var @string in strings)
126+
{
127+
if (@string is null)
128+
throw ExceptionHelper.NotNull($"{dataMember}[UNKNOWN]");
129+
list.Add(@string);
130+
}
131+
132+
return new AttributeValue { SS = list };
133+
}
134+
else
135+
{
136+
if (count is 0)
137+
return new AttributeValue { SS = [] };
138+
139+
var list = new List<string>(count);
140+
141+
foreach (var @string in strings)
142+
list.Add(@string ?? throw ExceptionHelper.NotNull($"{dataMember}[UNKNOWN]"));
143+
144+
return new AttributeValue { SS = list };
145+
}
146+
}
147+
148+
private static TSet ToNumberSet<TNumber, TSet>(
149+
List<string> numbers,
150+
Func<int, TSet> factory,
151+
string? dataMember
152+
)
153+
where TSet : ICollection<TNumber>
154+
where TNumber : struct, INumber<TNumber>
155+
{
156+
var span = AsSpan(numbers);
157+
var set = factory(span.Length);
158+
159+
foreach (var number in span)
160+
{
161+
if (number is null)
162+
throw ExceptionHelper.NotNull($"{dataMember}[UNKNOWN]");
163+
164+
set.Add(TNumber.Parse(number, null));
165+
}
166+
167+
return set;
168+
}
169+
170+
private static TSet ToNullableNumberSet<TNumber, TSet>(
171+
List<string?> numbers,
172+
Func<int, TSet> factory,
173+
string? _
174+
)
175+
where TSet : ICollection<TNumber?>
176+
where TNumber : struct, INumber<TNumber>
177+
{
178+
var span = AsSpan(numbers);
179+
var set = factory(span.Length);
180+
181+
foreach (var number in span)
182+
{
183+
if (number is null)
184+
set.Add(null);
185+
else
186+
set.Add(TNumber.Parse(number, null));
187+
}
188+
189+
return set;
190+
}
191+
192+
public static ISet<TNumber> ToNumberISet<TNumber>(List<string> ns, string? dataMember)
193+
where TNumber : struct, INumber<TNumber>
194+
{
195+
return ToNumberSet<TNumber, HashSet<TNumber>>(ns, i => new HashSet<TNumber>(i), dataMember);
196+
}
197+
198+
public static IReadOnlySet<TNumber> ToNumberIReadOnlySet<TNumber>(List<string> ns, string? dataMember)
199+
where TNumber : struct, INumber<TNumber>
200+
{
201+
return ToNumberSet<TNumber, HashSet<TNumber>>(ns, i => new HashSet<TNumber>(i), dataMember);
202+
}
203+
204+
public static HashSet<TNumber> ToNumberHashSet<TNumber>(List<string> ns, string? dataMember)
205+
where TNumber : struct, INumber<TNumber>
206+
{
207+
return ToNumberSet<TNumber, HashSet<TNumber>>(ns, i => new HashSet<TNumber>(i), dataMember);
208+
}
209+
210+
public static SortedSet<TNumber> ToNumberSortedSet<TNumber>(List<string> ns, string? dataMember)
211+
where TNumber : struct, INumber<TNumber>
212+
{
213+
var span = AsSpan(ns);
214+
var set = new SortedSet<TNumber>();
215+
foreach (var se in span)
216+
{
217+
if (se is null)
218+
throw ExceptionHelper.NotNull($"{dataMember}[UNKNOWN]");
219+
220+
set.Add(TNumber.Parse(se, null));
221+
}
222+
223+
return set;
224+
}
225+
226+
public static ISet<TNumber?> ToNullableNumberISet<TNumber>(List<string?> ns, string? dataMember)
227+
where TNumber : struct, INumber<TNumber>
228+
{
229+
return ToNullableNumberSet<TNumber, HashSet<TNumber?>>(ns, i => new HashSet<TNumber?>(i), dataMember);
230+
}
231+
232+
public static IReadOnlySet<TNumber?> ToNullableNumberIReadOnlySet<TNumber>(List<string?> ns, string? dataMember)
233+
where TNumber : struct, INumber<TNumber>
234+
{
235+
return ToNullableNumberSet<TNumber, HashSet<TNumber?>>(ns, i => new HashSet<TNumber?>(i), dataMember);
236+
}
237+
238+
public static HashSet<TNumber?> ToNullableNumberHashSet<TNumber>(List<string?> ns, string? dataMember)
239+
where TNumber : struct, INumber<TNumber>
240+
{
241+
return ToNullableNumberSet<TNumber, HashSet<TNumber?>>(ns, i => new HashSet<TNumber?>(i), dataMember);
242+
}
243+
244+
public static SortedSet<TNumber?> ToNullableNumberSortedSet<TNumber>(List<string?> ns, string? _)
245+
where TNumber : struct, INumber<TNumber>
246+
{
247+
return new SortedSet<TNumber?>(ns.Select(x => x is null ? (TNumber?)null : TNumber.Parse(x, null)));
248+
}
48249

49250
public static ILookup<string, T> ToLookup<T, TArgument>(
50251
Dictionary<string, AttributeValue> dictionary,
@@ -134,15 +335,20 @@ public static AttributeValue FromEnumerable<T, TArgument>(
134335
string? dataMember,
135336
Func<T, TArgument, string?, AttributeValue> resultSelector)
136337
{
137-
var attributeValues = enumerable.TryGetNonEnumeratedCount(out var count)
138-
? new List<AttributeValue>(count)
139-
: new List<AttributeValue>();
338+
if (enumerable.TryGetNonEnumeratedCount(out var count) is false)
339+
return new AttributeValue
340+
{
341+
L = [..enumerable.Select((element, i) => resultSelector(element, argument, $"{dataMember}[{i}]"))]
342+
};
140343

344+
if (count == 0)
345+
return new AttributeValue { L = [] };
346+
347+
var list = new List<AttributeValue>(count);
141348
foreach (var (element, i) in enumerable.Select((x, y) => (x, y)))
142-
attributeValues.Add(resultSelector(element, argument, $"{dataMember}[{i}]"));
349+
list.Add(resultSelector(element, argument, $"{dataMember}[{i}]"));
143350

144-
145-
return new AttributeValue { L = attributeValues };
351+
return new AttributeValue { L = list };
146352
}
147353

148354
public static List<TResult> ToList<TResult, TArgument>(

tests/DynamoDBGenerator.SourceGenerator.Tests/DynamoDBDocumentTests/Marshaller/Asserters/MarshalAsserter.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ public void Marshall_IsEquivalentTo_UnmarshallResult()
3333
public void UnMarshall_IsEquivalentTo_MarshallResult()
3434
{
3535
Arguments().Should().AllSatisfy(x =>
36-
MarshallImplementation(UnmarshallImplementation(x.attributeValues)).Should()
37-
.BeEquivalentTo(x.attributeValues));
36+
{
37+
var unmarshallImplementation = UnmarshallImplementation(x.attributeValues);
38+
var marshallImplementation = MarshallImplementation(unmarshallImplementation);
39+
marshallImplementation.Should().BeEquivalentTo(x.attributeValues);
40+
});
3841
}
3942

4043
[Fact]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Amazon.DynamoDBv2.Model;
2+
using DynamoDBGenerator.Attributes;
3+
using DynamoDBGenerator.SourceGenerator.Tests.DynamoDBDocumentTests.Marshaller.Asserters;
4+
using DynamoDBGenerator.SourceGenerator.Tests.DynamoDBDocumentTests.Marshaller.Generics.Sets.Asserters;
5+
6+
namespace DynamoDBGenerator.SourceGenerator.Tests.DynamoDBDocumentTests.Marshaller.Generics.Sets;
7+
8+
[DynamoDBMarshaller(EntityType = typeof(Container<HashSet<decimal>>))]
9+
public partial class DecimalHashSetTests()
10+
: SetAsserter<HashSet<decimal>, decimal>([2032m, 0.323232932m, 0.9329392m], x => new HashSet<decimal>(x))
11+
{
12+
protected override Dictionary<string, AttributeValue> MarshallImplementation(Container<HashSet<decimal>> element)
13+
{
14+
return ContainerMarshaller.Marshall(element);
15+
}
16+
17+
protected override Container<HashSet<decimal>> UnmarshallImplementation(
18+
Dictionary<string, AttributeValue> attributeValues)
19+
{
20+
return ContainerMarshaller.Unmarshall(attributeValues);
21+
}
22+
}

0 commit comments

Comments
 (0)