Skip to content

Comments

feat: streaming APIExecutor#296

Open
SoulPancake wants to merge 3 commits intomainfrom
feat/streaming-raw-requests
Open

feat: streaming APIExecutor#296
SoulPancake wants to merge 3 commits intomainfrom
feat/streaming-raw-requests

Conversation

@SoulPancake
Copy link
Member

@SoulPancake SoulPancake commented Feb 19, 2026

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

  • 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

Release Notes

  • New Features

    • Introduced StreamingApiExecutor for executing HTTP requests to streaming endpoints not yet wrapped by the SDK.
    • Added support for both concrete and generic response types in streaming operations.
  • Documentation

    • Added comprehensive streaming endpoint documentation with code examples.
    • Updated API reference and usage guides for the new streaming capabilities.
  • Examples

    • Added new streaming example demonstrating per-object callbacks and error handling patterns.
  • Chores

    • Updated Java requirement from 11+ to 17+.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

Walkthrough

This PR introduces StreamingApiExecutor, a new component for executing HTTP requests to streaming endpoints not yet wrapped by the SDK. It includes refactored request building logic, comprehensive documentation, test coverage, and example implementations for streaming use cases.

Changes

Cohort / File(s) Summary
Documentation & Changelog
CHANGELOG.md, README.md, docs/ApiExecutor.md
Added entries documenting StreamingApiExecutor, its purpose, usage patterns with per-object callbacks, TypeReference support for generic types, and streaming lifecycle management including error handling and completion signals.
Example Configuration & Documentation
examples/README.md, examples/api-executor/README.md, examples/api-executor/Makefile, examples/api-executor/build.gradle
Updated build targets and run instructions to support streaming examples; incremented Java requirement to 17+; added comparison table for ApiExecutor vs StreamingApiExecutor; updated gradle task to execute StreamingApiExecutorExample and bumped SDK version to 0.9.6.
Streaming Example Implementation
examples/api-executor/src/main/java/dev/openfga/sdk/example/StreamingApiExecutorExample.java
New example demonstrating StreamingApiExecutor usage with concrete response types and TypeReference-based overloads; includes store bootstrap, relation tuple writes, streaming invocation with progress tracking, error handling, and cleanup.
Core Request Building Refactor
src/main/java/dev/openfga/sdk/api/client/ApiExecutor.java, src/main/java/dev/openfga/sdk/api/client/ApiExecutorRequestBuilder.java
Centralized HTTP request construction to ApiExecutorRequestBuilder; ApiExecutor now delegates to requestBuilder.buildHttpRequest(); RequestBuilder adds buildPath(), buildHttpRequest() with parameter substitution, URL encoding, and body serialization.
New Streaming API
src/main/java/dev/openfga/sdk/api/client/StreamingApiExecutor.java, src/main/java/dev/openfga/sdk/api/client/OpenFgaClient.java
Added StreamingApiExecutor class supporting Class and TypeReference<StreamResult> constructors; stream() overloads with optional error consumer; added streamingApiExecutor() factory methods to OpenFgaClient for both concrete and generic types.
Streaming API Tests
src/test/java/dev/openfga/sdk/api/client/StreamingApiExecutorTest.java
Comprehensive test suite validating happy-path streaming, error handling, request builder parity, null validations, TypeReference overload support, store-id auto-injection, and CompletableFuture lifecycle semantics.

Sequence Diagram

sequenceDiagram
    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)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • curfew-marathon
  • jimmyjames
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: streaming APIExecutor' clearly summarizes the main feature addition—a streaming executor for raw API requests—which is the central change throughout the pull request.

✏️ 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 feat/streaming-raw-requests

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
Copy link
Member Author

@copilot review

Copy link

Copilot AI commented Feb 19, 2026

@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.

Copy link
Contributor

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 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> and OpenFgaClient.streamingApiExecutor(...) factory methods (Class + TypeReference overloads).
  • Refactored request construction logic into ApiExecutorRequestBuilder (buildPath / buildHttpRequest) and updated ApiExecutor to 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-commenter
Copy link

codecov-commenter commented Feb 19, 2026

Codecov Report

❌ Patch coverage is 89.70588% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 37.93%. Comparing base (8fd7fe7) to head (de91742).

Files with missing lines Patch % Lines
...nfga/sdk/api/client/ApiExecutorRequestBuilder.java 86.11% 1 Missing and 4 partials ⚠️
...v/openfga/sdk/api/client/StreamingApiExecutor.java 93.10% 1 Missing and 1 partial ⚠️

❌ 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.
📢 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.

Copy link
Contributor

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 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
@SoulPancake SoulPancake force-pushed the feat/streaming-raw-requests branch from 0ccbd30 to 7bd70ff Compare February 20, 2026 07:22
@SoulPancake SoulPancake marked this pull request as ready for review February 20, 2026 07:25
@SoulPancake SoulPancake requested review from a team as code owners February 20, 2026 07:25
@dosubot
Copy link

dosubot bot commented Feb 20, 2026

Related Documentation

1 document(s) may need updating based on files changed in this PR:

OpenFGA's Space

StreamedListObjects Feature Overview
View 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)

[Accept] [Decline]

Note: You must be authenticated to accept/decline updates.

How did I do? Any feedback?  Join Discord

@SoulPancake SoulPancake changed the title feat: streaming raw requests feat: streaming APIExecutor Feb 20, 2026
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: 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 | 🟡 Minor

Pre-existing doc error: builder("POST", …) won't compile — first arg must be HttpMethod.

ApiExecutorRequestBuilder.builder(HttpMethod method, String path) expects a HttpMethod enum value; the existing example at line 1197 passes the raw String "POST", which is a type mismatch and would cause a compilation error if copied. The newly-added example at line 1253 correctly uses HttpMethod.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: all should be declared in .PHONY.

The all target (line 2) has no associated build artifact, so it should be in .PHONY to avoid stale-target issues. checkmake flags 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() returns this — there is no finalisation, no immutability enforcement, and no requirement to call it before send(). The Javadoc saying "Must be called before passing to ApiExecutor#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 under ApiExecutor only (line 114), but this behavior is in the shared ApiExecutorRequestBuilder.buildPath() and applies equally to StreamingApiExecutor. The streaming tests in StreamingApiExecutorTest.stream_autoInjectsStoreIdFromConfiguration confirm 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_SIZE uses integer division. If someone changes the constants such that TOTAL_OWNER_DOCUMENTS is not evenly divisible by WRITE_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: ArrayList in 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 storeId is absent, but only asserts ExecutionException without checking the wrapped cause. The failure occurs because buildPath() skips substitution of {store_id} when the store ID is null (lines 164–170 of ApiExecutorRequestBuilder), leaving a literal placeholder in the path. When ApiClient.requestBuilder() creates the URI with this malformed path, the exception is caught by StreamingApiExecutor.stream() (lines 130–135) and wrapped in ApiException. Adding an assertion to verify the cause type—for example, checking that ExecutionException.getCause() is an ApiException—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.

@SoulPancake SoulPancake linked an issue Feb 20, 2026 that may be closed by this pull request
1 task
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.

Add streaming variant of raw request method

3 participants