Conversation
WalkthroughThis PR introduces Changes
Sequence DiagramsequenceDiagram
participant Client as OpenFgaClient
participant Executor as StreamingApiExecutor
participant RequestBuilder as ApiExecutorRequestBuilder
participant Http as HttpClient
participant Parser as ObjectMapper
participant Consumer as User Consumer
Client->>Executor: streamingApiExecutor(MyResponse.class)
Executor->>Executor: Initialize with responseType
Client->>RequestBuilder: builder(HttpMethod, path)
RequestBuilder-->>Client: RequestBuilder instance
Client->>Executor: stream(requestBuilder, consumer, errorConsumer)
Executor->>RequestBuilder: buildHttpRequest(config, apiClient)
RequestBuilder->>RequestBuilder: Substitute path params
RequestBuilder->>RequestBuilder: Append query params
RequestBuilder->>Http: Create HttpRequest
Executor->>Http: Execute streaming request
Http-->>Executor: Response stream
loop For each streamed object
Executor->>Parser: Parse JSON to MyResponse
Parser-->>Executor: Parsed object
Executor->>Consumer: accept(object)
Consumer->>Consumer: Process object
end
alt Error during streaming
Http-->>Executor: Exception/Stream error
Executor->>Consumer: errorConsumer.accept(error)
end
Executor-->>Client: CompletableFuture<Void> (completed/failed)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
|
@copilot review |
|
@SoulPancake I've opened a new pull request, #297, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new low-level StreamingApiExecutor to let SDK consumers call streaming endpoints (e.g., streamed-list-objects) using the same “raw request builder” style as ApiExecutor, and updates docs/examples/tests accordingly.
Changes:
- Added
StreamingApiExecutor<T>andOpenFgaClient.streamingApiExecutor(...)factory methods (Class + TypeReference overloads). - Refactored request construction logic into
ApiExecutorRequestBuilder(buildPath/buildHttpRequest) and updatedApiExecutorto reuse it. - Added a streaming executor example plus expanded documentation and tests.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
src/main/java/dev/openfga/sdk/api/client/StreamingApiExecutor.java |
New streaming “raw request” executor built on BaseStreamingApi. |
src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.java |
Adds streamingApiExecutor(...) entry points. |
src/main/java/dev/openfga/sdk/api/client/ApiExecutorRequestBuilder.java |
Centralizes URL/path/query/header/body request building for both executors. |
src/main/java/dev/openfga/sdk/api/client/ApiExecutor.java |
Uses requestBuilder.buildHttpRequest(...) instead of duplicating logic. |
src/test/java/dev/openfga/sdk/api/client/StreamingApiExecutorTest.java |
New unit tests for streaming executor behaviors and guards. |
examples/api-executor/src/main/java/dev/openfga/sdk/example/StreamingApiExecutorExample.java |
New runnable example demonstrating streaming executor usage end-to-end. |
examples/api-executor/build.gradle |
Adds runStreaming task; bumps local jar reference. |
examples/api-executor/README.md |
Documents both executors and adds streaming usage guidance. |
examples/api-executor/Makefile |
Adds run-streaming target. |
examples/README.md |
Expands API-executor examples section to include streaming variant. |
docs/ApiExecutor.md |
Extends API Executor docs to cover StreamingApiExecutor as well. |
README.md |
Adds a top-level snippet showing how to call streaming endpoints via streamingApiExecutor. |
CHANGELOG.md |
Notes the addition of StreamingApiExecutor in Unreleased. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is ❌ Your project status has failed because the head coverage (37.93%) 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 #296 +/- ##
============================================
+ Coverage 37.70% 37.93% +0.22%
- Complexity 1236 1250 +14
============================================
Files 197 198 +1
Lines 7609 7632 +23
Branches 880 882 +2
============================================
+ Hits 2869 2895 +26
+ Misses 4601 4598 -3
Partials 139 139 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
feat: docs e.g. and changelog fix: changelog feat: cleanup feat: address comments feat: provide typereference overload example feat: nullable consumer feat: cleanup fix: async contract leak feat: refactor
0ccbd30 to
7bd70ff
Compare
|
Related Documentation 1 document(s) may need updating based on files changed in this PR: OpenFGA's Space StreamedListObjects Feature OverviewView Suggested Changes@@ -1,7 +1,7 @@
## SDK Support Overview
| SDK | StreamedListObjects Support | API Style | Example/Docs | Notes |
|----------|----------------------------|-------------------|---------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
-| Java | Temporarily unavailable | N/A | [Example (disabled)](https://github.com/openfga/java-sdk/blob/main/examples/streamed-list-objects/README.md) | The `streamedListObjects` method is private and not available for public use. Example and tests are disabled. Will be enabled in a future release. |
+| Java | Yes | Callback consumer | [Example](https://github.com/openfga/java-sdk/blob/main/examples/api-executor/README.md) | Streams results using consumer callback via `StreamingApiExecutor`. Delivers objects asynchronously as they arrive. |
| Python | Yes | Async generator | [Example](https://github.com/openfga/python-sdk/blob/fd04bc4b8525e536208e2091dce16edf2f220250/example/streamed-list-objects/README.md) | Both async and sync versions. Yields results as they arrive. |
| Go | Yes | Channel | [Example](https://github.com/openfga/go-sdk/blob/main/example/streamed_list_objects/main.go) | The `StreamedListObjects` method is public and available for use. Streams results using channels. |
| JS | Yes (>= v0.9.2-beta.1) | Async iterator | [Documentation](#streamed-list-objects) | Supported as of v0.9.2-beta.1. Streams results using async iterators. |
@@ -10,14 +10,31 @@
## Feature Details
### How StreamedListObjects Works
-Unlike ListObjects, which collects all results before returning and enforces a 1000-object limit, StreamedListObjects streams each object to the client as soon as it is available and has no pagination limit. This reduces memory usage, enables early termination, and improves performance for large or computed result sets ([Java example](https://github.com/openfga/java-sdk/blob/e45b1e45755f486d8dd0306fb01fc52c00a01682/examples/streamed-list-objects/README.md), [Go example](https://github.com/openfga/go-sdk/blob/e6dd3e6c12ecb6654531d5febe80404db4018c26/example/streamed_list_objects/README.md)).
+Unlike ListObjects, which collects all results before returning and enforces a 1000-object limit, StreamedListObjects streams each object to the client as soon as it is available and has no pagination limit. This reduces memory usage, enables early termination, and improves performance for large or computed result sets ([Java example](https://github.com/openfga/java-sdk/blob/main/examples/api-executor/README.md), [Go example](https://github.com/openfga/go-sdk/blob/e6dd3e6c12ecb6654531d5febe80404db4018c26/example/streamed_list_objects/README.md)).
### Java SDK
-> **NOTE:** The StreamedListObjects feature is currently not available for public use in the Java SDK. The `streamedListObjects` method is private, and the example and related tests are disabled. This feature will be enabled in a future release.
+The Java SDK provides streaming access via `StreamingApiExecutor`, which allows you to call the `/streamed-list-objects` endpoint (or any streaming endpoint) directly. The API delivers each object asynchronously to a consumer callback and returns a `CompletableFuture<Void>` that completes when the stream is exhausted.
-Previously, the Java SDK exposed StreamedListObjects via the `streamedListObjects` method on `OpenFgaClient`, streaming results asynchronously using a consumer callback and returning a `CompletableFuture<Void>`. However, this API is now private and not accessible to SDK users.
+**Example:**
+```java
+ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores/{store_id}/streamed-list-objects")
+ .body(new ListObjectsRequest().user("user:anne").relation("can_read").type("document"))
+ .build();
-For updates on availability, refer to the [Java SDK StreamedListObjects Example (disabled)](https://github.com/openfga/java-sdk/blob/main/examples/streamed-list-objects/README.md) and the SDK release notes.
+fgaClient.streamingApiExecutor(StreamedListObjectsResponse.class)
+ .stream(
+ request,
+ response -> System.out.println("Object: " + response.getObject()), // called per object
+ error -> System.err.println("Stream error: " + error.getMessage()) // optional
+ )
+ .thenRun(() -> System.out.println("Streaming complete"))
+ .exceptionally(err -> {
+ System.err.println("Fatal error: " + err.getMessage());
+ return null;
+ });
+```
+
+For more details, see the [Java SDK StreamingApiExecutor Example](https://github.com/openfga/java-sdk/blob/main/examples/api-executor/README.md) and [API Executor Documentation](https://github.com/openfga/java-sdk/blob/main/docs/ApiExecutor.md).
### Python SDK
The Python SDK provides both async and sync `streamed_list_objects` methods on `OpenFgaClient`. These yield each object as it arrives.
@@ -70,10 +87,10 @@
The .NET SDK does not currently support StreamedListObjects. There is an open feature request and ongoing work to add support ([.NET issue](https://github.com/openfga/dotnet-sdk/issues/110)). The current workaround is to call the `/streamed-list-objects` API directly.
## Benefits Over ListObjects
-StreamedListObjects removes the 1000-object pagination limit, streams results as they are available, reduces memory usage, enables early termination, and is better suited for large or computed result sets ([Java example](https://github.com/openfga/java-sdk/blob/e45b1e45755f486d8dd0306fb01fc52c00a01682/examples/streamed-list-objects/README.md), [Go example](https://github.com/openfga/go-sdk/blob/e6dd3e6c12ecb6654531d5febe80404db4018c26/example/streamed_list_objects/README.md)).
+StreamedListObjects removes the 1000-object pagination limit, streams results as they are available, reduces memory usage, enables early termination, and is better suited for large or computed result sets ([Java example](https://github.com/openfga/java-sdk/blob/main/examples/api-executor/README.md), [Go example](https://github.com/openfga/go-sdk/blob/e6dd3e6c12ecb6654531d5febe80404db4018c26/example/streamed_list_objects/README.md)).
## References and Further Reading
-- [Java SDK StreamedListObjects Example](https://github.com/openfga/java-sdk/blob/e45b1e45755f486d8dd0306fb01fc52c00a01682/examples/streamed-list-objects/README.md)
+- [Java SDK StreamingApiExecutor Example](https://github.com/openfga/java-sdk/blob/main/examples/api-executor/README.md)
- [Python SDK StreamedListObjects Example](https://github.com/openfga/python-sdk/blob/fd04bc4b8525e536208e2091dce16edf2f220250/example/streamed-list-objects/README.md)
- [Go SDK StreamedListObjects Example](https://github.com/openfga/go-sdk/blob/e6dd3e6c12ecb6654531d5febe80404db4018c26/example/streamed_list_objects/README.md)
- [JS SDK Feature Request](https://github.com/openfga/js-sdk/issues/236)Note: You must be authenticated to accept/decline updates. |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
README.md (1)
1196-1204:⚠️ Potential issue | 🟡 MinorPre-existing doc error:
builder("POST", …)won't compile — first arg must beHttpMethod.
ApiExecutorRequestBuilder.builder(HttpMethod method, String path)expects aHttpMethodenum value; the existing example at line 1197 passes the rawString "POST", which is a type mismatch and would cause a compilation error if copied. The newly-added example at line 1253 correctly usesHttpMethod.POST— the same fix should be applied here for consistency.📝 Proposed fix
-ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores/{store_id}/custom-endpoint") +ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores/{store_id}/custom-endpoint")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 1196 - 1204, The example uses ApiExecutorRequestBuilder.builder("POST", "/stores/{store_id}/custom-endpoint") which is a type mismatch; change the first argument to the HttpMethod enum (use HttpMethod.POST) to match the signature ApiExecutorRequestBuilder.builder(HttpMethod method, String path) so the sample compiles; update the example invocation accordingly and keep the rest of the builder chain (pathParam, queryParam, body, header, build) unchanged.
🧹 Nitpick comments (6)
examples/api-executor/Makefile (1)
1-1:allshould be declared in.PHONY.The
alltarget (line 2) has no associated build artifact, so it should be in.PHONYto avoid stale-target issues.checkmakeflags this as well.♻️ Proposed fix
-.PHONY: build run run-streaming run-openfga +.PHONY: all build run run-streaming run-openfga🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/api-executor/Makefile` at line 1, The .PHONY declaration in the Makefile is missing the all target, which can cause make to treat the phony all target as a real file; update the .PHONY line to include all alongside build, run, run-streaming, and run-openfga so the Makefile treats the all target as always out-of-date (i.e., add "all" to the .PHONY list in the Makefile).src/main/java/dev/openfga/sdk/api/client/ApiExecutorRequestBuilder.java (1)
119-125:build()is a no-op, but its JavaDoc claims to "finalise" the builder.
build()returnsthis— there is no finalisation, no immutability enforcement, and no requirement to call it beforesend(). The Javadoc saying "Must be called before passing toApiExecutor#send" is factually incorrect and misleading. Consider either (a) documenting the actual semantics (ceremonial/conventional step), or (b) enforcing immutability post-build()with a guard flag.♻️ Minimal doc fix (least disruptive)
- /** - * Finalises the builder. Must be called before passing to {`@link` ApiExecutor#send}. - * - * `@return` This builder instance - */ + /** + * Conventional terminal step in the fluent chain; returns this builder unchanged. + * Calling {`@link` ApiExecutor#send} or {`@link` StreamingApiExecutor#stream} without first + * calling {`@code` build()} is also valid — this method exists purely for readability. + * + * `@return` This builder instance + */ public ApiExecutorRequestBuilder build() { return this; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/dev/openfga/sdk/api/client/ApiExecutorRequestBuilder.java` around lines 119 - 125, The JavaDoc for ApiExecutorRequestBuilder.build() is misleading because build() currently does nothing; enforce immutability instead: add a private boolean built = false field to ApiExecutorRequestBuilder, set built = true in build(), update build() JavaDoc to state it locks the builder, and in every mutating method on ApiExecutorRequestBuilder (all setters/withX/header/body/etc.) add a guard that throws IllegalStateException("ApiExecutorRequestBuilder has been built and is immutable") if built is true; ensure ApiExecutor#send can still accept the builder and update tests/docs accordingly.examples/api-executor/README.md (1)
106-116: Clarify that{store_id}auto-substitution applies to both executors.The "SDK Features Applied" section lists
{store_id}substitution underApiExecutoronly (line 114), but this behavior is in the sharedApiExecutorRequestBuilder.buildPath()and applies equally toStreamingApiExecutor. The streaming tests inStreamingApiExecutorTest.stream_autoInjectsStoreIdFromConfigurationconfirm this. Consider adding a note or restructuring to avoid the impression that streaming requests don't get store-ID substitution.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/api-executor/README.md` around lines 106 - 116, The README incorrectly implies `{store_id}` substitution only applies to ApiExecutor; update the text to state that automatic `{store_id}` substitution is performed by ApiExecutorRequestBuilder.buildPath() and therefore applies to both ApiExecutor and StreamingApiExecutor (see StreamingApiExecutor and StreamingApiExecutorTest.stream_autoInjectsStoreIdFromConfiguration). Edit the "SDK Features Applied" section to either move the `{store_id}` bullet out of only ApiExecutor and make it global for both executors or add a clarifying note under StreamingApiExecutor that store-ID substitution is applied via ApiExecutorRequestBuilder.buildPath().examples/api-executor/src/main/java/dev/openfga/sdk/example/StreamingApiExecutorExample.java (1)
114-125: Integer division silently drops remainder tuples if constants change.
ownerBatches = TOTAL_OWNER_DOCUMENTS / WRITE_BATCH_SIZEuses integer division. If someone changes the constants such thatTOTAL_OWNER_DOCUMENTSis not evenly divisible byWRITE_BATCH_SIZE, the remainder tuples are silently dropped. Currently both are 100, so this is a non-issue, but a ceiling division or an assertion would be more robust for an example that users may copy-paste and adapt.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/api-executor/src/main/java/dev/openfga/sdk/example/StreamingApiExecutorExample.java` around lines 114 - 125, The batch loop uses integer division when computing ownerBatches (ownerBatches = TOTAL_OWNER_DOCUMENTS / WRITE_BATCH_SIZE) which drops remainder tuples; update the logic in StreamingApiExecutorExample to handle non‑divisible totals by either using ceiling division to compute ownerBatches (e.g., (TOTAL_OWNER_DOCUMENTS + WRITE_BATCH_SIZE - 1) / WRITE_BATCH_SIZE) or by adding an explicit tail pass to write the remaining TOTAL_OWNER_DOCUMENTS % WRITE_BATCH_SIZE tuples, and ensure the inner loop or tuple count uses the remaining count on the final batch so no documents are silently skipped.docs/ApiExecutor.md (1)
138-148: Minor:ArrayListin streaming callback example is not thread-safe.The example uses
new ArrayList<>()with a consumer callback that runs on the async HTTP thread. While this works correctly when.get()is called before reading (as in the test code), this particular snippet chains.thenRun()without.get(), which still provides happens-before guarantees for the continuation. Worth a brief comment in the example or using a thread-safe collection to avoid misleading copy-paste.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/ApiExecutor.md` around lines 138 - 148, The example in the StreamingApiExecutor snippet uses a non-thread-safe ArrayList (objects) with the async callback in streamingApiExecutor.stream; replace the ArrayList with a thread-safe collection (e.g., CopyOnWriteArrayList or Collections.synchronizedList) or add a comment to synchronize access inside the consumer to avoid concurrent modification when StreamedListObjectsResponse callbacks run on the HTTP thread; update the example that builds the request via ApiExecutorRequestBuilder and uses client.streamingApiExecutor(StreamedListObjectsResponse.class).stream(...) so readers copy a safe pattern.src/test/java/dev/openfga/sdk/api/client/StreamingApiExecutorTest.java (1)
339-352: Add cause assertion to document the failure source and improve test resilience.This test verifies that streaming fails when
storeIdis absent, but only assertsExecutionExceptionwithout checking the wrapped cause. The failure occurs becausebuildPath()skips substitution of{store_id}when the store ID is null (lines 164–170 ofApiExecutorRequestBuilder), leaving a literal placeholder in the path. WhenApiClient.requestBuilder()creates the URI with this malformed path, the exception is caught byStreamingApiExecutor.stream()(lines 130–135) and wrapped inApiException. Adding an assertion to verify the cause type—for example, checking thatExecutionException.getCause()is anApiException—would document the expected failure mode and make the test resilient to changes in exception handling.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/dev/openfga/sdk/api/client/StreamingApiExecutorTest.java` around lines 339 - 352, Update the test stream_storeIdRequired to also assert the wrapped cause type so the failure source is documented: after asserting ExecutionException is thrown from future.get(), retrieve the thrown exception's cause (ExecutionException.getCause()) and assert it is an instance of ApiException (or the specific ApiException subclass expected), referencing the test method stream_storeIdRequired and the code paths buildPath (ApiExecutorRequestBuilder) and StreamingApiExecutor.stream which wrap the underlying error into an ApiException.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/java/dev/openfga/sdk/api/client/ApiExecutorRequestBuilder.java`:
- Around line 160-187: buildPath can leave the literal "{store_id}" (or any
"{...}" token) unresolved when configuration is not a ClientConfiguration and no
path param was provided; update buildPath to detect any remaining "{...}"
placeholders after doing the ClientConfiguration substitution and pathParams
loop and throw a clear IllegalStateException naming the unresolved token(s) so
callers get an earlier, actionable error. Specifically, in
ApiExecutorRequestBuilder.buildPath add a post-substitution validation step that
scans pathBuilder for patterns like "{" + name + "}" (you can reuse the existing
replaceAll behavior) and if any remain, throw IllegalStateException with a
message listing the unresolved placeholders (reference symbols: buildPath,
pathParams, Configuration, ClientConfiguration, replaceAll,
StringUtil.urlEncode). Also update the build() JavaDoc or implementation
contract to clarify that callers must call build() before
ApiExecutor.send/StreamingApiExecutor.send or alternatively have send()
validate/require a built request; prefer adding the validation in buildPath as
above so send() will fail fast if build() wasn't called.
In `@src/main/java/dev/openfga/sdk/api/client/StreamingApiExecutor.java`:
- Around line 115-137: The stream(...) method currently only catches
FgaInvalidParameterException, IOException, and IllegalArgumentException so
runtime exceptions thrown by user-provided requestInterceptor in
ApiExecutorRequestBuilder can escape instead of returning a failed
CompletableFuture; update the catch to catch Exception (or add an extra
catch(Exception e)) around the call to configuration.assertValid() and
processStreamingResponse(requestBuilder.buildHttpRequest(...), ...) so any
RuntimeException from ApiExecutorRequestBuilder.requestInterceptor is wrapped in
an ApiException, passed to errorConsumer if present, and returned as
CompletableFuture.failedFuture, matching the pattern used by
StreamedListObjectsApi and BaseStreamingApi.
---
Outside diff comments:
In `@README.md`:
- Around line 1196-1204: The example uses
ApiExecutorRequestBuilder.builder("POST", "/stores/{store_id}/custom-endpoint")
which is a type mismatch; change the first argument to the HttpMethod enum (use
HttpMethod.POST) to match the signature
ApiExecutorRequestBuilder.builder(HttpMethod method, String path) so the sample
compiles; update the example invocation accordingly and keep the rest of the
builder chain (pathParam, queryParam, body, header, build) unchanged.
---
Nitpick comments:
In `@docs/ApiExecutor.md`:
- Around line 138-148: The example in the StreamingApiExecutor snippet uses a
non-thread-safe ArrayList (objects) with the async callback in
streamingApiExecutor.stream; replace the ArrayList with a thread-safe collection
(e.g., CopyOnWriteArrayList or Collections.synchronizedList) or add a comment to
synchronize access inside the consumer to avoid concurrent modification when
StreamedListObjectsResponse callbacks run on the HTTP thread; update the example
that builds the request via ApiExecutorRequestBuilder and uses
client.streamingApiExecutor(StreamedListObjectsResponse.class).stream(...) so
readers copy a safe pattern.
In `@examples/api-executor/Makefile`:
- Line 1: The .PHONY declaration in the Makefile is missing the all target,
which can cause make to treat the phony all target as a real file; update the
.PHONY line to include all alongside build, run, run-streaming, and run-openfga
so the Makefile treats the all target as always out-of-date (i.e., add "all" to
the .PHONY list in the Makefile).
In `@examples/api-executor/README.md`:
- Around line 106-116: The README incorrectly implies `{store_id}` substitution
only applies to ApiExecutor; update the text to state that automatic
`{store_id}` substitution is performed by ApiExecutorRequestBuilder.buildPath()
and therefore applies to both ApiExecutor and StreamingApiExecutor (see
StreamingApiExecutor and
StreamingApiExecutorTest.stream_autoInjectsStoreIdFromConfiguration). Edit the
"SDK Features Applied" section to either move the `{store_id}` bullet out of
only ApiExecutor and make it global for both executors or add a clarifying note
under StreamingApiExecutor that store-ID substitution is applied via
ApiExecutorRequestBuilder.buildPath().
In
`@examples/api-executor/src/main/java/dev/openfga/sdk/example/StreamingApiExecutorExample.java`:
- Around line 114-125: The batch loop uses integer division when computing
ownerBatches (ownerBatches = TOTAL_OWNER_DOCUMENTS / WRITE_BATCH_SIZE) which
drops remainder tuples; update the logic in StreamingApiExecutorExample to
handle non‑divisible totals by either using ceiling division to compute
ownerBatches (e.g., (TOTAL_OWNER_DOCUMENTS + WRITE_BATCH_SIZE - 1) /
WRITE_BATCH_SIZE) or by adding an explicit tail pass to write the remaining
TOTAL_OWNER_DOCUMENTS % WRITE_BATCH_SIZE tuples, and ensure the inner loop or
tuple count uses the remaining count on the final batch so no documents are
silently skipped.
In `@src/main/java/dev/openfga/sdk/api/client/ApiExecutorRequestBuilder.java`:
- Around line 119-125: The JavaDoc for ApiExecutorRequestBuilder.build() is
misleading because build() currently does nothing; enforce immutability instead:
add a private boolean built = false field to ApiExecutorRequestBuilder, set
built = true in build(), update build() JavaDoc to state it locks the builder,
and in every mutating method on ApiExecutorRequestBuilder (all
setters/withX/header/body/etc.) add a guard that throws
IllegalStateException("ApiExecutorRequestBuilder has been built and is
immutable") if built is true; ensure ApiExecutor#send can still accept the
builder and update tests/docs accordingly.
In `@src/test/java/dev/openfga/sdk/api/client/StreamingApiExecutorTest.java`:
- Around line 339-352: Update the test stream_storeIdRequired to also assert the
wrapped cause type so the failure source is documented: after asserting
ExecutionException is thrown from future.get(), retrieve the thrown exception's
cause (ExecutionException.getCause()) and assert it is an instance of
ApiException (or the specific ApiException subclass expected), referencing the
test method stream_storeIdRequired and the code paths buildPath
(ApiExecutorRequestBuilder) and StreamingApiExecutor.stream which wrap the
underlying error into an ApiException.
Description
What problem is being solved?
Introduces the StreamingApiExecutor to support OpenFGA streaming endpoints (like streamed-list-objects) and unifies the request-execution lifecycle.
Streaming Support: Adds StreamingApiExecutor for handling chunked JSON responses via a Consumer callback and CompletableFuture.
Unified Request Building: Centralizes path resolution and automatic {store_id} substitution into a shared ApiExecutorRequestBuilder.
How is it being solved?
What changes are made to solve it?
References
Review Checklist
mainSummary by CodeRabbit
Release Notes
New Features
StreamingApiExecutorfor executing HTTP requests to streaming endpoints not yet wrapped by the SDK.Documentation
Examples
Chores