Skip to content

fix: Throw DynamoPaginationException on pagination errors#71

Merged
Im5tu merged 2 commits intomainfrom
fix/pagination-and-cancellation
Mar 10, 2026
Merged

fix: Throw DynamoPaginationException on pagination errors#71
Im5tu merged 2 commits intomainfrom
fix/pagination-and-cancellation

Conversation

@Im5tu
Copy link
Copy Markdown
Owner

@Im5tu Im5tu commented Mar 9, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Pagination streams now surface pagination errors as exceptions instead of silently ending, ensuring failures are reported.
  • Tests

    • Added tests verifying automatic pagination across pages for queries and scans and asserting that error conditions propagate as exceptions.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

Error-path handling in DynamoDB paginated iterators now throws a new DynamoPaginationException instead of terminating the async stream. A new exception class was added, and tests were introduced to validate multi-page enumeration for QueryAllAsync and ScanAllAsync using DynamoRecord.

Changes

Cohort / File(s) Summary
Pagination extensions
src/Clients/Goa.Clients.Dynamo/DynamoExtensions.cs
Replaced error-path behavior in QueryAllAsync, ScanAllAsync, and BatchGetAllAsync to throw DynamoPaginationException(result.FirstError) instead of yield break. Added using Goa.Clients.Dynamo.Exceptions;. No public signatures changed.
Exception infrastructure
src/Clients/Goa.Clients.Dynamo/Exceptions/DynamoPaginationException.cs
Added new public DynamoPaginationException with a public Error property and two constructors (Error; Error + inner Exception). Constructs a descriptive message including error code and description.
Tests
tests/Clients/Goa.Clients.Dynamo.Tests/TypedExtensionTests.cs
Added tests verifying automatic pagination across multiple pages for QueryAllAsync and ScanAllAsync using DynamoRecord with mocked IDynamoClient responses; asserts items from successive pages are returned.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete. It lacks all four required checklist items (Tests, AOT compatibility, Documentation, Self-review) that are specified in the repository template. Add the checklist items from the template with appropriate status indicators (checked or unchecked) to complete the description.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: introducing exception throwing for pagination errors instead of silent truncation.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/pagination-and-cancellation

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Clients/Goa.Clients.Dynamo/DynamoExtensions.cs (1)

1-12: ⚠️ Potential issue | 🔴 Critical

Critical: DynamoItemReader type is undefined and required methods are missing from IDynamoClient.

The code in DynamoExtensions.cs calls client.QueryAsync(), client.ScanAsync(), and client.BatchGetItemAsync() with a DynamoItemReader<T> parameter, but:

  1. DynamoItemReader<T> is not defined anywhere in the codebase
  2. IDynamoClient interface has no overloads accepting DynamoItemReader<T>
  3. QueryResult<T> and ScanResult<T> generic types don't exist (only non-generic QueryResult and ScanResult exist)

You need to:

  • Define the DynamoItemReader<T> delegate (likely public delegate T DynamoItemReader<T>(ref System.Text.Json.Utf8JsonReader reader);)
  • Add generic overloads to IDynamoClient for typed operations
  • Create generic QueryResult<T> and ScanResult<T> types or update method signatures
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Clients/Goa.Clients.Dynamo/DynamoExtensions.cs` around lines 1 - 12, The
extension methods use a missing generic reader and typed results; define a
delegate public delegate T DynamoItemReader<T>(ref
System.Text.Json.Utf8JsonReader reader) and then add generic overloads to
IDynamoClient for QueryAsync<T>, ScanAsync<T>, and BatchGetItemAsync<T> that
accept the DynamoItemReader<T> and return or populate strongly-typed result
types; either introduce QueryResult<T> and ScanResult<T> (wrapping existing
non-generic QueryResult/ScanResult with an Items: IReadOnlyList<T>) or change
the extension signatures to map non-generic results into typed lists using the
reader—update the calls in DynamoExtensions.QueryAsync, ScanAsync and
Batch-related methods to use the new generic overloads and result types.
🧹 Nitpick comments (3)
tests/Clients/Goa.Clients.Dynamo.Tests/TypedExtensionTests.cs (2)

12-14: TestReader is a no-op stub - consider adding a clarifying comment.

The TestReader delegate ignores its input and returns an empty TestModel. This works because the mock returns pre-built QueryResult<TestModel> / ScanResult<TestModel> objects, bypassing the actual reader. A brief comment would clarify this is intentional for the mock setup.

📝 Suggested clarification
-    private static readonly DynamoItemReader<TestModel> TestReader = (ref System.Text.Json.Utf8JsonReader _) => new TestModel("", "");
+    // Stub reader - actual deserialization is bypassed by mock returning pre-built results
+    private static readonly DynamoItemReader<TestModel> TestReader = (ref System.Text.Json.Utf8JsonReader _) => new TestModel("", "");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Clients/Goa.Clients.Dynamo.Tests/TypedExtensionTests.cs` around lines
12 - 14, The TestReader delegate currently ignores its Utf8JsonReader input and
returns an empty TestModel; add a brief inline comment above the TestReader
declaration clarifying this is an intentional no-op used because mocks supply
pre-built QueryResult<TestModel> and ScanResult<TestModel> objects (so the
reader is never exercised in these tests), to prevent confusion when reading
TestReader and TestModel.

82-106: Good error handling test, but coverage could be expanded.

This test validates that DynamoPaginationException is thrown on error. Consider adding similar tests for:

  • BatchGetAllAsync and BatchGetAllAsync<T> (different retry logic with unprocessed keys)
  • Optionally, error cases for ScanAllAsync variants to ensure consistency

Would you like me to generate test cases for BatchGetAllAsync error handling scenarios?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Clients/Goa.Clients.Dynamo.Tests/TypedExtensionTests.cs` around lines
82 - 106, Add parallel tests that mirror
QueryAllAsync_T_StopsOnError_MidPagination for the batch-get paths: create new
tests for BatchGetAllAsync and BatchGetAllAsync<T> that use Mock<IDynamoClient>
to simulate a successful first response with some UnprocessedKeys (or a
non-empty RequestItems map) and then return an ErrorOr failure on the subsequent
BatchGet call; call the client methods BatchGetAllAsync (and the generic
BatchGetAllAsync<T>) with the same TestModel/TestReader and assert the same
exception behavior (throwing the expected pagination/retry-related exception),
and optionally add a similar ScanAllAsync error test to ensure consistent error
propagation across ScanAllAsync, QueryAllAsync, and BatchGetAllAsync.
src/Clients/Goa.Clients.Dynamo/DynamoExtensions.cs (1)

221-251: XML documentation is incomplete for new generic overloads.

The new generic methods (QueryAllAsync<T>, ScanAllAsync<T>, BatchGetAllAsync<T>) have minimal XML documentation compared to their non-generic counterparts. Consider adding <param> and <returns> tags for consistency with the existing methods.

📝 Example documentation for QueryAllAsync<T>
 /// <summary>
 /// Executes a typed DynamoDB Query operation with automatic pagination using an async iterator.
 /// </summary>
+/// <typeparam name="T">The type to deserialize items into.</typeparam>
+/// <param name="client">The DynamoDB client instance.</param>
+/// <param name="tableName">The name of the table to query.</param>
+/// <param name="itemReader">The reader delegate to deserialize items.</param>
+/// <param name="builder">Action to configure the query using QueryBuilder.</param>
+/// <param name="cancellationToken">Token to monitor for cancellation requests.</param>
+/// <returns>An async enumerable that yields typed items from all pages of the query result.</returns>
 public static async IAsyncEnumerable<T> QueryAllAsync<T>(...)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Clients/Goa.Clients.Dynamo/DynamoExtensions.cs` around lines 221 - 251,
The XML documentation for the generic paging overload QueryAllAsync<T> is
incomplete; add XML doc tags consistent with the non-generic overloads: include
a <typeparam name="T"> describing the item type, <param name="client">, <param
name="tableName">, <param name="itemReader">, <param name="builder">, and <param
name="cancellationToken"> describing each parameter, and a <returns> describing
the IAsyncEnumerable<T> of paginated items (and mention that it may throw
DynamoPaginationException). Update the same style for the other generic methods
ScanAllAsync<T> and BatchGetAllAsync<T> to match existing documentation
conventions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/Clients/Goa.Clients.Dynamo/DynamoExtensions.cs`:
- Around line 1-12: The extension methods use a missing generic reader and typed
results; define a delegate public delegate T DynamoItemReader<T>(ref
System.Text.Json.Utf8JsonReader reader) and then add generic overloads to
IDynamoClient for QueryAsync<T>, ScanAsync<T>, and BatchGetItemAsync<T> that
accept the DynamoItemReader<T> and return or populate strongly-typed result
types; either introduce QueryResult<T> and ScanResult<T> (wrapping existing
non-generic QueryResult/ScanResult with an Items: IReadOnlyList<T>) or change
the extension signatures to map non-generic results into typed lists using the
reader—update the calls in DynamoExtensions.QueryAsync, ScanAsync and
Batch-related methods to use the new generic overloads and result types.

---

Nitpick comments:
In `@src/Clients/Goa.Clients.Dynamo/DynamoExtensions.cs`:
- Around line 221-251: The XML documentation for the generic paging overload
QueryAllAsync<T> is incomplete; add XML doc tags consistent with the non-generic
overloads: include a <typeparam name="T"> describing the item type, <param
name="client">, <param name="tableName">, <param name="itemReader">, <param
name="builder">, and <param name="cancellationToken"> describing each parameter,
and a <returns> describing the IAsyncEnumerable<T> of paginated items (and
mention that it may throw DynamoPaginationException). Update the same style for
the other generic methods ScanAllAsync<T> and BatchGetAllAsync<T> to match
existing documentation conventions.

In `@tests/Clients/Goa.Clients.Dynamo.Tests/TypedExtensionTests.cs`:
- Around line 12-14: The TestReader delegate currently ignores its
Utf8JsonReader input and returns an empty TestModel; add a brief inline comment
above the TestReader declaration clarifying this is an intentional no-op used
because mocks supply pre-built QueryResult<TestModel> and ScanResult<TestModel>
objects (so the reader is never exercised in these tests), to prevent confusion
when reading TestReader and TestModel.
- Around line 82-106: Add parallel tests that mirror
QueryAllAsync_T_StopsOnError_MidPagination for the batch-get paths: create new
tests for BatchGetAllAsync and BatchGetAllAsync<T> that use Mock<IDynamoClient>
to simulate a successful first response with some UnprocessedKeys (or a
non-empty RequestItems map) and then return an ErrorOr failure on the subsequent
BatchGet call; call the client methods BatchGetAllAsync (and the generic
BatchGetAllAsync<T>) with the same TestModel/TestReader and assert the same
exception behavior (throwing the expected pagination/retry-related exception),
and optionally add a similar ScanAllAsync error test to ensure consistent error
propagation across ScanAllAsync, QueryAllAsync, and BatchGetAllAsync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eeb4acdb-17b2-4075-9055-c797d45e00a3

📥 Commits

Reviewing files that changed from the base of the PR and between 849faa9 and 3b75d8f.

📒 Files selected for processing (3)
  • src/Clients/Goa.Clients.Dynamo/DynamoExtensions.cs
  • src/Clients/Goa.Clients.Dynamo/Exceptions/DynamoPaginationException.cs
  • tests/Clients/Goa.Clients.Dynamo.Tests/TypedExtensionTests.cs

Strip QueryAllAsync<T>, ScanAllAsync<T>, and BatchGetAllAsync<T>
overloads and their tests — these belong to the typed API branch.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/Clients/Goa.Clients.Dynamo.Tests/TypedExtensionTests.cs (1)

10-75: Consider adding error scenario tests.

The PR objectives mention "typed extension test coverage for pagination error scenarios," but the tests only cover successful pagination. Consider adding tests that verify DynamoPaginationException is thrown when the client returns an error mid-pagination. Additionally, BatchGetAllAsync pagination is not covered.

Example error scenario test
[Test]
public async Task QueryAllAsync_ThrowsDynamoPaginationException_OnError()
{
    var mock = new Mock<IDynamoClient>();
    var error = Error.Failure("TestError", "Simulated pagination failure");

    mock.Setup(c => c.QueryAsync(It.IsAny<QueryRequest>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(error);

    var enumerable = mock.Object.QueryAllAsync("TestTable", _ => { });

    await Assert.ThrowsAsync<DynamoPaginationException>(async () => await enumerable.ToListAsync());
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Clients/Goa.Clients.Dynamo.Tests/TypedExtensionTests.cs` around lines
10 - 75, Add tests that cover pagination error paths: for QueryAllAsync,
ScanAllAsync and BatchGetAllAsync, mock IDynamoClient to return a successful
first page (with LastEvaluatedKey) then return an Error (Error.Failure) on the
subsequent QueryAsync/ScanAsync/BatchGetAsync call and assert that enumerating
the IAsyncEnumerable throws DynamoPaginationException; also add a test where the
first call immediately returns an Error and assert the same exception. Reference
QueryAllAsync, ScanAllAsync, BatchGetAllAsync, IDynamoClient, QueryAsync,
ScanAsync, BatchGetAsync, and DynamoPaginationException when locating where to
add these tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/Clients/Goa.Clients.Dynamo.Tests/TypedExtensionTests.cs`:
- Line 1: Remove the unused using for Goa.Clients.Dynamo.Exceptions in
TypedExtensionTests.cs (the imported symbol DynamoPaginationException is not
referenced); either delete the using directive or add a test that asserts
expected behavior using DynamoPaginationException (e.g., catching/Asserting it)
so the import is justified—look for the top-of-file using block and update the
file accordingly.

---

Nitpick comments:
In `@tests/Clients/Goa.Clients.Dynamo.Tests/TypedExtensionTests.cs`:
- Around line 10-75: Add tests that cover pagination error paths: for
QueryAllAsync, ScanAllAsync and BatchGetAllAsync, mock IDynamoClient to return a
successful first page (with LastEvaluatedKey) then return an Error
(Error.Failure) on the subsequent QueryAsync/ScanAsync/BatchGetAsync call and
assert that enumerating the IAsyncEnumerable throws DynamoPaginationException;
also add a test where the first call immediately returns an Error and assert the
same exception. Reference QueryAllAsync, ScanAllAsync, BatchGetAllAsync,
IDynamoClient, QueryAsync, ScanAsync, BatchGetAsync, and
DynamoPaginationException when locating where to add these tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 22fecb20-f97e-4a2e-8ff4-703d9bb2bf5c

📥 Commits

Reviewing files that changed from the base of the PR and between 3b75d8f and f21da56.

📒 Files selected for processing (2)
  • src/Clients/Goa.Clients.Dynamo/DynamoExtensions.cs
  • tests/Clients/Goa.Clients.Dynamo.Tests/TypedExtensionTests.cs

@@ -0,0 +1,76 @@
using Goa.Clients.Dynamo.Exceptions;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unused import.

Goa.Clients.Dynamo.Exceptions is imported but DynamoPaginationException is never used in these tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Clients/Goa.Clients.Dynamo.Tests/TypedExtensionTests.cs` at line 1,
Remove the unused using for Goa.Clients.Dynamo.Exceptions in
TypedExtensionTests.cs (the imported symbol DynamoPaginationException is not
referenced); either delete the using directive or add a test that asserts
expected behavior using DynamoPaginationException (e.g., catching/Asserting it)
so the import is justified—look for the top-of-file using block and update the
file accordingly.

@Im5tu Im5tu merged commit 263bbd9 into main Mar 10, 2026
3 checks passed
@Im5tu Im5tu deleted the fix/pagination-and-cancellation branch March 10, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant