Skip to content

Conversation

@SoulPancake
Copy link
Member

@SoulPancake SoulPancake commented Nov 19, 2025

Description

What problem is being solved?

How is it being solved?

What changes are made to solve it?

References

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

Summary by CodeRabbit

  • New Features

    • Enhanced error reporting now exposes API error codes, messages, and operation names for better debugging.
    • Request IDs are now captured and included in error responses for tracing.
    • Error messages are formatted with operation context for improved clarity.
  • Tests

    • Added comprehensive error handling test suite validating various error scenarios across API operations.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR enhances error handling in the OpenFGA Java SDK by parsing API error responses, exposing structured error details (code, message, operation name), capturing request IDs from response headers, and formatting error messages with operation context. Comprehensive integration tests validate various error scenarios across multiple operations.

Changes

Cohort / File(s) Summary
Core error handling implementation
src/main/java/dev/openfga/sdk/errors/FgaError.java
Adds ApiErrorResponse nested class with code and message fields. Introduces private fields apiErrorMessage and operationName with public getters/setters. Extends getError() to parse JSON API error body, capture x-request-id header, and set operation name. Overrides getMessage() to return formatted message [operationName] apiErrorMessage(apiErrorCode) when available, otherwise delegates to superclass. Adds Jackson imports for JSON parsing.
Integration test suite
src/test-integration/java/dev/openfga/sdk/errors/FgaErrorIntegrationTest.java
Adds comprehensive integration test class with Testcontainers-based OpenFGA container setup. Includes 14 test methods validating error handling across write, read, check, and expand operations, verifying exception types, error codes/messages, request IDs, error message formatting, operation context in messages, and preservation of error details in stack traces.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SDK as FgaError.getError()
    participant Parser as ObjectMapper
    participant Error as FgaError instance

    Client->>SDK: receive HTTP error response
    SDK->>SDK: extract x-request-id header
    SDK->>Parser: parse JSON error body
    Parser-->>SDK: ApiErrorResponse (code, message)
    SDK->>Error: populate apiErrorMessage
    SDK->>Error: populate operationName
    SDK->>Error: set requestId from header
    Error->>Error: getMessage() formats<br/>[operationName] message(code)
    SDK-->>Client: FgaError with structured details
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • JSON parsing logic — New ObjectMapper usage for API error response parsing requires verification of error handling and null-safety
  • getMessage() override — Ensure the conditional formatting logic correctly handles all combinations of present/absent fields (operationName, apiErrorMessage, apiErrorCode)
  • Header extraction — Verify x-request-id capture is properly integrated into the error flow
  • Integration tests — Large test suite follows a consistent pattern, but each test method validates different error scenarios that should be spot-checked for accuracy against expected SDK behavior

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'feat: Improve error messaging' is vague and generic, failing to convey specific information about the actual changes made to error handling in the codebase. Make the title more specific by describing what was improved—e.g., 'feat: Add API error details and context to FgaError messages' or similar to clarify the actual implementation changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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.

❤️ Share

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

@SoulPancake SoulPancake marked this pull request as ready for review November 19, 2025 08:09
@SoulPancake SoulPancake requested a review from a team as a code owner November 19, 2025 08:09
Copilot AI review requested due to automatic review settings November 19, 2025 08:09
@codecov-commenter
Copy link

codecov-commenter commented Nov 19, 2025

Codecov Report

❌ Patch coverage is 62.29508% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 37.13%. Comparing base (33a8520) to head (26ec73f).

Files with missing lines Patch % Lines
src/main/java/dev/openfga/sdk/errors/FgaError.java 62.29% 15 Missing and 8 partials ⚠️

❌ Your project status has failed because the head coverage (37.13%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##               main     #260      +/-   ##
============================================
+ Coverage     36.89%   37.13%   +0.23%     
- Complexity     1187     1195       +8     
============================================
  Files           192      192              
  Lines          7410     7470      +60     
  Branches        854      862       +8     
============================================
+ Hits           2734     2774      +40     
- Misses         4553     4566      +13     
- Partials        123      130       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot finished reviewing on behalf of SoulPancake November 19, 2025 08:13
Copy link
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: 0

🧹 Nitpick comments (7)
src/main/java/dev/openfga/sdk/errors/FgaError.java (3)

24-30: ApiErrorResponse visibility and new error-detail fields look good; consider tightening the public surface

The addition of apiErrorMessage / operationName (with getters/setters) cleanly exposes structured error context to callers and aligns with the new integration tests’ expectations.

The nested ApiErrorResponse type, however, is declared public with public mutable fields but is only consumed internally in getError. That effectively leaks the backend’s wire-level error schema into the SDK’s public API surface without providing a first-class way to obtain instances of this type.

If you don’t intend SDK consumers to rely on ApiErrorResponse directly, consider making it an implementation detail:

-    public static class ApiErrorResponse {
-        public String code;
-        public String message;
-    }
+    private static class ApiErrorResponse {
+        public String code;
+        public String message;
+    }

This preserves JSON binding while keeping the public API focused on FgaError’s own fields (apiErrorCode, apiErrorMessage, operationName, etc.).

Also applies to: 176-190


5-6: JSON parsing and request-id extraction are correct; consider reusing ObjectMapper and avoiding System.err in library code

The flow to set operationName, parse the JSON response body into ApiErrorResponse, and extract the "x-request-id" header is sound and matches what the new integration tests assert.

Two refinements to consider:

  1. Reuse ObjectMapper instead of allocating per error

    Creating a new ObjectMapper on each error is relatively heavy and unnecessary here:

-        if (body != null && !body.trim().isEmpty()) {
-            ObjectMapper mapper = new ObjectMapper();
+        if (body != null && !body.trim().isEmpty()) {
+            // Reuse a single mapper instance for error parsing
+            ObjectMapper mapper = ERROR_OBJECT_MAPPER;

with a cached mapper:

+    private static final ObjectMapper ERROR_OBJECT_MAPPER = new ObjectMapper();

This keeps error handling lightweight, especially in high-error-rate scenarios.

  1. Avoid System.err.println in a reusable SDK

    Printing directly to System.err from library code can be noisy and hard to manage for consumers. It’s usually better to either:

    • Use the project’s logging abstraction, or
    • Silently ignore parse failures and rely on super.getMessage() + getResponseData() for diagnostics.

For example, if you have a logger available:

-            } catch (JsonProcessingException e) {
-                // Fall back, do nothing - log the exception for debugging
-                System.err.println("Failed to parse API error response JSON: " + e.getMessage());
-            }
+            } catch (JsonProcessingException e) {
+                // Fall back silently or log via the project logger if available
+                LOGGER.debug("Failed to parse API error response JSON", e);
+            }

(Adjust to whatever logging facility the rest of the SDK uses.)

Also applies to: 87-107


102-107: Request-id extraction logic is fine; consider centralizing the header name

Extracting "x-request-id" via headers.firstValue("x-request-id") and storing it on the error object is useful for debugging and matches the integration tests.

To avoid scattering magic header strings and to keep things consistent with FgaConstants.RETRY_AFTER_HEADER_NAME, consider introducing a constant for the request-id header (either here or in FgaConstants) and reusing it wherever needed in the future.

src/test-integration/java/dev/openfga/sdk/errors/FgaErrorIntegrationTest.java (4)

37-40: Consider loading auth-model.json from the classpath instead of a hard-coded filesystem path

Reading the model via:

authModelJson = Files.readString(Paths.get("src", "test-integration", "resources", "auth-model.json"));

ties the tests to a specific working directory layout. Using a classpath resource tends to be more robust across build tools and IDEs. For example:

var url = getClass().getClassLoader().getResource("auth-model.json");
assertNotNull(url, "auth-model.json should be on the test classpath");
authModelJson = Files.readString(Paths.get(url.toURI()));

and put auth-model.json on the test (or integration-test) classpath.


42-58: Per-test client/store setup is correct; you may be able to amortize it if tests grow

The initializeApi method correctly initializes OpenFgaClient, creates a store, and writes the authorization model before each test, ensuring isolation.

If this suite expands or becomes slow, you could consider:

  • Moving store creation + model write to a @BeforeAll method and reusing the same store across tests (since tests only read/write invalid data), or
  • Sharing OpenFgaClient and storeId at class level while resetting any mutable state as needed.

Not urgent, but a possible optimization if CI times become an issue.


212-231: Not-found error assertions are reasonable; consider asserting on operationName directly

testNotFoundError correctly:

  • Exercises a non-existent store path,
  • Asserts the cause is an FgaError (allowing for FgaApiNotFoundError or other subclasses),
  • Verifies message presence and core error metadata.

Given that operationName is now explicitly tracked, you might make the test a bit more robust and less dependent on message formatting by also checking:

assertEquals("getStore", exception.getOperationName());

rather than relying solely on substrings in getMessage().


251-278: Authentication/authorization error test is flexible; update comment to match behavior

testAuthenticationError intentionally accepts any FgaError subclass (not strictly FgaApiAuthenticationError), acknowledging in the code comment that the backend may return either 404 or 403/401 depending on version, and then checks for a sensible message, apiErrorCode, and requestId.

The assertions are appropriate; the only small mismatch is the leading comment:

// Test FgaApiAuthenticationError when store doesn't exist (403/401 from API)

Given the test is intentionally tolerant of both authentication and not-found cases, you might reword the comment to something like “Test that authentication/authorization related errors surface details when the store doesn’t exist” to better match the actual assertion strategy.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d93032f and c03e301.

📒 Files selected for processing (2)
  • src/main/java/dev/openfga/sdk/errors/FgaError.java (4 hunks)
  • src/test-integration/java/dev/openfga/sdk/errors/FgaErrorIntegrationTest.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/test-integration/java/dev/openfga/sdk/errors/FgaErrorIntegrationTest.java (4)
src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.java (1)
  • OpenFgaClient (23-1314)
src/main/java/dev/openfga/sdk/api/client/model/ClientTupleKey.java (1)
  • ClientTupleKey (9-69)
src/main/java/dev/openfga/sdk/api/client/model/ClientWriteRequest.java (1)
  • ClientWriteRequest (5-34)
src/main/java/dev/openfga/sdk/api/configuration/ClientConfiguration.java (1)
  • ClientConfiguration (8-137)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (java)
🔇 Additional comments (12)
src/main/java/dev/openfga/sdk/errors/FgaError.java (1)

192-200: getMessage override achieves the intended formatting; be aware of changed message shape

The new getMessage() implementation:

if (apiErrorMessage != null && !apiErrorMessage.isEmpty() && operationName != null) {
    String codePart = (apiErrorCode != null && !apiErrorCode.isEmpty()) ? " (" + apiErrorCode + ")" : "";
    return String.format("[%s] %s%s", operationName, apiErrorMessage, codePart);
} else {
    return super.getMessage();
}

correctly yields messages like [write] type 'invalid_type' not found (validation_error), which the integration tests assert on, and still falls back to super.getMessage() if structured fields are missing.

The only behavioral change to be mindful of is that, once apiErrorMessage and operationName are set, the formatted message no longer includes any extra context that might have been present in the original super.getMessage() (such as HTTP status or low-level detail). Callers that need that information can still access getCode(), getCause(), and getResponseData(), so this is likely acceptable, but it’s worth acknowledging as an intentional change in how messages are presented.

src/test-integration/java/dev/openfga/sdk/errors/FgaErrorIntegrationTest.java (11)

60-79: Validation error for invalid type is well-covered

testWriteValidationError cleanly verifies:

  • The exception type (FgaApiValidationError),
  • That the formatted message includes the backend message,
  • apiErrorCode, apiErrorMessage, operationName, and requestId.

This is a solid regression test for the new error-parsing behavior on write.


81-102: Validation error for invalid relation correctly asserts formatted message and metadata

testWriteValidationErrorWithInvalidRelation nicely checks:

  • Message contains both the operation name and specific relation error text,
  • apiErrorCode is populated,
  • operationName and requestId are set.

Good coverage of a distinct validation failure mode for the same operation.


104-130: Message formatting test directly validates the new getMessage contract

testErrorMessageFormattingWithAllFields explicitly asserts:

  • getMessage() format [operationName] apiErrorMessage (apiErrorCode),
  • Individual fields like operationName, apiErrorCode, apiErrorMessage, and requestId.

This is an important high-level contract test for the overridden getMessage() and the JSON parsing in FgaError.


132-152: Check operation wiring and error mapping are validated appropriately

testCheckValidationError ensures that:

  • check operations surface FgaApiValidationError,
  • operationName is "check" and reflected in the message,
  • apiErrorCode, apiErrorMessage, and requestId are set.

This guards against mislabeling the operation context when mapping errors from the low-level API.


154-183: Stack-trace and message detail preservation test is effective

testErrorDetailsAreNotLostInStackTrace verifies that:

  • The cause type remains FgaApiValidationError,
  • Both toString() and getMessage() contain the backend error details and operation/code,
  • getResponseData() is still populated.

This is a good safeguard against future changes that might accidentally strip detail when wrapping exceptions.


185-210: Batch write validation error behavior is covered

testMultipleTupleErrorsShowDetailedMessage ensures that, even when multiple tuples are invalid, the resulting FgaApiValidationError still:

  • Contains a useful message mentioning the operation,
  • Exposes apiErrorCode, apiErrorMessage, and requestId.

This is valuable coverage for non-singleton error scenarios.


233-249: Invalid-parameter behavior at the client layer is well tested

testInvalidParameterException ensures that:

  • A write with missing storeId and an empty request fails fast with FgaInvalidParameterException before any API call,
  • The message surfaces enough context about the bad parameter.

This is a good guardrail around client-side validation.


280-299: Read operation error mapping and context are validated correctly

testReadOperationError confirms that:

  • Invalid read requests result in FgaApiValidationError,
  • operationName is "read" (or at least present in the message),
  • apiErrorCode, apiErrorMessage, and requestId are set.

This nicely mirrors the write/check coverage for the read path.


301-320: Expand operation error behavior is covered analogously to other operations

testExpandOperationError exercises the expand path and verifies:

  • FgaApiValidationError is thrown,
  • operationName is "expand" (or present in the message),
  • apiErrorCode, apiErrorMessage, and requestId are populated.

Good symmetry across all major operations.


322-352: Different validation subcases sharing the same code are clearly distinguished

testDifferentErrorCodesAreSurfaced checks that:

  • Two different validation scenarios (invalid type vs invalid relation) both have the same apiErrorCode,
  • But produce distinct apiErrorMessage values.

This is a helpful regression test to ensure the SDK does not accidentally collapse distinct backend messages when mapping errors.


354-380: Operation-context in error messages is validated across write and check

testErrorMessageContainsOperationContext verifies that:

  • getMessage() for write and check operations includes [write] and [check] respectively, and
  • getOperationName() differs between those two failures.

This is a concise, high-level assertion that the operation context is reliably threaded through to the final exception messages.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR improves error messaging in the OpenFGA Java SDK by enhancing how error details are surfaced to developers. The key improvement is that error information (operation name, API error message, and error code) is now formatted into the exception message itself, making errors more discoverable without requiring developers to dig through stack traces.

  • Adds structured error message formatting with format [operationName] apiErrorMessage (apiErrorCode)
  • Extracts and parses API error responses to populate error details (code and message)
  • Adds comprehensive integration tests to verify error messaging across different operations

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

File Description
src/main/java/dev/openfga/sdk/errors/FgaError.java Adds JSON parsing of API error responses, new fields for operation name and API error message, and overrides getMessage() to format error details
src/test-integration/java/dev/openfga/sdk/errors/FgaErrorIntegrationTest.java Adds comprehensive integration tests covering various error scenarios (validation errors, not found errors, different operations) to verify error message formatting

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

error.setApiErrorMessage(resp.message);
} catch (JsonProcessingException e) {
// Fall back, do nothing - log the exception for debugging
System.err.println("Failed to parse API error response JSON: " + e.getMessage());
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

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

Using System.err.println() for error logging in production code is not a best practice. Consider using a proper logging framework (like SLF4J) or, if intentional for debugging purposes, consider whether this should be silently swallowed instead. Library code should avoid writing directly to stderr as it interferes with application-level logging configuration.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

I think it's not a good idea to introduce a logging framework dependency within the SDK
I wonder if we should even log this but we shouldn't swallow exceptions either
@jimmyjames Would like your thoughts

Choose a reason for hiding this comment

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

We should add a logger but not with this PR

Copy link
Member

Choose a reason for hiding this comment

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

Is there any problem with silently swallowing the error given that we should fallback to what the current behaviour is?

Copy link
Member Author

Choose a reason for hiding this comment

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

Is there any problem with silently swallowing the error given that we should fallback to what the current behaviour is?

There isn't but I was thinking more from a debugging perspective, But I guess it's not really required to log to do that when we need to.

@ewanharris ewanharris requested a review from Copilot November 25, 2025 17:02
Copilot finished reviewing on behalf of ewanharris November 25, 2025 17:05
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

Comment on lines +129 to +130
// Fall back, do nothing - log the exception for debugging
System.err.println("Failed to parse API error response JSON: " + e.getMessage());
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

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

Using System.err.println() for error logging is not suitable for production code. Consider using a proper logging framework (e.g., SLF4J) or removing the statement entirely if silent fallback is acceptable. Console output can interfere with application logs and is not configurable.

Copilot uses AI. Check for mistakes.
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.

5 participants