-
Notifications
You must be signed in to change notification settings - Fork 54
feat(java-sdk): implement individual tuple error handling for non-tra… #573
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
feat(java-sdk): implement individual tuple error handling for non-tra… #573
Conversation
…nsaction writes Addresses GitHub issue #99 by implementing proper error handling for non-transaction writes to match JavaScript and .NET SDK behavior. ### Key Changes: **New Model Classes:** - Add ClientWriteStatus enum with SUCCESS/FAILURE values - Add ClientWriteSingleResponse class for individual tuple write results - Extend ClientWriteResponse with new constructor supporting detailed results **Core Logic Updates:** - Completely rewrite writeNonTransaction method with parallel processing - Implement individual error isolation per chunk using CompletableFuture.allOf() - Add authentication error re-throwing to maintain existing behavior - Process writes and deletes in parallel chunks for improved performance **Error Handling Improvements:** - Individual tuple failures no longer stop entire operation - Each tuple gets SUCCESS/FAILURE status with optional error details - Authentication errors (FgaApiAuthenticationError) are properly re-thrown - Non-authentication errors are captured per tuple without stopping others **Test Coverage:** - Add comprehensive unit tests for new model classes - Update OpenFgaClientTest with mixed success/failure scenarios - Test authentication error handling and individual tuple status tracking **Configuration Updates:** - Register new template files in config.overrides.json - Maintain backward compatibility with existing API signatures ### Technical Details: The implementation uses CompletableFuture.allOf() for parallel chunk processing, with .exceptionally() handlers that distinguish between authentication errors (which should halt execution) and other errors (which should be captured per tuple). Empty chunk handling is properly implemented to avoid null pointer exceptions, and the chunksOf utility method handles edge cases correctly. ### Backward Compatibility: All existing API signatures are preserved. The new detailed response format is available through the new ClientWriteResponse constructor while maintaining the legacy ApiResponse-based constructor for transaction-based writes. Resolves: openfga/java-sdk#99
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughThis change introduces non-transactional write support to the Java SDK client, enabling per-tuple error handling and status reporting. It adds new model classes and enums to represent individual write results and statuses, implements chunked parallel writes with granular error wrapping, updates the response structure, and enhances related tests and documentation. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant OpenFgaClient
participant FGA_API
Client->>OpenFgaClient: write(request, options)
alt Transactional mode (default)
OpenFgaClient->>FGA_API: POST /write (all tuples)
alt Success
FGA_API-->>OpenFgaClient: 200 OK
OpenFgaClient-->>Client: ClientWriteResponse (all tuples SUCCESS)
else Failure
FGA_API-->>OpenFgaClient: Error
OpenFgaClient-->>Client: Exception thrown
end
else Non-transactional mode
loop For each chunk of tuples
OpenFgaClient->>FGA_API: POST /write (chunk)
alt Chunk Success
FGA_API-->>OpenFgaClient: 200 OK
OpenFgaClient-->>OpenFgaClient: Mark chunk tuples as SUCCESS
else Chunk Failure
FGA_API-->>OpenFgaClient: Error
OpenFgaClient-->>OpenFgaClient: Mark chunk tuples as FAILURE, wrap error
end
end
OpenFgaClient-->>Client: ClientWriteResponse (per-tuple SUCCESS/FAILURE)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
✨ Finishing Touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
…l writes - Update writeNonTransaction method to properly isolate chunk failures - Failed chunks now mark tuples as FAILURE while allowing other chunks to continue processing - Authentication errors are re-thrown to stop all processing as expected - Matches .NET SDK behavior where 'some will pass some will fail' for non-transactional operations - Each chunk processes independently with proper error boundary isolation - Resolves issue #99 for Java SDK non-transaction write error handling This ensures that individual chunk failures don't affect the processing of other chunks, allowing for proper partial success scenarios in non-transactional write operations.
- Fix compilation errors by replacing incorrect toTupleKey() calls - Use asTupleKey() for ClientTupleKey objects - Use asTupleKeyWithoutCondition() for ClientTupleKeyWithoutCondition objects - Fixes all 8 compilation errors in transaction-based writes, chunk processing, and utility methods - Resolves PR build failures while maintaining correct error handling logic
- Replace asTupleKeyWithoutCondition() calls with direct TupleKey creation - ClientWriteSingleResponse constructor expects TupleKey, not TupleKeyWithoutCondition - Create TupleKey objects manually using user, relation, and object fields - Fixes type compatibility errors in delete chunk processing and utility methods - Resolves compilation failures while preserving error handling logic
The Issue99 reproduction test was accidentally added to the template during development. This test was causing failures in PR builds and was not intended to be part of the permanent test suite. The test has been removed to ensure clean PR integration.
Adds detailed documentation explaining: - Transactional vs non-transactional modes - Success and failure scenarios for each mode - Exception handling behavior - Caller responsibilities for error handling - Implementation details for non-transactional error isolation This addresses the need for clear documentation of the complex error handling behavior introduced for non-transactional writes.
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.
Actionable comments posted: 4
🧹 Nitpick comments (6)
config/clients/java/template/src/main/api/client/model/ClientWriteStatus.java.mustache (1)
4-22
: Add Jackson annotations for stable JSON (de)serializationIf this enum is ever serialized/deserialized (e.g., in responses), prefer explicit @JsonValue/@JsonCreator to ensure "success"/"failure" round-trip, independent of ObjectMapper settings.
{{>licenseInfo}} package {{clientPackage}}.model; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; public enum ClientWriteStatus { SUCCESS("success"), FAILURE("failure"); private final String value; ClientWriteStatus(String value) { this.value = value; } + @JsonValue public String getValue() { return value; } + @JsonCreator + public static ClientWriteStatus fromValue(String v) { + for (ClientWriteStatus s : values()) { + if (s.value.equalsIgnoreCase(v)) { + return s; + } + } + throw new IllegalArgumentException("Unknown ClientWriteStatus: " + v); + } + @Override public String toString() { return value; } }config/clients/java/template/src/main/api/client/model/ClientWriteSingleResponse.java.mustache (1)
6-32
: UseThrowable
(notException
) and add small ergonomics helpers
- Using
Exception
forces wrapping of non-Exception throwables and loses fidelity. PreferThrowable
to carry original causes.- Add
isSuccess()
/isFailure()
to reduce enum comparisons at call sites.-public class ClientWriteSingleResponse { +public class ClientWriteSingleResponse { private final TupleKey tupleKey; private final ClientWriteStatus status; - private final Exception error; + private final Throwable error; - public ClientWriteSingleResponse(TupleKey tupleKey, ClientWriteStatus status) { + public ClientWriteSingleResponse(TupleKey tupleKey, ClientWriteStatus status) { this(tupleKey, status, null); } - public ClientWriteSingleResponse(TupleKey tupleKey, ClientWriteStatus status, Exception error) { + public ClientWriteSingleResponse(TupleKey tupleKey, ClientWriteStatus status, Throwable error) { this.tupleKey = tupleKey; this.status = status; this.error = error; } public TupleKey getTupleKey() { return tupleKey; } public ClientWriteStatus getStatus() { return status; } - public Exception getError() { + public Throwable getError() { return error; } + + public boolean isSuccess() { return status == ClientWriteStatus.SUCCESS; } + public boolean isFailure() { return status == ClientWriteStatus.FAILURE; } }config/clients/java/template/src/test/api/client/OpenFgaClientTest.java.mustache (1)
1313-1330
: Strengthen assertions for non-transaction failure detailsAdd checks for:
- Response status code (should be 200 for non-transaction mode).
- Error type on the failed tuple (e.g.,
FgaApiValidationError
) to ensure correct mapping.// Verify response structure assertEquals(3, response.getWrites().size()); assertEquals(0, response.getDeletes().size()); + assertEquals(200, response.getStatusCode()); // Check individual tuple statuses var writes = response.getWrites(); assertEquals(ClientWriteStatus.SUCCESS, writes.get(0).getStatus()); assertEquals(firstUser, writes.get(0).getTupleKey().getUser()); assertNull(writes.get(0).getError()); assertEquals(ClientWriteStatus.FAILURE, writes.get(1).getStatus()); assertEquals(failedUser, writes.get(1).getTupleKey().getUser()); assertNotNull(writes.get(1).getError()); + assertTrue(writes.get(1).getError() instanceof FgaApiValidationError); assertEquals(ClientWriteStatus.SUCCESS, writes.get(2).getStatus()); assertEquals(thirdUser, writes.get(2).getTupleKey().getUser()); assertNull(writes.get(2).getError());config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache (3)
397-441
: Doc nit: clarify retry semantics scopeMinor: in “Caller Responsibilities” for non-transactional mode, consider a brief note that retries should be issued only for tuples marked FAILURE, not re-sending entire chunks.
548-575
: DRY the write/delete chunk handling with a small helperThe success/failure mapping logic is duplicated for writes and deletes. Extract a private helper that:
- Accepts the tuple list, a request builder (writes/deletes), and a TupleKey mapper.
- Returns
CompletableFuture<List<ClientWriteSingleResponse>>
.This reduces cognitive load and maintenance surface.
Also applies to: 578-611
535-541
: Avoid mutating caller-provided options (optional)You mutate
writeOptions
by inserting headers. Consider copying headers into a localConfigurationOverride
instead of modifying the options instance to prevent side effects when callers reuse it.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
config/clients/java/config.overrides.json
(2 hunks)config/clients/java/template/settings.gradle.mustache
(1 hunks)config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache
(6 hunks)config/clients/java/template/src/main/api/client/model/ClientWriteResponse.java.mustache
(2 hunks)config/clients/java/template/src/main/api/client/model/ClientWriteSingleResponse.java.mustache
(1 hunks)config/clients/java/template/src/main/api/client/model/ClientWriteStatus.java.mustache
(1 hunks)config/clients/java/template/src/test/api/client/OpenFgaClientTest.java.mustache
(5 hunks)config/clients/java/template/src/test/api/client/model/ClientWriteSingleResponseTest.java.mustache
(1 hunks)config/clients/java/template/src/test/api/client/model/ClientWriteStatusTest.java.mustache
(1 hunks)
🔇 Additional comments (10)
config/clients/java/template/settings.gradle.mustache (1)
1-6
: LGTM: plugin repositories configuredAdding gradlePluginPortal() and mavenCentral() under pluginManagement is correct and commonly needed for plugin resolution.
config/clients/java/template/src/test/api/client/model/ClientWriteStatusTest.java.mustache (1)
9-19
: LGTM: covers getValue() and toString()The assertions validate both accessors for SUCCESS/FAILURE.
config/clients/java/config.overrides.json (2)
184-191
: Registered new client model templatesClientWriteSingleResponse and ClientWriteStatus entries look correct (paths and destination filenames align with clientPackage/model).
440-447
: Registered new test templatesClientWriteStatusTest and ClientWriteSingleResponseTest entries are correct and will ensure coverage is generated.
config/clients/java/template/src/test/api/client/model/ClientWriteSingleResponseTest.java.mustache (1)
12-16
: Unable to locate TupleKey template—please verify setter signatureI couldn’t find a
TupleKey.java.mustache
underconfig/clients/java/template/src/main/api/client/model/
. Please confirm whether the generatedTupleKey
builder defines:•
public TupleKey object(String object)
rather than
•public TupleKey _object(String _object)
Steps to validate:
- Locate the template file for
TupleKey
inconfig/clients/java/template/src/main/api/client/model/
.- Open it and check the fluent‐setter method declaration.
config/clients/java/template/src/test/api/client/OpenFgaClientTest.java.mustache (1)
1147-1154
: LGTM: validates the new per-tuple results for transactional writesSolid assertions on writes/deletes sizes, status, user, and error nullity.
config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache (4)
345-387
: Great Javadoc clarifying dual-mode behaviorClear, actionable documentation of transactional vs non-transactional semantics, including caller responsibilities. This will reduce misuse.
483-499
: Transactional path correctly constructs per-tuple success resultsMapping request tuples to SUCCESS results on a 2xx response is consistent with atomic behavior and aligns with tests.
613-632
: All-of reduction preserves input order; goodCollecting chunk futures in input order and joining post
allOf
preserves tuple order across parallel execution. The final aggregation is correct.
683-689
: Utility methods correctly return per-tuple SUCCESS responses
writeTuples
anddeleteTuples
now return detailed SUCCESS results which aligns with the new response shape.Also applies to: 723-731
config/clients/java/template/src/main/api/client/model/ClientWriteResponse.java.mustache
Show resolved
Hide resolved
config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache
Show resolved
Hide resolved
…uples - Fix .apiTokenIssuer to use FGA_API_TOKEN_ISSUER instead of FGA_TOKEN_ISSUER - Add duplicate tuple writes to demonstrate individual error handling in non-transactional mode
- Add mapping for settings.gradle.mustache in config.overrides.json - Ensures settings.gradle file is generated with pluginManagement block - Addresses Code Rabbit suggestion for complete Gradle project structure
This reverts commit 9761133.
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.
Looks good! Nice update of the docs and example, and fixing the example as well ❤️
Addresses GitHub issue openfga/java-sdk#99 by implementing proper error handling for non-transaction writes to match JavaScript and .NET SDK behavior.
Key Changes:
New Model Classes:
Core Logic Updates:
Error Handling Improvements:
Test Coverage:
Configuration Updates:
Technical Details:
The implementation uses CompletableFuture.allOf() for parallel chunk processing, with .exceptionally() handlers that distinguish between authentication errors (which should halt execution) and other errors (which should be captured per tuple).
Empty chunk handling is properly implemented to avoid null pointer exceptions, and the chunksOf utility method handles edge cases correctly.
Backward Compatibility:
All existing API signatures are preserved. The new detailed response format is available through the new ClientWriteResponse constructor while maintaining the legacy ApiResponse-based constructor for transaction-based writes.
Resolves: openfga/java-sdk#99
Description
What problem is being solved?
How is it being solved?
What changes are made to solve it?
References
Review Checklist
main
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Tests