< Summary

Information
Class: ArturRios.Data.DynamoDb.Repositories.DynamoRepository<T>
Assembly: ArturRios.Data.DynamoDb
File(s): D:\Repositories\dotnet-data\src\ArturRios.Data.DynamoDb\Repositories\DynamoRepository.cs
Line coverage
84%
Covered lines: 58
Uncovered lines: 11
Coverable lines: 69
Total lines: 148
Line coverage: 84%
Branch coverage
50%
Covered branches: 2
Total branches: 4
Branch coverage: 50%
Method coverage
100%
Covered methods: 15
Fully covered methods: 13
Total methods: 15
Method coverage: 100%
Full method coverage: 86.6%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.cctor()100%11100%
SaveAsync(...)100%11100%
LoadAsync(...)100%11100%
LoadAsync(...)100%11100%
DeleteAsync(...)100%11100%
QueryAsync(...)100%11100%
QueryAsync(...)100%11100%
ScanAsync(...)100%11100%
SaveManyAsync(...)100%11100%
DeleteManyAsync(...)100%11100%
LoadManyAsync(...)100%11100%
GuardedAsync()100%1170%
GuardedProcessAsync()0%3238.46%
Fail(...)100%22100%

File(s)

D:\Repositories\dotnet-data\src\ArturRios.Data.DynamoDb\Repositories\DynamoRepository.cs

#LineLine coverage
 1using Amazon.DynamoDBv2.DataModel;
 2using Amazon.DynamoDBv2.DocumentModel;
 3using Amazon.DynamoDBv2.Model;
 4using ArturRios.Data.DynamoDb.Interfaces;
 5using ArturRios.Output;
 6
 7namespace ArturRios.Data.DynamoDb.Repositories;
 8
 9/// <summary>
 10///     DynamoDB implementation of <see cref="IAsyncDynamoRepository{T}" /> over the AWS object-persistence
 11///     model (<see cref="IDynamoDBContext" />). Failures are returned as <see cref="DataOutput{T}" /> /
 12///     <see cref="ProcessOutput" />; a <see cref="ConditionalCheckFailedException" /> (from
 13///     <c>[DynamoDBVersion]</c> optimistic locking) becomes a concurrency error.
 14/// </summary>
 15/// <typeparam name="T">The item type.</typeparam>
 16/// <param name="context">The DynamoDB object-persistence context.</param>
 917public class DynamoRepository<T>(IDynamoDBContext context) : IAsyncDynamoRepository<T> where T : class
 18{
 19    /// <summary>Message prefix returned when an operation fails.</summary>
 20    protected const string OperationFailedMessage = "A data-access error occurred:";
 21
 22    /// <summary>Message returned on an optimistic-concurrency conflict.</summary>
 23    protected const string ConcurrencyMessage = "Concurrency conflict: the item was modified by another process.";
 24
 25    /// <summary>
 26    ///     The DynamoDB batch-write API rejects types with a <c>[DynamoDBVersion]</c> property unless
 27    ///     version checking is explicitly skipped (batch writes have no per-item conditional-check
 28    ///     support). Optimistic concurrency remains enforced on the single-item <see cref="SaveAsync" />/
 29    ///     <see cref="DeleteAsync" /> paths.
 30    /// </summary>
 131    private static readonly BatchWriteConfig BatchSkipVersionCheckConfig = new() { SkipVersionCheck = true };
 32
 33    /// <inheritdoc />
 34    public Task<DataOutput<T>> SaveAsync(T item, CancellationToken ct = default) =>
 1335        GuardedAsync(async () =>
 1336        {
 1337            await context.SaveAsync(item, ct);
 1338
 1139            return item;
 2440        });
 41
 42    /// <inheritdoc />
 43    public Task<DataOutput<T?>> LoadAsync(object hashKey, CancellationToken ct = default) =>
 244        GuardedAsync<T?>(async () => await context.LoadAsync<T>(hashKey, ct));
 45
 46    /// <inheritdoc />
 47    public Task<DataOutput<T?>> LoadAsync(object hashKey, object rangeKey, CancellationToken ct = default) =>
 648        GuardedAsync<T?>(async () => await context.LoadAsync<T>(hashKey, rangeKey, ct));
 49
 50    /// <inheritdoc />
 51    public Task<ProcessOutput> DeleteAsync(T item, CancellationToken ct = default) =>
 452        GuardedProcessAsync(async () => await context.DeleteAsync(item, ct));
 53
 54    /// <inheritdoc />
 55    public Task<DataOutput<IEnumerable<T>>> QueryAsync(object hashKey, CancellationToken ct = default) =>
 256        GuardedAsync<IEnumerable<T>>(async () => await context.QueryAsync<T>(hashKey).GetRemainingAsync(ct));
 57
 58    /// <inheritdoc />
 59    public Task<DataOutput<IEnumerable<T>>> QueryAsync(object hashKey, QueryOperator op,
 60        IEnumerable<object> sortKeyValues, CancellationToken ct = default) =>
 161        GuardedAsync<IEnumerable<T>>(async () =>
 262            await context.QueryAsync<T>(hashKey, op, sortKeyValues).GetRemainingAsync(ct));
 63
 64    /// <inheritdoc />
 65    public Task<DataOutput<IEnumerable<T>>> ScanAsync(IEnumerable<ScanCondition> conditions,
 66        CancellationToken ct = default) =>
 267        GuardedAsync<IEnumerable<T>>(async () => await context.ScanAsync<T>(conditions).GetRemainingAsync(ct));
 68
 69    /// <inheritdoc />
 70    public Task<DataOutput<IEnumerable<T>>> SaveManyAsync(IEnumerable<T> items, CancellationToken ct = default) =>
 171        GuardedAsync<IEnumerable<T>>(async () =>
 172        {
 173            var list = items.ToList();
 174            var batch = context.CreateBatchWrite<T>(BatchSkipVersionCheckConfig);
 175            batch.AddPutItems(list);
 176            await batch.ExecuteAsync(ct);
 177            return list;
 278        });
 79
 80    /// <inheritdoc />
 81    public Task<ProcessOutput> DeleteManyAsync(IEnumerable<T> items, CancellationToken ct = default) =>
 182        GuardedProcessAsync(async () =>
 183        {
 184            var batch = context.CreateBatchWrite<T>(BatchSkipVersionCheckConfig);
 185            batch.AddDeleteItems(items.ToList());
 186            await batch.ExecuteAsync(ct);
 287        });
 88
 89    /// <inheritdoc />
 90    public Task<DataOutput<IEnumerable<T>>>
 91        LoadManyAsync(IEnumerable<object> hashKeys, CancellationToken ct = default) =>
 292        GuardedAsync<IEnumerable<T>>(async () =>
 293        {
 294            var batch = context.CreateBatchGet<T>();
 1495            foreach (var key in hashKeys)
 496            {
 497                batch.AddKey(key);
 498            }
 299
 2100            await batch.ExecuteAsync(ct);
 2101
 2102            return batch.Results;
 4103        });
 104
 105    /// <summary>Runs an operation returning data, converting failures to envelope errors.</summary>
 106    protected static async Task<DataOutput<TResult>> GuardedAsync<TResult>(Func<Task<TResult>> operation)
 23107    {
 108        try
 23109        {
 23110            return DataOutput<TResult>.New.WithData(await operation());
 111        }
 0112        catch (OperationCanceledException)
 0113        {
 0114            throw;
 115        }
 2116        catch (Exception ex)
 2117        {
 2118            return Fail<TResult>(ex);
 119        }
 23120    }
 121
 122    /// <summary>Runs an operation with no payload, converting failures to envelope errors.</summary>
 123    protected static async Task<ProcessOutput> GuardedProcessAsync(Func<Task> operation)
 3124    {
 125        try
 3126        {
 3127            await operation();
 3128            return ProcessOutput.New;
 129        }
 0130        catch (OperationCanceledException)
 0131        {
 0132            throw;
 133        }
 0134        catch (Exception ex)
 0135        {
 0136            return ex is ConditionalCheckFailedException
 0137                ? ProcessOutput.New.WithError(ConcurrencyMessage)
 0138                : ProcessOutput.New.WithError($"{OperationFailedMessage} {ex.GetBaseException().Message}");
 139        }
 3140    }
 141
 142    /// <summary>Maps an exception to a data-output error envelope.</summary>
 2143    protected static DataOutput<TResult> Fail<TResult>(Exception ex) => ex switch
 2144    {
 1145        ConditionalCheckFailedException => DataOutput<TResult>.New.WithError(ConcurrencyMessage),
 1146        _ => DataOutput<TResult>.New.WithError($"{OperationFailedMessage} {ex.GetBaseException().Message}")
 2147    };
 148}