diff --git a/DynamoDBGenerator.sln b/DynamoDBGenerator.sln
index eb6a406f..563f44c6 100644
--- a/DynamoDBGenerator.sln
+++ b/DynamoDBGenerator.sln
@@ -2,7 +2,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamoDBGenerator.SourceGenerator", "src\DynamoDBGenerator.SourceGenerator\DynamoDBGenerator.SourceGenerator.csproj", "{648B1DF4-9684-4422-95F5-74BB89862E4D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp", "samples\ConsoleApp\ConsoleApp.csproj", "{834A7F6C-3C82-427F-9BFB-9686672A5BDE}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynatelloRepository", "samples\DynatelloRepository\DynatelloRepository.csproj", "{834A7F6C-3C82-427F-9BFB-9686672A5BDE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamoDBGenerator", "src\DynamoDBGenerator\DynamoDBGenerator.csproj", "{53F899A8-28AA-450F-9C62-FD478119B2B7}"
EndProject
@@ -16,6 +16,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{CF34
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{11F1D954-39EC-4EDD-9460-04FCC216E97A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dynatello.Tests", "tests\Dynatello.Tests\Dynatello.Tests.csproj", "{D9C9C74E-B52C-4510-9258-BA78532EAABB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dynatello", "src\Dynatello\Dynatello.csproj", "{E4D47C8F-A0C8-4368-BBE0-DC6045B2D734}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -42,6 +46,14 @@ Global
{A80AC940-3BD8-4377-BDB9-AE82FD4DF944}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A80AC940-3BD8-4377-BDB9-AE82FD4DF944}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A80AC940-3BD8-4377-BDB9-AE82FD4DF944}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D9C9C74E-B52C-4510-9258-BA78532EAABB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D9C9C74E-B52C-4510-9258-BA78532EAABB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D9C9C74E-B52C-4510-9258-BA78532EAABB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D9C9C74E-B52C-4510-9258-BA78532EAABB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E4D47C8F-A0C8-4368-BBE0-DC6045B2D734}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E4D47C8F-A0C8-4368-BBE0-DC6045B2D734}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E4D47C8F-A0C8-4368-BBE0-DC6045B2D734}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E4D47C8F-A0C8-4368-BBE0-DC6045B2D734}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D8EABA41-D014-49BD-B109-54829DB835E7} = {E84C4630-5241-4FAA-8F86-964AB25A2C6F}
@@ -49,5 +61,7 @@ Global
{834A7F6C-3C82-427F-9BFB-9686672A5BDE} = {CF34190F-AC01-42FC-B28A-DD820442243A}
{53F899A8-28AA-450F-9C62-FD478119B2B7} = {11F1D954-39EC-4EDD-9460-04FCC216E97A}
{648B1DF4-9684-4422-95F5-74BB89862E4D} = {11F1D954-39EC-4EDD-9460-04FCC216E97A}
+ {D9C9C74E-B52C-4510-9258-BA78532EAABB} = {E84C4630-5241-4FAA-8F86-964AB25A2C6F}
+ {E4D47C8F-A0C8-4368-BBE0-DC6045B2D734} = {11F1D954-39EC-4EDD-9460-04FCC216E97A}
EndGlobalSection
EndGlobal
diff --git a/README.md b/README.md
index 26de8862..cb7fe38f 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,15 @@ This source generator is crafted to simplify DynamoDB integration for your proje
This project has not been tested in any real scenario and currently serves as a hobby project.
## Installation
-Install the following packages from Nuget:
+Theres two way to install this project either by the using `Dynatello` or by `DynamoDBGenerator.SourceGenerator` & `DynamoDBGenerator`.
+
+* `DynamoDBGenerator.SourceGenerator` & `DynamoDBGenerator` is the base functionality where you get reusable marshaller that's source generated.
+* Dyntello extends the marshaller in order to create reusable generic request builders. See this [example](https://github.com/inputfalken/DynamoDB.SourceGenerator/blob/main/samples/DynatelloRepository/Program.cs).
+ * NOTE: If you install Dynatello, you do not need to specify `DynamoDBGenerator.SourceGenerator` & `DynamoDBGenerator` as dependencies.
+
+[![DynamoDBGenerator][5]][6]
+
+---
[![DynamoDBGenerator][1]][2]
@@ -16,6 +24,8 @@ Install the following packages from Nuget:
[2]: https://www.nuget.org/packages/DynamoDBGenerator
[3]: https://img.shields.io/nuget/v/DynamoDBGenerator.SourceGenerator.svg?label=DynamoDBGenerator.SourceGenerator
[4]: https://www.nuget.org/packages/DynamoDBGenerator.SourceGenerator
+[5]: https://img.shields.io/nuget/v/Dynatello.svg?label=Dynatello
+[6]: https://www.nuget.org/packages/Dynatello
The `DynamoDBGenerator.SourceGenerator` is where the source generator is implemented.
The source generator will look for attributes and implement interfaces that exists in `DynamoDBGenerator`.
diff --git a/samples/ConsoleApp/Program.cs b/samples/ConsoleApp/Program.cs
deleted file mode 100644
index 5ad2c947..00000000
--- a/samples/ConsoleApp/Program.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using DynamoDBGenerator.Attributes;
-
-namespace SampleApp;
-
-internal static class Program
-{
- public static void Main()
- {
- }
-
-}
\ No newline at end of file
diff --git a/samples/ConsoleApp/ConsoleApp.csproj b/samples/DynatelloRepository/DynatelloRepository.csproj
similarity index 54%
rename from samples/ConsoleApp/ConsoleApp.csproj
rename to samples/DynatelloRepository/DynatelloRepository.csproj
index d7a49aaa..ed9cca51 100644
--- a/samples/ConsoleApp/ConsoleApp.csproj
+++ b/samples/DynatelloRepository/DynatelloRepository.csproj
@@ -9,8 +9,7 @@
-
-
+
diff --git a/samples/DynatelloRepository/Program.cs b/samples/DynatelloRepository/Program.cs
new file mode 100644
index 00000000..4644d670
--- /dev/null
+++ b/samples/DynatelloRepository/Program.cs
@@ -0,0 +1,114 @@
+using System.Net;
+using Amazon.DynamoDBv2;
+using Amazon.DynamoDBv2.DataModel;
+using Amazon.DynamoDBv2.Model;
+using DynamoDBGenerator.Attributes;
+using Dynatello;
+using Dynatello.Builders;
+using Dynatello.Builders.Types;
+
+ProductRepository productRepository = new ProductRepository("MY_TABLE", new AmazonDynamoDBClient());
+
+public class ProductRepository
+{
+ private readonly IAmazonDynamoDB _amazonDynamoDb;
+ private readonly GetRequestBuilder _getProductByTable;
+ private readonly UpdateRequestBuilder<(string Id, decimal NewPrice, DateTime TimeStamp)> _updatePrice;
+ private readonly PutRequestBuilder _createProduct;
+ private readonly QueryRequestBuilder _queryByPrice;
+
+ public ProductRepository(string tableName, IAmazonDynamoDB amazonDynamoDb)
+ {
+ _amazonDynamoDb = amazonDynamoDb;
+
+ _getProductByTable = Product.GetById
+ .OnTable(tableName)
+ .ToGetRequestBuilder(arg => arg); // Since the ArgumentType is set to string, we don't need to select a property.
+
+ _updatePrice = Product.UpdatePrice
+ .OnTable(tableName)
+ .WithUpdateExpression((db, arg) => $"SET {db.Price} = {arg.NewPrice}, {db.Metadata.ModifiedAt} = {arg.TimeStamp}") // Specify the update operation
+ .ToUpdateItemRequestBuilder((marshaller, arg) => marshaller.PartitionKey(arg.Id));
+
+ _createProduct = Product.Put
+ .OnTable(tableName)
+ .WithConditionExpression((db, arg) => $"{db.Id} <> {arg.Id}") // Ensure we don't have an existing Product in DynamoDB
+ .ToPutRequestBuilder();
+
+ _queryByPrice = Product.QueryByPrice
+ .OnTable(tableName)
+ .WithKeyConditionExpression((db, arg) => $"{db.Price} = {arg}")
+ .ToQueryRequestBuilder()
+ with
+ {
+ IndexName = Product.PriceIndex
+ };
+ }
+
+ public async Task> SearchByPrice(decimal price)
+ {
+ QueryRequest request = _queryByPrice.Build(price);
+ QueryResponse? response = await _amazonDynamoDb.QueryAsync(request);
+
+ if (response.HttpStatusCode is not HttpStatusCode.OK)
+ throw new Exception("...");
+
+ return response.Items
+ .Select(x => Product.QueryByPrice.Unmarshall(x))
+ .ToArray();
+ }
+
+ public async Task Create(Product product)
+ {
+ PutItemRequest request = _createProduct.Build(product);
+ PutItemResponse response = await _amazonDynamoDb.PutItemAsync(request);
+
+ if (response.HttpStatusCode is not HttpStatusCode.OK)
+ throw new Exception("...");
+ }
+
+ public async Task GetById(string id)
+ {
+ GetItemRequest request = _getProductByTable.Build(id);
+ GetItemResponse response = await _amazonDynamoDb.GetItemAsync(request);
+
+ if (response.HttpStatusCode is HttpStatusCode.NotFound)
+ return null;
+
+ if (response.HttpStatusCode is not HttpStatusCode.OK)
+ throw new Exception("...");
+
+ Product product = Product.GetById.Unmarshall(response.Item);
+
+ return product;
+ }
+
+ public async Task UpdatePrice(string id, decimal price)
+ {
+ UpdateItemRequest request = _updatePrice.Build((id, price, DateTime.UtcNow));
+ UpdateItemResponse response = await _amazonDynamoDb.UpdateItemAsync(request);
+
+ if (response.HttpStatusCode is not HttpStatusCode.OK)
+ return null;
+
+ Product product = Product.UpdatePrice.Unmarshall(response.Attributes);
+
+ return product;
+ }
+}
+
+[DynamoDBMarshaller(typeof(Product), PropertyName = "Put")]
+[DynamoDBMarshaller(typeof(Product), ArgumentType = typeof(string), PropertyName = "GetById")]
+[DynamoDBMarshaller(typeof(Product), ArgumentType = typeof((string Id, decimal NewPrice, DateTime TimeStamp)), PropertyName = "UpdatePrice")]
+[DynamoDBMarshaller(typeof(Product), ArgumentType = typeof(decimal), PropertyName = "QueryByPrice")]
+public partial record Product(
+ [property: DynamoDBHashKey, DynamoDBGlobalSecondaryIndexRangeKey(Product.PriceIndex)] string Id,
+ [property: DynamoDBGlobalSecondaryIndexHashKey(Product.PriceIndex)] decimal Price,
+ string Description,
+ Product.MetadataEntity Metadata
+)
+{
+ public const string PriceIndex = "PriceIndex";
+
+ public record MetadataEntity(DateTime CreatedAt, DateTime ModifiedAt);
+}
diff --git a/src/DynamoDBGenerator/Extensions/DynamoDBMarshallerExtensions.cs b/src/DynamoDBGenerator/Extensions/DynamoDBMarshallerExtensions.cs
index 2a46234f..dd27ea6d 100644
--- a/src/DynamoDBGenerator/Extensions/DynamoDBMarshallerExtensions.cs
+++ b/src/DynamoDBGenerator/Extensions/DynamoDBMarshallerExtensions.cs
@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using Amazon.DynamoDBv2;
-using Amazon.DynamoDBv2.Model;
using DynamoDBGenerator.Internal;
+
namespace DynamoDBGenerator.Extensions;
///
@@ -74,86 +72,4 @@ params Func[] expressionBuilders
expressionBuilders
);
}
-
- ///
- /// Converts the into an .
- ///
- public static IDynamoDBClient ToDynamoDBClient(
- this IDynamoDBMarshaller item,
- string tableName,
- IAmazonDynamoDB dynamoDB
- )
- where TReferences : IAttributeExpressionNameTracker
- where TArgumentReferences : IAttributeExpressionValueTracker
- {
- return new DynamoDBClient(item, tableName, dynamoDB);
- }
-
- ///
- /// Creates a .
- ///
- public static PutItemRequest ToPutItemRequest(
- this IDynamoDBMarshaller item,
- T entity,
- ReturnValue returnValue,
- string tableName
- )
- where TReferences : IAttributeExpressionNameTracker
- where TArgumentReferences : IAttributeExpressionValueTracker
- where T : TArg
- {
- return item.ToPutItemRequestInternal(entity, entity, null, returnValue, tableName);
- }
-
- ///
- /// Creates a with condition expression.
- ///
- public static PutItemRequest ToPutItemRequest(
- this IDynamoDBMarshaller item,
- T entity,
- Func conditionExpressionBuilder,
- ReturnValue returnValue,
- string tableName
- )
- where TReferences : IAttributeExpressionNameTracker
- where TArgumentReferences : IAttributeExpressionValueTracker
- where T : TArg
- {
- return item.ToPutItemRequestInternal(entity, entity, conditionExpressionBuilder, returnValue, tableName);
- }
-
- ///
- /// Creates a .
- ///
- public static UpdateItemRequest ToUpdateItemRequest(
- this IDynamoDBMarshaller item,
- TArg argument,
- Func> keySelector,
- Func updateExpressionBuilder,
- ReturnValue returnValue,
- string tableName
- )
- where TReferences : IAttributeExpressionNameTracker
- where TArgumentReferences : IAttributeExpressionValueTracker
- {
- return item.ToUpdateItemRequestInternal(argument, keySelector, updateExpressionBuilder, null, returnValue, tableName);
- }
-
- ///
- /// Creates a with a condition expression.
- ///
- public static UpdateItemRequest ToUpdateItemRequest(
- this IDynamoDBMarshaller item,
- TArg argument,
- Func> keySelector,
- Func updateExpressionBuilder,
- Func conditionExpressionBuilder,
- ReturnValue returnValue,
- string tableName
- )
- where TReferences : IAttributeExpressionNameTracker
- where TArgumentReferences : IAttributeExpressionValueTracker
- {
- return item.ToUpdateItemRequestInternal(argument, keySelector, updateExpressionBuilder, conditionExpressionBuilder, returnValue, tableName);
- }
}
\ No newline at end of file
diff --git a/src/DynamoDBGenerator/IDynamoDBClient.cs b/src/DynamoDBGenerator/IDynamoDBClient.cs
deleted file mode 100644
index d72b8530..00000000
--- a/src/DynamoDBGenerator/IDynamoDBClient.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using Amazon.DynamoDBv2.Model;
-namespace DynamoDBGenerator;
-
-///
-/// Represents a client with asynchronous methods for sending requests to DynamoDB.
-///
-/// The type of entity associated with DynamoDB operations.
-/// The type of argument used in DynamoDB operations.
-/// The type for tracking attribute names related to .
-/// The type for tracking argument attribute values related to .
-public interface IDynamoDBClient
- where TReferences : IAttributeExpressionNameTracker
- where TArgumentReferences : IAttributeExpressionValueTracker
-{
- ///
- /// Saves an entity to DynamoDB with an optional condition expression builder.
- ///
- /// The type of entity to save.
- /// The entity to be saved.
- /// A function to build a condition expression.
- /// A token to cancel the operation.
- /// A Task representing the asynchronous operation.
- Task Save(
- T entity,
- Func conditionExpressionBuilder,
- CancellationToken cancellationToken = default
- ) where T : TEntity, TArgument;
-
- ///
- /// Saves an entity to DynamoDB without a condition expression.
- ///
- /// The type of entity to save.
- /// The entity to be saved.
- /// A token to cancel the operation.
- /// A Task representing the asynchronous operation.
- Task Save(
- T entity,
- CancellationToken cancellationToken = default
- ) where T : TEntity, TArgument;
-
- ///
- /// Updates an entity in DynamoDB.
- ///
- /// The entity to update.
- /// A function to select the keys for the update operation.
- /// A function to build the update expression.
- /// A token to cancel the operation.
- /// A Task representing the asynchronous operation.
- Task Update(
- TArgument entity,
- Func> keySelector,
- Func updateExpressionBuilder,
- CancellationToken cancellationToken = default
- );
-
- ///
- /// Updates an entity in DynamoDB with optional condition and update expression builders.
- ///
- /// The entity to update.
- /// A function to select the keys for the update operation.
- /// A function to build the update expression.
- /// A function to build the condition expression.
- /// A token to cancel the operation.
- /// A Task representing the asynchronous operation.
- Task Update(
- TArgument entity,
- Func> keySelector,
- Func updateExpressionBuilder,
- Func conditionExpressionBuilder,
- CancellationToken cancellationToken = default
- );
-
- ///
- /// Updates an entity in DynamoDB and returns the updated entity.
- ///
- /// The entity to update.
- /// A function to select the keys for the update operation.
- /// A function to build the update expression.
- /// A function to build the condition expression.
- /// A token to cancel the operation.
- /// The updated entity.
- Task UpdateReturned(
- TArgument entity,
- Func> keySelector,
- Func updateExpressionBuilder,
- Func conditionExpressionBuilder,
- CancellationToken cancellationToken = default
- );
-}
\ No newline at end of file
diff --git a/src/DynamoDBGenerator/Internal/DynamoDBClient.cs b/src/DynamoDBGenerator/Internal/DynamoDBClient.cs
deleted file mode 100644
index 66ebb591..00000000
--- a/src/DynamoDBGenerator/Internal/DynamoDBClient.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using Amazon.DynamoDBv2;
-using Amazon.DynamoDBv2.Model;
-namespace DynamoDBGenerator.Internal;
-
-internal class DynamoDBClient : IDynamoDBClient
- where TReferences : IAttributeExpressionNameTracker
- where TArgumentReferences : IAttributeExpressionValueTracker
-{
- private readonly IAmazonDynamoDB _amazonDynamoDB;
- private readonly IDynamoDBMarshaller _marshaller;
- private readonly string _tableName;
-
- public DynamoDBClient(IDynamoDBMarshaller marshaller, string tableName, IAmazonDynamoDB amazonDynamoDB)
- {
- _marshaller = marshaller;
- _tableName = tableName;
- _amazonDynamoDB = amazonDynamoDB;
- }
-
- public Task Save(T1 entity, Func conditionExpressionBuilder, CancellationToken cancellationToken = default) where T1 : T, TArg
- {
- var putRequest = _marshaller.ToPutItemRequestInternal(entity, entity, conditionExpressionBuilder, ReturnValue.NONE, _tableName);
- return _amazonDynamoDB.PutItemAsync(putRequest, cancellationToken);
- }
-
- public Task Save(T1 entity, CancellationToken cancellationToken = default) where T1 : T, TArg
- {
- var putRequest = _marshaller.ToPutItemRequestInternal(entity, entity, null, ReturnValue.NONE, _tableName);
- return _amazonDynamoDB.PutItemAsync(putRequest, cancellationToken);
- }
-
- public Task Update(
- TArg entity,
- Func> keySelector,
- Func updateExpressionBuilder,
- CancellationToken cancellationToken = default
- )
- {
- var updateItemRequest = _marshaller.ToUpdateItemRequestInternal(entity, keySelector, updateExpressionBuilder, null, ReturnValue.NONE, _tableName);
- return _amazonDynamoDB.UpdateItemAsync(updateItemRequest, cancellationToken);
- }
-
- public Task Update(
- TArg entity,
- Func> keySelector,
- Func updateExpressionBuilder,
- Func conditionExpressionBuilder,
- CancellationToken cancellationToken = default
- )
- {
- var updateItemRequest = _marshaller.ToUpdateItemRequestInternal(entity, keySelector, updateExpressionBuilder, conditionExpressionBuilder, ReturnValue.NONE, _tableName);
- return _amazonDynamoDB.UpdateItemAsync(updateItemRequest, cancellationToken);
- }
-
- public async Task UpdateReturned(TArg entity, Func> keySelector, Func updateExpressionBuilder,
- Func conditionExpressionBuilder, CancellationToken cancellationToken = default)
- {
- var updateItemRequest = _marshaller.ToUpdateItemRequestInternal(entity, keySelector, updateExpressionBuilder, conditionExpressionBuilder, ReturnValue.ALL_NEW, _tableName);
- var result = await _amazonDynamoDB.UpdateItemAsync(updateItemRequest, cancellationToken);
-
- return _marshaller.Unmarshall(result.Attributes);
- }
-}
\ No newline at end of file
diff --git a/src/DynamoDBGenerator/Internal/ExceptionHelper.cs b/src/DynamoDBGenerator/Internal/ExceptionHelper.cs
index 08bb9055..0845f7fe 100644
--- a/src/DynamoDBGenerator/Internal/ExceptionHelper.cs
+++ b/src/DynamoDBGenerator/Internal/ExceptionHelper.cs
@@ -23,7 +23,7 @@ public static DynamoDBMarshallingException KeysArgumentNotNull(string memberName
public static DynamoDBMarshallingException KeysInvalidConversion(string memberName, string argumentName, object value, string expectedType)
{
- return new DynamoDBMarshallingException(memberName, $"Value '{{{value}}}' from argument '{{nameof({argumentName})}}' is not convertable to '{expectedType}'.");
+ return new DynamoDBMarshallingException(memberName, $"Value '{{{value}}}' from argument '{argumentName}' is not convertable to '{expectedType}'.");
}
public static InvalidOperationException KeysValueWithNoCorrespondence(string argumentName, object value)
diff --git a/src/DynamoDBGenerator/Internal/RequestFactory.cs b/src/DynamoDBGenerator/Internal/RequestFactory.cs
deleted file mode 100644
index 32eb2ef7..00000000
--- a/src/DynamoDBGenerator/Internal/RequestFactory.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Amazon.DynamoDBv2;
-using Amazon.DynamoDBv2.Model;
-namespace DynamoDBGenerator.Internal;
-
-internal static class RequestFactory
-{
- internal static PutItemRequest ToPutItemRequestInternal(
- this IDynamoDBMarshaller item,
- T entity,
- TArg argument,
- Func? conditionExpressionBuilder,
- ReturnValue returnValue,
- string tableName
- )
- where TReferences : IAttributeExpressionNameTracker
- where TArgumentReferences : IAttributeExpressionValueTracker
- {
-
- Dictionary? expressionAttributeValues = null;
- Dictionary? expressionAttributeNames = null;
- string? conditionExpression = null;
-
- if (conditionExpressionBuilder is not null)
- {
- var nameTracker = item.AttributeExpressionNameTracker();
- var valueTracker = item.AttributeExpressionValueTracker();
- conditionExpression = conditionExpressionBuilder.Invoke(nameTracker, valueTracker);
- expressionAttributeNames = nameTracker.AccessedNames().ToDictionary(x => x.Key, x => x.Value);
- expressionAttributeValues = valueTracker.AccessedValues(argument).ToDictionary(x => x.Key, x => x.Value);
- }
-
- return new PutItemRequest
- {
- TableName = tableName,
- ExpressionAttributeNames = expressionAttributeNames,
- ExpressionAttributeValues = expressionAttributeValues,
- ConditionExpression = conditionExpression,
- Item = item.Marshall(entity),
- ReturnValues = returnValue
- };
- }
- internal static UpdateItemRequest ToUpdateItemRequestInternal(
- this IDynamoDBMarshaller item,
- TArg argument,
- Func> keySelector,
- Func updateExpressionBuilder,
- Func? conditionExpressionBuilder,
- ReturnValue returnValue,
- string tableName
- )
- where TReferences : IAttributeExpressionNameTracker
- where TArgumentReferences : IAttributeExpressionValueTracker
- {
-
- var nameTracker = item.AttributeExpressionNameTracker();
- var argumentTracker = item.AttributeExpressionValueTracker();
- var updateExpression = updateExpressionBuilder(nameTracker, argumentTracker);
- var conditionExpression = conditionExpressionBuilder?.Invoke(nameTracker, argumentTracker);
-
- return new UpdateItemRequest
- {
- Key = keySelector(item.PrimaryKeyMarshaller, argument),
- TableName = tableName,
- ExpressionAttributeNames = nameTracker.AccessedNames().ToDictionary(x => x.Key, x => x.Value),
- ExpressionAttributeValues = argumentTracker.AccessedValues(argument).ToDictionary(x => x.Key, x => x.Value),
- ConditionExpression = conditionExpression,
- UpdateExpression = updateExpression,
- ReturnValues = returnValue
- };
- }
-}
\ No newline at end of file
diff --git a/src/Dynatello/Builders/Extensions.cs b/src/Dynatello/Builders/Extensions.cs
new file mode 100644
index 00000000..c296fc54
--- /dev/null
+++ b/src/Dynatello/Builders/Extensions.cs
@@ -0,0 +1,145 @@
+using Amazon.DynamoDBv2.Model;
+using DynamoDBGenerator;
+using Dynatello.Builders.Types;
+
+namespace Dynatello.Builders;
+
+public static class Extensions
+{
+ public static QueryRequestBuilder ToQueryRequestBuilder(
+ this KeyConditionedFilterExpression source
+ )
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new QueryRequestBuilder(
+ source.TableAccess.Item.ComposeAttributeExpression(source.Condition, source.Filter),
+ source.TableAccess.TableName
+ );
+ }
+
+ public static QueryRequestBuilder ToQueryRequestBuilder(
+ this KeyConditionExpression source
+ )
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new QueryRequestBuilder(
+ source.TableAccess.Item.ComposeAttributeExpression(source.Condition, null),
+ source.TableAccess.TableName
+ );
+ }
+
+ public static GetRequestBuilder ToGetRequestBuilder(
+ this TableAccess source)
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ where TArg : notnull
+ {
+ return new GetRequestBuilder(
+ source.TableName,
+ source.Item.PrimaryKeyMarshaller.ComposeKeys(y => y, null)
+ );
+ }
+
+ public static GetRequestBuilder ToGetRequestBuilder(
+ this TableAccess source,
+ Func partitionKeySelector)
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ where TPartition : notnull
+ {
+ return new GetRequestBuilder(
+ source.TableName,
+ source.Item.PrimaryKeyMarshaller.ComposeKeys(y => partitionKeySelector(y), null)
+ );
+ }
+
+ public static GetRequestBuilder ToGetRequestBuilder(
+ this TableAccess source,
+ Func partitionKeySelector,
+ Func rangeKeySelector)
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ where TPartition : notnull
+ where TRange : notnull
+ {
+ return new GetRequestBuilder(
+ source.TableName,
+ source.Item.PrimaryKeyMarshaller.ComposeKeys(y => partitionKeySelector(y), y => rangeKeySelector(y))
+ );
+ }
+
+
+ public static UpdateRequestBuilder ToUpdateItemRequestBuilder(
+ this UpdateExpression source,
+ Func> keySelector
+ )
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new UpdateRequestBuilder(
+ source.TableAccess.Item.ComposeAttributeExpression(source.Update, null),
+ source.TableAccess.TableName,
+ keySelector,
+ source.TableAccess.Item.PrimaryKeyMarshaller
+ );
+ }
+
+ public static UpdateRequestBuilder ToUpdateItemRequestBuilder(
+ this ConditionalUpdateExpression source,
+ Func> keySelector
+ )
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new UpdateRequestBuilder(
+ source.TableAccess.Item.ComposeAttributeExpression(source.Update, source.Condition),
+ source.TableAccess.TableName,
+ keySelector,
+ source.TableAccess.Item.PrimaryKeyMarshaller
+ );
+ }
+
+ public static KeyConditionExpression WithKeyConditionExpression(
+ this TableAccess source,
+ Func condition)
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new KeyConditionExpression(source, condition);
+ }
+
+ public static PutRequestBuilder ToPutRequestBuilder(
+ this TableAccess source
+ )
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new PutRequestBuilder
+ (
+ null,
+ source.Item.Marshall,
+ source.TableName
+ );
+ }
+
+ public static PutRequestBuilder ToPutRequestBuilder(
+ this ConditionExpression source
+ )
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new PutRequestBuilder
+ (
+ source.TableAccess.Item.ComposeAttributeExpression(null, source.Condition),
+ source.TableAccess.Item.Marshall,
+ source.TableAccess.TableName
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Builders/GetRequestBuilder.cs b/src/Dynatello/Builders/GetRequestBuilder.cs
new file mode 100644
index 00000000..311f58e1
--- /dev/null
+++ b/src/Dynatello/Builders/GetRequestBuilder.cs
@@ -0,0 +1,50 @@
+using Amazon.DynamoDBv2;
+using Amazon.DynamoDBv2.Model;
+
+namespace Dynatello.Builders;
+
+public readonly record struct GetRequestBuilder
+{
+ private readonly Func> _keysSelector;
+
+ internal GetRequestBuilder(
+ string tableName,
+ Func> keysSelector)
+ {
+ _keysSelector = keysSelector;
+ TableName = tableName;
+ }
+
+ [Obsolete(Constants.ObsoleteConstructorMessage, true)]
+ public GetRequestBuilder()
+ {
+ throw Constants.InvalidConstructor();
+ }
+
+ ///
+ public string TableName { get; init; }
+
+ ///
+ public bool? ConsistentRead { get; init; } = null;
+
+ ///
+ public ReturnConsumedCapacity? ReturnConsumedCapacity { get; init; } = null;
+
+ public GetItemRequest Build(T arg)
+ {
+ var request = new GetItemRequest
+ {
+ ReturnConsumedCapacity = ReturnConsumedCapacity,
+ TableName = TableName,
+ Key = _keysSelector(arg)
+ };
+
+ if (ConsistentRead is { } consistentRead)
+ request.ConsistentRead = consistentRead;
+
+ if (ReturnConsumedCapacity is not null)
+ request.ReturnConsumedCapacity = ReturnConsumedCapacity;
+
+ return request;
+ }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Builders/PutRequestBuilder.cs b/src/Dynatello/Builders/PutRequestBuilder.cs
new file mode 100644
index 00000000..cf7b718b
--- /dev/null
+++ b/src/Dynatello/Builders/PutRequestBuilder.cs
@@ -0,0 +1,86 @@
+using Amazon.DynamoDBv2;
+using Amazon.DynamoDBv2.Model;
+using DynamoDBGenerator;
+
+namespace Dynatello.Builders;
+
+///
+/// A record based builder for creating that can be configured via the `with` syntax.
+///
+public readonly record struct PutRequestBuilder
+{
+ private readonly Func? _attributeExpressionSelector;
+ private readonly Func> _marshall;
+
+ private readonly string _tableName;
+
+
+ internal PutRequestBuilder(
+ Func? attributeExpressionSelector,
+ Func> marshall,
+ string tableName
+ )
+ {
+ _attributeExpressionSelector = attributeExpressionSelector;
+ _marshall = marshall;
+ _tableName = tableName;
+ }
+
+ [Obsolete(Constants.ObsoleteConstructorMessage, true)]
+ public PutRequestBuilder()
+ {
+ throw Constants.InvalidConstructor();
+ }
+
+ ///
+ public string TableName
+ {
+ get => _tableName;
+ init => _tableName = value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+ ///
+ public ReturnValue? ReturnValues { get; init; } = null;
+
+ ///
+ public ReturnConsumedCapacity? ReturnConsumedCapacity { get; init; } = null;
+
+ ///
+ public ReturnItemCollectionMetrics? ReturnItemCollectionMetrics { get; init; } = null;
+
+ ///
+ public ReturnValuesOnConditionCheckFailure? ReturnValuesOnConditionCheckFailure { get; init; } = null;
+
+
+ public PutItemRequest Build(T element)
+ {
+ var request = new PutItemRequest
+ {
+ TableName = TableName,
+ Item = _marshall(element),
+ Expected = null,
+ ConditionalOperator = null,
+ ConditionExpression = null,
+ ExpressionAttributeNames = null,
+ ExpressionAttributeValues = null
+ };
+
+ if (ReturnValues is not null)
+ request.ReturnValues = ReturnValues;
+ if (ReturnConsumedCapacity is not null)
+ request.ReturnConsumedCapacity = ReturnConsumedCapacity;
+ if (ReturnItemCollectionMetrics is not null)
+ request.ReturnItemCollectionMetrics = ReturnItemCollectionMetrics;
+ if (ReturnValuesOnConditionCheckFailure is not null)
+ request.ReturnValuesOnConditionCheckFailure = ReturnValuesOnConditionCheckFailure;
+
+ if (_attributeExpressionSelector is null) return request;
+ var attributeExpression = _attributeExpressionSelector(element);
+
+ request.ExpressionAttributeNames = attributeExpression.Names;
+ request.ExpressionAttributeValues = attributeExpression.Values;
+ request.ConditionExpression = attributeExpression.Expressions[0];
+
+ return request;
+ }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Builders/QueryRequestBuilder.cs b/src/Dynatello/Builders/QueryRequestBuilder.cs
new file mode 100644
index 00000000..2ecff3d0
--- /dev/null
+++ b/src/Dynatello/Builders/QueryRequestBuilder.cs
@@ -0,0 +1,86 @@
+using Amazon.DynamoDBv2;
+using Amazon.DynamoDBv2.Model;
+using DynamoDBGenerator;
+
+namespace Dynatello.Builders;
+
+public readonly record struct QueryRequestBuilder
+{
+ private readonly Func _attributeExpressionSelector;
+
+ internal QueryRequestBuilder(Func attributeExpressionSelector, string tableName)
+ {
+ _attributeExpressionSelector = attributeExpressionSelector;
+ TableName = tableName;
+ }
+
+ [Obsolete(Constants.ObsoleteConstructorMessage, true)]
+ public QueryRequestBuilder()
+ {
+ throw Constants.InvalidConstructor();
+ }
+
+ ///
+ public string TableName { get; init; }
+
+ ///
+ public string? IndexName { get; init; } = null;
+
+ ///
+ public int? Limit { get; init; } = null;
+
+ ///
+ public bool? ConsistentRead { get; init; } = null;
+
+ ///
+ public bool? ScanIndexForward { get; init; } = null;
+
+ ///
+ public Select? Select { get; init; } = null;
+
+ ///
+ public ReturnConsumedCapacity? ReturnConsumedCapacity { get; init; } = null;
+
+ public QueryRequest Build(T arg)
+ {
+ var attributeExpression = _attributeExpressionSelector(arg);
+
+ var queryRequest = new QueryRequest
+ {
+ AttributesToGet = null,
+ QueryFilter = null,
+ ConditionalOperator = null,
+ KeyConditions = null,
+ KeyConditionExpression = attributeExpression.Expressions[0],
+ ExpressionAttributeValues = attributeExpression.Values,
+ ExpressionAttributeNames = attributeExpression.Names,
+ TableName = TableName,
+ IndexName = null,
+ ProjectionExpression = null
+ };
+
+
+ if (ReturnConsumedCapacity is not null)
+ queryRequest.ReturnConsumedCapacity = ReturnConsumedCapacity;
+
+ if (ConsistentRead is { } consistentRead)
+ queryRequest.ConsistentRead = consistentRead;
+
+ if (ScanIndexForward is { } scanIndexForward)
+ queryRequest.ScanIndexForward = scanIndexForward;
+
+ if (Select is not null)
+ queryRequest.Select = Select;
+
+ if (Limit is { } limit)
+ queryRequest.Limit = limit;
+
+ if (IndexName is not null)
+ queryRequest.IndexName = IndexName;
+
+ if (attributeExpression.Expressions.Count == 2)
+ queryRequest.FilterExpression = attributeExpression.Expressions[1];
+
+ return queryRequest;
+ }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Builders/Types/ConditionExpression.cs b/src/Dynatello/Builders/Types/ConditionExpression.cs
new file mode 100644
index 00000000..2b856361
--- /dev/null
+++ b/src/Dynatello/Builders/Types/ConditionExpression.cs
@@ -0,0 +1,26 @@
+using DynamoDBGenerator;
+
+namespace Dynatello.Builders.Types;
+
+public readonly record struct ConditionExpression
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+{
+ internal readonly Func Condition;
+ internal readonly TableAccess TableAccess;
+
+ [Obsolete(Constants.ObsoleteConstructorMessage, true)]
+ public ConditionExpression()
+ {
+ throw Constants.InvalidConstructor();
+ }
+
+ internal ConditionExpression(
+ in TableAccess tableAccess,
+ in Func condition
+ )
+ {
+ TableAccess = tableAccess;
+ Condition = condition;
+ }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Builders/Types/ConditionalUpdateExpression.cs b/src/Dynatello/Builders/Types/ConditionalUpdateExpression.cs
new file mode 100644
index 00000000..911ad497
--- /dev/null
+++ b/src/Dynatello/Builders/Types/ConditionalUpdateExpression.cs
@@ -0,0 +1,28 @@
+using DynamoDBGenerator;
+
+namespace Dynatello.Builders.Types;
+
+public readonly record struct ConditionalUpdateExpression
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+{
+ internal readonly Func Condition;
+ internal readonly TableAccess TableAccess;
+ internal readonly Func Update;
+
+ [Obsolete(Constants.ObsoleteConstructorMessage, true)]
+ public ConditionalUpdateExpression()
+ {
+ throw Constants.InvalidConstructor();
+ }
+
+ internal ConditionalUpdateExpression(
+ in TableAccess tableAccess,
+ in Func update,
+ in Func condition)
+ {
+ TableAccess = tableAccess;
+ Update = update;
+ Condition = condition;
+ }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Builders/Types/Extensions.cs b/src/Dynatello/Builders/Types/Extensions.cs
new file mode 100644
index 00000000..815183f1
--- /dev/null
+++ b/src/Dynatello/Builders/Types/Extensions.cs
@@ -0,0 +1,74 @@
+using DynamoDBGenerator;
+
+namespace Dynatello.Builders.Types;
+
+public static class Extensions
+{
+ public static KeyConditionedFilterExpression WithFilterExpression(
+ this KeyConditionExpression source,
+ Func filter
+ )
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new KeyConditionedFilterExpression(
+ source.TableAccess,
+ source.Condition,
+ filter
+ );
+ }
+
+ public static ConditionalUpdateExpression WithConditionExpression(
+ this UpdateExpression source,
+ Func condition)
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new ConditionalUpdateExpression(
+ in source.TableAccess,
+ in source.Update,
+ condition
+ );
+ }
+
+ public static UpdateExpression WithUpdateExpression(
+ this TableAccess source,
+ Func updateExpression
+ )
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new UpdateExpression(in source, in updateExpression);
+ }
+
+ public static ConditionExpression WithConditionExpression(
+ this TableAccess source,
+ Func condition
+ )
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new ConditionExpression(in source, in condition);
+ }
+
+ public static ConditionalUpdateExpression WithUpdateExpression(
+ this ConditionExpression source,
+ Func update
+ )
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new ConditionalUpdateExpression(
+ in source.TableAccess,
+ in update,
+ in source.Condition
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Builders/Types/KeyConditionExpression.cs b/src/Dynatello/Builders/Types/KeyConditionExpression.cs
new file mode 100644
index 00000000..e6b61b7b
--- /dev/null
+++ b/src/Dynatello/Builders/Types/KeyConditionExpression.cs
@@ -0,0 +1,26 @@
+using DynamoDBGenerator;
+
+namespace Dynatello.Builders.Types;
+
+public readonly struct KeyConditionExpression
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+{
+ internal readonly Func Condition;
+ internal readonly TableAccess TableAccess;
+
+ [Obsolete(Constants.ObsoleteConstructorMessage, true)]
+ public KeyConditionExpression()
+ {
+ throw Constants.InvalidConstructor();
+ }
+
+ internal KeyConditionExpression(
+ in TableAccess tableAccess,
+ in Func condition
+ )
+ {
+ TableAccess = tableAccess;
+ Condition = condition;
+ }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Builders/Types/KeyConditionedFilterExpression.cs b/src/Dynatello/Builders/Types/KeyConditionedFilterExpression.cs
new file mode 100644
index 00000000..ec610ac7
--- /dev/null
+++ b/src/Dynatello/Builders/Types/KeyConditionedFilterExpression.cs
@@ -0,0 +1,29 @@
+using DynamoDBGenerator;
+
+namespace Dynatello.Builders.Types;
+
+public readonly struct KeyConditionedFilterExpression
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+{
+ internal readonly Func Condition;
+ internal readonly Func Filter;
+ internal readonly TableAccess TableAccess;
+
+ [Obsolete(Constants.ObsoleteConstructorMessage, true)]
+ public KeyConditionedFilterExpression()
+ {
+ throw Constants.InvalidConstructor();
+ }
+
+ internal KeyConditionedFilterExpression(
+ in TableAccess tableAccess,
+ in Func condition,
+ in Func filter
+ )
+ {
+ TableAccess = tableAccess;
+ Condition = condition;
+ Filter = filter;
+ }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Builders/Types/TableAccess.cs b/src/Dynatello/Builders/Types/TableAccess.cs
new file mode 100644
index 00000000..c92f2f88
--- /dev/null
+++ b/src/Dynatello/Builders/Types/TableAccess.cs
@@ -0,0 +1,23 @@
+using DynamoDBGenerator;
+
+namespace Dynatello.Builders.Types;
+
+public readonly record struct TableAccess
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+{
+ [Obsolete(Constants.ObsoleteConstructorMessage, true)]
+ public TableAccess()
+ {
+ throw Constants.InvalidConstructor();
+ }
+
+ internal TableAccess(in string tableName, in IDynamoDBMarshaller item)
+ {
+ TableName = tableName ?? throw new ArgumentNullException(nameof(tableName));
+ Item = item ?? throw new ArgumentNullException(nameof(item));
+ }
+
+ internal string TableName { get; }
+ internal IDynamoDBMarshaller Item { get; }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Builders/Types/UpdateExpression.cs b/src/Dynatello/Builders/Types/UpdateExpression.cs
new file mode 100644
index 00000000..80fd8b4e
--- /dev/null
+++ b/src/Dynatello/Builders/Types/UpdateExpression.cs
@@ -0,0 +1,25 @@
+using DynamoDBGenerator;
+
+namespace Dynatello.Builders.Types;
+
+public readonly record struct UpdateExpression
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+{
+ internal readonly TableAccess TableAccess;
+ internal readonly Func Update;
+
+ [Obsolete(Constants.ObsoleteConstructorMessage, true)]
+ public UpdateExpression()
+ {
+ throw Constants.InvalidConstructor();
+ }
+
+ internal UpdateExpression(
+ in TableAccess tableAccess,
+ in Func update)
+ {
+ TableAccess = tableAccess;
+ Update = update;
+ }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Builders/UpdateRequestBuilder.cs b/src/Dynatello/Builders/UpdateRequestBuilder.cs
new file mode 100644
index 00000000..bc643fd8
--- /dev/null
+++ b/src/Dynatello/Builders/UpdateRequestBuilder.cs
@@ -0,0 +1,100 @@
+using Amazon.DynamoDBv2;
+using Amazon.DynamoDBv2.Model;
+using DynamoDBGenerator;
+
+namespace Dynatello.Builders;
+
+///
+/// A record based builder for creating that can be configured via the `with` syntax.
+///
+public readonly record struct UpdateRequestBuilder
+{
+ private readonly Func _attributeExpressionSelector;
+ private readonly IDynamoDBKeyMarshaller _keyMarshaller;
+ private readonly Func> _keySelector;
+
+ private readonly string _tableName;
+
+ [Obsolete(Constants.ObsoleteConstructorMessage, true)]
+ public UpdateRequestBuilder()
+ {
+ throw Constants.InvalidConstructor();
+ }
+
+ internal UpdateRequestBuilder(
+ Func attributeExpressionSelector,
+ string tableName,
+ Func> keySelector,
+ IDynamoDBKeyMarshaller keyMarshaller
+ )
+ {
+ _attributeExpressionSelector = attributeExpressionSelector;
+ _tableName = tableName;
+ _keySelector = keySelector;
+ _keyMarshaller = keyMarshaller;
+ }
+
+ ///
+ public string TableName
+ {
+ get => _tableName;
+ init => _tableName = value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+ ///
+ /// A function to specify how the keys should be accessed through the .
+ ///
+ public Func> KeySelector
+ {
+ get => _keySelector;
+ init => _keySelector = value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+ ///
+ public ReturnConsumedCapacity? ReturnConsumedCapacity { get; init; } = null;
+
+ ///
+ public ReturnItemCollectionMetrics? ReturnItemCollectionMetrics { get; init; } = null;
+
+ ///
+ public ReturnValue? ReturnValues { get; init; } = null;
+
+ ///
+ public ReturnValuesOnConditionCheckFailure? ReturnValuesOnConditionCheckFailure { get; init; } = null;
+
+
+ ///
+ /// Will build a with the specified configurations.
+ ///
+ public UpdateItemRequest Build(T arg)
+ {
+ var expression = _attributeExpressionSelector(arg);
+ var update = new UpdateItemRequest
+ {
+ UpdateExpression = expression.Expressions[0],
+ ConditionExpression = expression.Expressions.Count is 2 ? expression.Expressions[1] : null,
+ TableName = TableName,
+ Key = KeySelector(_keyMarshaller, arg),
+ ExpressionAttributeNames = expression.Names,
+ ExpressionAttributeValues = expression.Values,
+ Expected = null,
+ AttributeUpdates = null,
+ ConditionalOperator = null
+ };
+
+ if (ReturnValues is not null)
+ update.ReturnValues = ReturnValues;
+
+ if (ReturnConsumedCapacity is not null)
+ update.ReturnConsumedCapacity = ReturnConsumedCapacity;
+
+ if (ReturnItemCollectionMetrics is not null)
+ update.ReturnItemCollectionMetrics = ReturnItemCollectionMetrics;
+
+ if (ReturnValuesOnConditionCheckFailure is not null)
+ update.ReturnValuesOnConditionCheckFailure = ReturnValuesOnConditionCheckFailure;
+
+
+ return update;
+ }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Constants.cs b/src/Dynatello/Constants.cs
new file mode 100644
index 00000000..53d74748
--- /dev/null
+++ b/src/Dynatello/Constants.cs
@@ -0,0 +1,13 @@
+namespace Dynatello;
+
+internal static class Constants
+{
+ internal const string ObsoleteConstructorMessage = "Do not use this constructor!";
+ private const string ConstructorException = "This is an invalid constructor access.";
+
+ internal static Exception InvalidConstructor()
+ {
+ return new InvalidOperationException(ConstructorException);
+ }
+
+}
\ No newline at end of file
diff --git a/src/Dynatello/DynamoDBMarshallerExtensions.cs b/src/Dynatello/DynamoDBMarshallerExtensions.cs
new file mode 100644
index 00000000..16fc73cd
--- /dev/null
+++ b/src/Dynatello/DynamoDBMarshallerExtensions.cs
@@ -0,0 +1,52 @@
+using Amazon.DynamoDBv2.Model;
+using DynamoDBGenerator;
+using Dynatello.Builders.Types;
+using static DynamoDBGenerator.Extensions.DynamoDBMarshallerExtensions;
+
+namespace Dynatello;
+
+public static class DynamoDBMarshallerExtensions
+{
+ public static TableAccess OnTable
+
+ (this IDynamoDBMarshaller item, string tableName)
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return new TableAccess(in tableName, in item);
+ }
+
+ internal static Func> ComposeKeys
+ (
+ this IDynamoDBKeyMarshaller source,
+ Func partitionKeySelector,
+ Func? rangeKeySelector
+ )
+ {
+ return (partitionKeySelector, rangeKeySelector) switch
+ {
+ (not null, not null) => y => source.Keys(partitionKeySelector(y), rangeKeySelector(y)),
+ (not null, null) => y => source.PartitionKey(partitionKeySelector(y)),
+ (null, not null) => y => source.RangeKey(rangeKeySelector(y)),
+ (null, null) => throw new ArgumentNullException("")
+ };
+ }
+
+ internal static Func ComposeAttributeExpression(
+ this IDynamoDBMarshaller source,
+ Func? update,
+ Func? condition
+ )
+ where TReferences : IAttributeExpressionNameTracker
+ where TArgumentReferences : IAttributeExpressionValueTracker
+ {
+ return (update, condition) switch
+ {
+ (null, null) => throw new ArgumentNullException(""),
+ (not null, not null) => y => source.ToAttributeExpression(y, update, condition),
+ (not null, null) => y => source.ToAttributeExpression(y, update),
+ (null, not null) => y => source.ToAttributeExpression(y, condition)
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/Dynatello/Dynatello.csproj b/src/Dynatello/Dynatello.csproj
new file mode 100644
index 00000000..cc917ad9
--- /dev/null
+++ b/src/Dynatello/Dynatello.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net6.0
+ enable
+ enable
+ false
+ 0.0.0
+ Dynatello
+ Robert Andersson
+ Contains functionality to improve the convinience when working the DynamoDB.SourceGenerator.
+ Robert Anderson
+ https://github.com/inputfalken/DynamoDB.SourceGenerator
+ MIT
+ https://github.com/inputfalken/DynamoDB.SourceGenerator
+ git
+ true
+ true
+
+
+
+
+
+
+
+
diff --git a/tests/DynamoDBGenerator.SourceGenerator.Tests/Extensions/ToPutItemRequestTests.cs b/tests/DynamoDBGenerator.SourceGenerator.Tests/Extensions/ToPutItemRequestTests.cs
deleted file mode 100644
index a7c841d7..00000000
--- a/tests/DynamoDBGenerator.SourceGenerator.Tests/Extensions/ToPutItemRequestTests.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using Amazon.DynamoDBv2;
-using AutoFixture;
-using DynamoDBGenerator.Attributes;
-using DynamoDBGenerator.Extensions;
-namespace DynamoDBGenerator.SourceGenerator.Tests.Extensions;
-
-[DynamoDBMarshaller(typeof(User))]
-public partial class ToPutItemRequestTests
-{
- private readonly Fixture _fixture = new();
-
- [Fact]
- public void Without_ConditionExpression_ShouldNotIncludeExpressionFields()
- {
- var user = _fixture.Create();
- var putItemRequest = UserMarshaller.ToPutItemRequest(user, ReturnValue.NONE, "TABLE");
-
- putItemRequest.ConditionExpression.Should().BeNullOrWhiteSpace();
- putItemRequest.ExpressionAttributeNames.Should().BeNullOrEmpty();
- putItemRequest.ExpressionAttributeValues.Should().BeNullOrEmpty();
- putItemRequest.Item.Should().HaveCount(5);
- putItemRequest.Item[nameof(user.Email)].S.Should().Be(user.Email);
- putItemRequest.Item[nameof(user.Firstname)].S.Should().Be(user.Firstname);
- putItemRequest.Item[nameof(user.Lastname)].S.Should().Be(user.Lastname);
- putItemRequest.Item[nameof(user.Id)].S.Should().Be(user.Id);
- putItemRequest.Item[nameof(user.Metadata)].M.Should().SatisfyRespectively(x =>
- {
- x.Key.Should().Be(nameof(user.Metadata.ModifiedAt));
- x.Value.S.Should().Be(user.Metadata.ModifiedAt.ToString("O"));
- });
- putItemRequest.ReturnValues.Should().Be(ReturnValue.NONE);
- putItemRequest.TableName.Should().Be("TABLE");
- }
-
- [Fact]
- public void With_ConditionExpression_ShouldIncludeExpressionFields()
- {
- var user = _fixture.Create();
- var putItemRequest = UserMarshaller.ToPutItemRequest(user, (x, y) => $"{x.Email} <> {y.Email} AND {x.Firstname} = {y.Firstname}", ReturnValue.NONE, "TABLE");
-
- putItemRequest.ConditionExpression.Should().Be("#Email <> :p1 AND #Firstname = :p2");
- putItemRequest.ExpressionAttributeNames.Should().HaveCount(2);
- putItemRequest.ExpressionAttributeNames["#Email"].Should().Be(nameof(user.Email));
- putItemRequest.ExpressionAttributeNames["#Firstname"].Should().Be(nameof(user.Firstname));
- putItemRequest.ExpressionAttributeValues.Should().HaveCount(2);
- putItemRequest.ExpressionAttributeValues[":p1"].S.Should().Be(user.Email);
- putItemRequest.ExpressionAttributeValues[":p2"].S.Should().Be(user.Firstname);
- putItemRequest.Item.Should().HaveCount(5);
- putItemRequest.Item[nameof(user.Email)].S.Should().Be(user.Email);
- putItemRequest.Item[nameof(user.Firstname)].S.Should().Be(user.Firstname);
- putItemRequest.Item[nameof(user.Lastname)].S.Should().Be(user.Lastname);
- putItemRequest.Item[nameof(user.Id)].S.Should().Be(user.Id);
- putItemRequest.Item[nameof(user.Metadata)].M.Should().SatisfyRespectively(x =>
- {
- x.Key.Should().Be(nameof(user.Metadata.ModifiedAt));
- x.Value.S.Should().Be(user.Metadata.ModifiedAt.ToString("O"));
- });
- putItemRequest.ReturnValues.Should().Be(ReturnValue.NONE);
- putItemRequest.TableName.Should().Be("TABLE");
- }
-
-}
-
-public class User
-{
- [DynamoDBHashKey]
- public string Id { get; set; } = null!;
-
- [DynamoDBRangeKey]
- public string Email { get; set; } = null!;
-
- public string Lastname { get; set; } = null!;
-
- public string Firstname { get; set; } = null!;
-
- public Meta Metadata { get; set; } = null!;
-
- public class Meta
- {
- public DateTimeOffset ModifiedAt { get; set; }
- }
-}
-
-public class UpdateUserEmail
-{
- public string UserId { get; set; } = null!;
- public string UserEmail { get; set; } = null!;
- public DateTimeOffset TimeStamp { get; set; }
-}
\ No newline at end of file
diff --git a/tests/DynamoDBGenerator.SourceGenerator.Tests/Extensions/ToUpdateItemRequestTests.cs b/tests/DynamoDBGenerator.SourceGenerator.Tests/Extensions/ToUpdateItemRequestTests.cs
deleted file mode 100644
index 800976fb..00000000
--- a/tests/DynamoDBGenerator.SourceGenerator.Tests/Extensions/ToUpdateItemRequestTests.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-using Amazon.DynamoDBv2;
-using AutoFixture;
-using DynamoDBGenerator.Attributes;
-using DynamoDBGenerator.Extensions;
-namespace DynamoDBGenerator.SourceGenerator.Tests.Extensions;
-
-[DynamoDBMarshaller(typeof(User))]
-[DynamoDBMarshaller(typeof(User), PropertyName = "UpdateEmail", ArgumentType = typeof(UpdateUserEmail))]
-public partial class ToUpdateItemRequestTests
-{
- private readonly Fixture _fixture = new();
-
- [Fact]
- public void ArgumentTypeProvided_WithConditionExpression_ShouldIncludeUpdateAndConditionExpressionFields()
- {
- var updateUserEmail = _fixture.Create();
- var updateItemRequest = UpdateEmail.ToUpdateItemRequest(
- updateUserEmail,
- (x, y) => x.Keys(y.UserId, y.UserEmail),
- (x, y) => $"SET {x.Email} = {y.UserEmail}, {x.Metadata.ModifiedAt} = {y.TimeStamp}",
- (x,y) => $"{x.Id} = {y.UserId} AND {x.Email} <> {y.UserEmail}",
- ReturnValue.NONE,
- "TABLE"
- );
-
- updateItemRequest.ConditionExpression.Should().Be("#Id = :p3 AND #Email <> :p1");
- updateItemRequest.ExpressionAttributeNames.Should().HaveCount(3);
- updateItemRequest.ExpressionAttributeNames["#Email"].Should().Be(nameof(User.Email));
- updateItemRequest.ExpressionAttributeNames["#Id"].Should().Be(nameof(User.Id));
- updateItemRequest.ExpressionAttributeNames["#Metadata.#ModifiedAt"].Should().Be(nameof(User.Metadata.ModifiedAt));
- updateItemRequest.ExpressionAttributeValues.Should().HaveCount(3);
- updateItemRequest.ExpressionAttributeValues[":p1"].S.Should().Be(updateUserEmail.UserEmail);
- updateItemRequest.ExpressionAttributeValues[":p2"].S.Should().Be(updateUserEmail.TimeStamp.ToString("O"));
- updateItemRequest.ExpressionAttributeValues[":p3"].S.Should().Be(updateUserEmail.UserId);
- updateItemRequest.Key[nameof(User.Email)].S.Should().Be(updateUserEmail.UserEmail);
- updateItemRequest.Key[nameof(User.Id)].S.Should().Be(updateUserEmail.UserId);
- updateItemRequest.ReturnValues.Should().Be(ReturnValue.NONE);
- updateItemRequest.TableName.Should().Be("TABLE");
- updateItemRequest.UpdateExpression.Should().Be("SET #Email = :p1, #Metadata.#ModifiedAt = :p2");
- }
-
- [Fact]
- public void ArgumentTypeProvided_WithoutConditionExpression_ShouldOnlyIncludeUpdateExpressionFields()
- {
- var updateUserEmail = _fixture.Create();
- var updateItemRequest = UpdateEmail.ToUpdateItemRequest(
- updateUserEmail,
- (x, y) => x.Keys(y.UserId, y.UserEmail),
- (x, y) => $"SET {x.Email} = {y.UserEmail}, {x.Metadata.ModifiedAt} = {y.TimeStamp}",
- ReturnValue.NONE,
- "TABLE"
- );
-
- updateItemRequest.ConditionExpression.Should().BeNullOrWhiteSpace();
- updateItemRequest.ExpressionAttributeNames.Should().HaveCount(2);
- updateItemRequest.ExpressionAttributeNames["#Email"].Should().Be(nameof(User.Email));
- updateItemRequest.ExpressionAttributeNames["#Metadata.#ModifiedAt"].Should().Be(nameof(User.Metadata.ModifiedAt));
- updateItemRequest.ExpressionAttributeValues.Should().HaveCount(2);
- updateItemRequest.ExpressionAttributeValues[":p1"].S.Should().Be(updateUserEmail.UserEmail);
- updateItemRequest.ExpressionAttributeValues[":p2"].S.Should().Be(updateUserEmail.TimeStamp.ToString("O"));
- updateItemRequest.Key[nameof(User.Email)].S.Should().Be(updateUserEmail.UserEmail);
- updateItemRequest.Key[nameof(User.Id)].S.Should().Be(updateUserEmail.UserId);
- updateItemRequest.ReturnValues.Should().Be(ReturnValue.NONE);
- updateItemRequest.TableName.Should().Be("TABLE");
- updateItemRequest.UpdateExpression.Should().Be("SET #Email = :p1, #Metadata.#ModifiedAt = :p2");
- }
-
- [Fact]
- public void NoArgumentTypeProvided_WithoutConditionExpression_ShouldOnlyIncludeUpdateExpressionFields()
- {
- var user = _fixture.Create();
- var updateItemRequest = UserMarshaller
- .ToUpdateItemRequest(
- user,
- (x, y) => x.Keys(y.Id, y.Lastname),
- (x, y) => $"SET {x.Email} = {y.Email}, {x.Firstname} = {y.Firstname}",
- ReturnValue.NONE,
- "TABLE"
- );
-
- updateItemRequest.ConditionExpression.Should().BeNullOrWhiteSpace();
- updateItemRequest.ExpressionAttributeNames.Should().HaveCount(2);
- updateItemRequest.ExpressionAttributeNames["#Email"].Should().Be(nameof(user.Email));
- updateItemRequest.ExpressionAttributeNames["#Firstname"].Should().Be(nameof(user.Firstname));
- updateItemRequest.ExpressionAttributeValues.Should().HaveCount(2);
- updateItemRequest.ExpressionAttributeValues[":p1"].S.Should().Be(user.Email);
- updateItemRequest.ExpressionAttributeValues[":p2"].S.Should().Be(user.Firstname);
- updateItemRequest.Key[nameof(user.Id)].S.Should().Be(user.Id);
- updateItemRequest.Key[nameof(user.Email)].S.Should().Be(user.Lastname);
- updateItemRequest.ReturnValues.Should().Be(ReturnValue.NONE);
- updateItemRequest.TableName.Should().Be("TABLE");
- updateItemRequest.UpdateExpression.Should().Be("SET #Email = :p1, #Firstname = :p2");
- }
-
- [Fact]
- public void NoArgumentTypeProvided_WithConditionExpression_ShouldIncludeUpdateAndConditionExpressionFields()
- {
- var user = _fixture.Create();
- var updateItemRequest = UserMarshaller
- .ToUpdateItemRequest(
- user,
- (x, y) => x.Keys(y.Id, y.Lastname),
- (x, y) => $"SET {x.Email} = {y.Email}, {x.Firstname} = {y.Firstname}",
- (x, y) => $"{x.Id} = {y.Id}",
- ReturnValue.NONE,
- "TABLE"
- );
-
- updateItemRequest.ConditionExpression.Should().Be("#Id = :p3");
- updateItemRequest.ExpressionAttributeNames.Should().HaveCount(3);
- updateItemRequest.ExpressionAttributeNames["#Email"].Should().Be(nameof(user.Email));
- updateItemRequest.ExpressionAttributeNames["#Firstname"].Should().Be(nameof(user.Firstname));
- updateItemRequest.ExpressionAttributeNames["#Id"].Should().Be(nameof(user.Id));
- updateItemRequest.ExpressionAttributeValues.Should().HaveCount(3);
- updateItemRequest.ExpressionAttributeValues[":p1"].S.Should().Be(user.Email);
- updateItemRequest.ExpressionAttributeValues[":p2"].S.Should().Be(user.Firstname);
- updateItemRequest.ExpressionAttributeValues[":p3"].S.Should().Be(user.Id);
- updateItemRequest.Key[nameof(user.Id)].S.Should().Be(user.Id);
- updateItemRequest.Key[nameof(user.Email)].S.Should().Be(user.Lastname);
- updateItemRequest.ReturnValues.Should().Be(ReturnValue.NONE);
- updateItemRequest.TableName.Should().Be("TABLE");
- updateItemRequest.UpdateExpression.Should().Be("SET #Email = :p1, #Firstname = :p2");
- }
-}
\ No newline at end of file
diff --git a/tests/Dynatello.Tests/Dynatello.Tests.csproj b/tests/Dynatello.Tests/Dynatello.Tests.csproj
new file mode 100644
index 00000000..94f35b92
--- /dev/null
+++ b/tests/Dynatello.Tests/Dynatello.Tests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Dynatello.Tests/ToGetItemRequestTests.cs b/tests/Dynatello.Tests/ToGetItemRequestTests.cs
new file mode 100644
index 00000000..459e4af3
--- /dev/null
+++ b/tests/Dynatello.Tests/ToGetItemRequestTests.cs
@@ -0,0 +1,121 @@
+using Amazon.DynamoDBv2.Model;
+using AutoFixture;
+using DynamoDBGenerator.Exceptions;
+using Dynatello.Builders;
+using FluentAssertions;
+
+namespace Dynatello.Tests;
+
+public class ToGetItemRequestTests
+{
+ [Fact]
+ public void Build_Request_CompositeKeys_InvalidPartition()
+ {
+ var act = () => Cat.GetByCompositeInvalidPartition
+ .OnTable("TABLE")
+ .ToGetRequestBuilder(x => x.Id, x => x.HomeId)
+ .Build(("", Guid.Empty));
+
+ act.Should()
+ .Throw()
+ .WithMessage("Value '*' from argument '*' is not convertable*");
+ }
+
+ [Fact]
+ public void Build_Request_CompositeKeys_InvalidRange()
+ {
+ var act = () => Cat.GetByCompositeInvalidRange
+ .OnTable("TABLE")
+ .ToGetRequestBuilder(x => x.Id, x => x.HomeId)
+ .Build((Guid.Empty, ""));
+
+ act.Should()
+ .Throw()
+ .WithMessage("Value '*' from argument '*' is not convertable*");
+ }
+
+ [Fact]
+ public void Build_Request_CompositeKeys_InvalidPartitionAndRange()
+ {
+ var act = () => Cat.GetByCompositeInvalidPartitionAndRange
+ .OnTable("TABLE")
+ .ToGetRequestBuilder(x => x.Id, x => x.HomeId)
+ .Build((2.3, ""));
+
+ act.Should()
+ .Throw()
+ .WithMessage("Value '*' from argument '*' is not convertable*");
+ }
+
+ [Fact]
+ public void Build_Request_WithInvalidPartitionKey()
+ {
+ var act = () => Cat.GetByInvalidPartition
+ .OnTable("TABLE")
+ .ToGetRequestBuilder(x => x)
+ .Build("TEST");
+
+ act.Should()
+ .Throw()
+ .WithMessage("Value '*' from argument '*' is not convertable*");
+ }
+
+ [Fact]
+ public void Build_Request_PartitionKeyOnly()
+ {
+ var getCatByPartitionKey = Cat.GetById
+ .OnTable("TABLE")
+ .ToGetRequestBuilder(x => x);
+
+ Cat.Fixture
+ .CreateMany()
+ .Should()
+ .AllSatisfy(x => getCatByPartitionKey
+ .Build(x)
+ .Should()
+ .BeEquivalentTo(new GetItemRequest
+ {
+ Key = new Dictionary
+ {
+ { nameof(Cat.Id), new AttributeValue { S = x.ToString() } }
+ },
+ TableName = "TABLE",
+ ConsistentRead = false,
+ ExpressionAttributeNames = new Dictionary(),
+ ProjectionExpression = null,
+ ReturnConsumedCapacity = null,
+ AttributesToGet = new List()
+ }));
+ }
+
+ [Fact]
+ public void Build_Request_CompositeKeys()
+ {
+ var getCatByCompositeKeys = Cat.GetByCompositeKey
+ .OnTable("TABLE")
+ .ToGetRequestBuilder(x => x.Id, x => x.HomeId);
+
+ Cat.Fixture
+ .CreateMany<(Guid PartitionKey, Guid RangeKey)>()
+ .Should()
+ .AllSatisfy(x => getCatByCompositeKeys
+ .Build(x)
+ .Should()
+ .BeEquivalentTo(new GetItemRequest
+ {
+ Key = new Dictionary
+ {
+ { nameof(Cat.Id), new AttributeValue { S = x.PartitionKey.ToString() } },
+ { nameof(Cat.HomeId), new AttributeValue { S = x.RangeKey.ToString() } }
+ },
+ TableName = "TABLE",
+ ConsistentRead = false,
+ ExpressionAttributeNames = new Dictionary(),
+ ProjectionExpression = null,
+ ReturnConsumedCapacity = null,
+ AttributesToGet = new List()
+ }
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git a/tests/Dynatello.Tests/ToPutItemRequestTests.cs b/tests/Dynatello.Tests/ToPutItemRequestTests.cs
new file mode 100644
index 00000000..cca3cc30
--- /dev/null
+++ b/tests/Dynatello.Tests/ToPutItemRequestTests.cs
@@ -0,0 +1,142 @@
+using Amazon.DynamoDBv2.DataModel;
+using Amazon.DynamoDBv2.Model;
+using AutoFixture;
+using DynamoDBGenerator.Attributes;
+using Dynatello.Builders;
+using Dynatello.Builders.Types;
+using FluentAssertions;
+
+namespace Dynatello.Tests;
+
+[DynamoDBMarshaller(typeof(User))]
+public partial class ToPutItemRequestTests
+{
+ private readonly Fixture _fixture = new();
+
+ [Fact]
+ public void Without_ConditionExpression_ShouldNotIncludeExpressionFields()
+ {
+ var builder = UserMarshaller
+ .OnTable("TABLE")
+ .ToPutRequestBuilder();
+ _fixture.CreateMany().Should().AllSatisfy(user =>
+ {
+ builder.Build(user)
+ .Should()
+ .BeEquivalentTo(new PutItemRequest
+ {
+ ConditionExpression = null,
+ ExpressionAttributeNames = null,
+ ExpressionAttributeValues = null,
+ Item = new Dictionary
+ {
+ { nameof(user.Email), new AttributeValue { S = user.Email } },
+ { nameof(user.Firstname), new AttributeValue { S = user.Firstname } },
+ { nameof(user.Lastname), new AttributeValue { S = user.Lastname } },
+ { nameof(user.Id), new AttributeValue { S = user.Id } },
+ {
+ nameof(user.Metadata), new AttributeValue
+ {
+ M = new Dictionary
+ {
+ {
+ nameof(user.Metadata.ModifiedAt),
+ new AttributeValue { S = user.Metadata.ModifiedAt.ToString("O") }
+ }
+ }
+ }
+ },
+ },
+ ReturnValues = null,
+ TableName = "TABLE",
+ Expected = null,
+ ReturnConsumedCapacity = null,
+ ConditionalOperator = null,
+ ReturnItemCollectionMetrics = null,
+ ReturnValuesOnConditionCheckFailure = null
+ });
+ });
+ }
+
+ [Fact]
+ public void With_ConditionExpression_ShouldIncludeExpressionFields()
+ {
+ var builder = UserMarshaller
+ .OnTable("TABLE")
+ .WithConditionExpression((x, y) => $"{x.Email} <> {y.Email} AND {x.Firstname} = {y.Firstname}")
+ .ToPutRequestBuilder();
+
+ _fixture.CreateMany().Should().AllSatisfy(user =>
+ {
+ builder.Build(user)
+ .Should()
+ .BeEquivalentTo(new PutItemRequest
+ {
+ ConditionExpression = "#Email <> :p1 AND #Firstname = :p2",
+ ExpressionAttributeNames = new Dictionary