-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Add typed DynamoDB query, write, and batch APIs with auto-pagination #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
da41504
3abb032
5d209e5
fff0845
ed0e82c
8c5e202
91df8dd
4ae7895
8f9bb98
66ec988
7490569
faa2e11
6708b7b
b989a0a
1098d84
5c59b2f
7f3247e
32e1b93
ae94472
23a29cd
d44fc23
a7434af
b042edf
5d69e69
e4237fb
1f00580
868730b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| using Microsoft.CodeAnalysis; | ||
| using Goa.Clients.Dynamo.Generator.Models; | ||
|
|
||
| namespace Goa.Clients.Dynamo.Generator.Attributes; | ||
|
|
||
| /// <summary> | ||
| /// Handles the DynamoConverterAttribute. | ||
| /// </summary> | ||
| public class DynamoConverterAttributeHandler : IAttributeHandler | ||
| { | ||
| public string AttributeTypeName => "Goa.Clients.Dynamo.DynamoConverterAttribute"; | ||
|
|
||
| public bool CanHandle(AttributeData attributeData) | ||
| { | ||
| return attributeData.AttributeClass?.ToDisplayString() == AttributeTypeName; | ||
| } | ||
|
|
||
| public AttributeInfo? ParseAttribute(AttributeData attributeData) | ||
| { | ||
| if (!CanHandle(attributeData)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| // Extract converter type from constructor arg | ||
| if (attributeData.ConstructorArguments.Length > 0 && | ||
| attributeData.ConstructorArguments[0].Value is INamedTypeSymbol converterType) | ||
| { | ||
| return new DynamoConverterAttributeInfo | ||
| { | ||
| AttributeData = attributeData, | ||
| AttributeTypeName = AttributeTypeName, | ||
| ConverterTypeName = converterType.ToDisplayString() | ||
| }; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| public void ValidateAttribute(AttributeInfo attributeInfo, ISymbol symbol, Action<Diagnostic> reportDiagnostic) | ||
| { | ||
| // No additional validation needed at this stage | ||
| } | ||
| } | ||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,83 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using Goa.Clients.Dynamo.Generator.Models; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace Goa.Clients.Dynamo.Generator.CodeGeneration; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Generates a static registration class for all concrete DynamoDB model types. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Consumers call <c>DynamoReaderRegistration.Initialize()</c> once at startup | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// to trigger the static constructor, which registers all types with DynamoItemReaderRegistry. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class ReaderRegistrationGenerator : ICodeGenerator | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public string GenerateCode(IEnumerable<DynamoTypeInfo> types, GenerationContext context) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var registrableTypes = types.Where(ShouldRegister).ToList(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!registrableTypes.Any()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return string.Empty; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var typesByNamespace = registrableTypes.GroupBy(t => t.Namespace).Where(x => x.Any()).ToList(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!typesByNamespace.Any()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return string.Empty; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Use the first namespace as the containing namespace for the registration class | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var targetNamespace = typesByNamespace.First().Key; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (string.IsNullOrEmpty(targetNamespace)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetNamespace = "Generated"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var builder = new CodeBuilder(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AppendLine("// <auto-generated/>"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AppendLine("#nullable enable"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AppendLine("using Goa.Clients.Dynamo;"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AppendLine(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AppendLine($"namespace {targetNamespace}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.OpenBrace(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.OpenBraceWithLine("internal static class DynamoReaderRegistration"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.OpenBraceWithLine("static DynamoReaderRegistration()"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var type in registrableTypes) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var normalizedName = NamingHelpers.NormalizeTypeName(type.Name); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AppendLine($"DynamoItemReaderRegistry.Register<{type.FullName}>(DynamoJsonMapper.{normalizedName}.ReadFromJson);"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AppendLine($"DynamoItemWriterRegistry.Register<{type.FullName}>(DynamoJsonMapper.{normalizedName}.WriteToJson);"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+24
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fully qualify mapper references before registering cross-namespace models. This generator emits one registration class in the first namespace, but 🛠️ Suggested direction foreach (var type in registrableTypes)
{
var normalizedName = NamingHelpers.NormalizeTypeName(type.Name);
- builder.AppendLine($"DynamoItemReaderRegistry.Register<{type.FullName}>(DynamoJsonMapper.{normalizedName}.ReadFromJson);");
- builder.AppendLine($"DynamoItemWriterRegistry.Register<{type.FullName}>(DynamoJsonMapper.{normalizedName}.WriteToJson);");
+ var mapperNamespace = string.IsNullOrEmpty(type.Namespace) ? "Generated" : type.Namespace;
+ builder.AppendLine($"DynamoItemReaderRegistry.Register<{type.FullName}>(global::{mapperNamespace}.DynamoJsonMapper.{normalizedName}.ReadFromJson);");
+ builder.AppendLine($"DynamoItemWriterRegistry.Register<{type.FullName}>(global::{mapperNamespace}.DynamoJsonMapper.{normalizedName}.WriteToJson);");
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.CloseBrace(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AppendLine(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AppendLine("/// <summary>"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AppendLine("/// Ensures all DynamoDB model readers are registered. Call once during application startup."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AppendLine("/// </summary>"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.AppendLine("public static void Initialize() { }"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.CloseBrace(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| builder.CloseBrace(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return builder.ToString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private bool ShouldRegister(DynamoTypeInfo type) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (type.IsAbstract) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return type.Attributes.Any(a => a is DynamoModelAttributeInfo) || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HasInheritedDynamoModel(type); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private bool HasInheritedDynamoModel(DynamoTypeInfo type) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var current = type.BaseType; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (current != null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (current.Attributes.Any(a => a is DynamoModelAttributeInfo)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current = current.BaseType; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| namespace Goa.Clients.Dynamo.Generator.Models; | ||
|
|
||
| /// <summary> | ||
| /// Represents DynamoConverter attribute information. | ||
| /// </summary> | ||
| public class DynamoConverterAttributeInfo : AttributeInfo | ||
| { | ||
| public string ConverterTypeName { get; set; } = string.Empty; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| namespace Goa.Clients.Dynamo; | ||
|
|
||
| /// <summary> | ||
| /// Registry for DynamoItemReader delegates, populated by source generators. | ||
| /// </summary> | ||
| public static class DynamoItemReaderRegistry | ||
| { | ||
| /// <summary> | ||
| /// Registers a reader delegate for the specified type. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type the reader deserializes.</typeparam> | ||
| /// <param name="reader">The reader delegate.</param> | ||
| public static void Register<T>(DynamoItemReader<T> reader) => Cache<T>.Reader = reader; | ||
|
|
||
| /// <summary> | ||
| /// Gets the registered reader delegate for the specified type. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type to get the reader for.</typeparam> | ||
| /// <returns>The registered reader delegate.</returns> | ||
| /// <exception cref="InvalidOperationException">Thrown when no reader is registered for the type.</exception> | ||
| public static DynamoItemReader<T> Get<T>() => Cache<T>.Reader | ||
| ?? throw new InvalidOperationException($"No DynamoItemReader registered for {typeof(T).Name}. Ensure the type has [DynamoModel] attribute and the source generator has run."); | ||
|
|
||
| private static class Cache<T> | ||
| { | ||
| public static volatile DynamoItemReader<T>? Reader; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| namespace Goa.Clients.Dynamo; | ||
|
|
||
| /// <summary> | ||
| /// Registry for DynamoItemWriter delegates, populated by source generators. | ||
| /// </summary> | ||
| public static class DynamoItemWriterRegistry | ||
| { | ||
| /// <summary> | ||
| /// Registers a writer delegate for the specified type. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type the writer serializes.</typeparam> | ||
| /// <param name="writer">The writer delegate.</param> | ||
| public static void Register<T>(DynamoItemWriter<T> writer) => Cache<T>.Writer = writer; | ||
|
|
||
| /// <summary> | ||
| /// Gets the registered writer delegate for the specified type. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type to get the writer for.</typeparam> | ||
| /// <returns>The registered writer delegate.</returns> | ||
| /// <exception cref="InvalidOperationException">Thrown when no writer is registered for the type.</exception> | ||
| public static DynamoItemWriter<T> Get<T>() => Cache<T>.Writer | ||
| ?? throw new InvalidOperationException($"No DynamoItemWriter registered for {typeof(T).Name}. Ensure the type has [DynamoModel] attribute and the source generator has run."); | ||
|
|
||
| private static class Cache<T> | ||
| { | ||
| public static volatile DynamoItemWriter<T>? Writer; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate converter shape before codegen.
JsonMapperGeneratorlater emitsnew {converter}.Write(...)andnew {converter}.Read(...). WithValidateAttributeempty, a converter missing the expected API or a public parameterless ctor only shows up as broken generated code instead of a diagnostic on the annotated symbol.