Skip to content

feat: ApiExecutor for raw requests#176

Open
SoulPancake wants to merge 19 commits intomainfrom
feat/raw-requests-api-executor
Open

feat: ApiExecutor for raw requests#176
SoulPancake wants to merge 19 commits intomainfrom
feat/raw-requests-api-executor

Conversation

@SoulPancake
Copy link
Member

@SoulPancake SoulPancake commented Jan 28, 2026

Generated from SDK generator PR : openfga/sdk-generator#676

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

    • Added ApiExecutor to perform raw HTTP requests against the OpenFGA API without SDK's typed methods
    • Introduced fluent builder for constructing custom API requests with path, query, headers, and body parameters
    • Added response wrapper providing access to both typed and raw JSON response data
    • Exposed method on client to retrieve the executor instance
  • Documentation

    • Added comprehensive documentation and example project demonstrating raw request usage patterns
    • Updated README with new example references
  • Tests

    • Added test coverage for ApiExecutor, request builder, and response handling

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

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.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Introduces ApiExecutor feature enabling raw HTTP requests against OpenFGA APIs without typed SDK methods. Adds public classes ApiExecutor, ApiExecutorRequestBuilder, and ApiResponse. Includes internal ApiClient method for response wrapper support and public GetApiExecutor() on OpenFgaClient. Adds comprehensive example project and tests.

Changes

Cohort / File(s) Summary
Core ApiExecutor Implementation
src/OpenFga.Sdk/Client/ApiExecutor/ApiExecutor.cs, src/OpenFga.Sdk/Client/ApiExecutor/ApiExecutorRequestBuilder.cs, src/OpenFga.Sdk/Client/ApiExecutor/ApiResponse.cs
New public classes for executing raw API requests with fluent request building, typed and raw JSON response handling, and response metadata access.
Client Integration
src/OpenFga.Sdk/Client/Client.cs
Added lazy-loaded ApiExecutor instance and public GetApiExecutor() method; enhanced Dispose to clean up executor.
Supporting Infrastructure
src/OpenFga.Sdk/Api/OpenFgaApi.cs, src/OpenFga.Sdk/ApiClient/ApiClient.cs
Exposed internal API client via ApiClientInternal property and added SendRequestWithWrapperAsync method returning full response wrapper with raw content.
Test Coverage
src/OpenFga.Sdk.Test/Client/ApiExecutor/ApiExecutorTests.cs, src/OpenFga.Sdk.Test/Client/ApiExecutor/ApiExecutorRequestBuilderTests.cs, src/OpenFga.Sdk.Test/Client/ApiExecutor/ApiResponseTests.cs, src/OpenFga.Sdk.Test/OpenFga.Sdk.Test.csproj
Comprehensive unit tests for ApiExecutor, ApiExecutorRequestBuilder, and ApiResponse; added Testcontainers NuGet dependency.
Example Project
example/ApiExecutorExample/.gitignore, example/ApiExecutorExample/ApiExecutorExample.csproj, example/ApiExecutorExample/Makefile, example/ApiExecutorExample/Program.cs, example/ApiExecutorExample/README.md
Complete working example demonstrating store operations, authorization models, tuple handling, permission checks, raw JSON responses, and custom headers with Docker-based lifecycle management.
Documentation
CHANGELOG.md, README.md, example/README.md, src/OpenFga.Sdk/Client/IOpenFgaClient.cs
Updated changelog and README files with new feature descriptions; added example project documentation with prerequisites, quick start, and troubleshooting guides; minor formatting fix in interface declaration.
Minor Cleanup
src/OpenFga.Sdk/Model/TupleChange.cs
Simplified null checks in Equals and GetHashCode methods for Timestamp field (non-nullable DateTime).

Sequence Diagram

sequenceDiagram
    participant Client as OpenFgaClient
    participant Executor as ApiExecutor
    participant Builder as ApiExecutorRequestBuilder
    participant ApiClient as ApiClient
    participant HTTP as HttpClient
    participant Response as ApiResponse

    Client->>Client: GetApiExecutor() creates lazy instance
    Client->>Executor: new ApiExecutor(apiClient, config)
    
    Note over Client,Builder: Build Request
    Builder->>Builder: Of(method, path)
    Builder->>Builder: PathParam/QueryParam/Header/Body
    Builder->>Builder: Build() validates
    
    Note over Executor,HTTP: Send Request
    Executor->>Executor: SendAsync(request)
    Executor->>ApiClient: SendRequestWithWrapperAsync<T>()
    ApiClient->>ApiClient: BuildHeaders (merge defaults + custom)
    ApiClient->>HTTP: Send with auth, retries
    HTTP-->>ApiClient: HttpResponseMessage
    ApiClient-->>Executor: ResponseWrapper<T>
    
    Note over Executor,Response: Process Response
    Executor->>Response: FromHttpResponse(message, raw, data)
    Response-->>Executor: ApiResponse<T>
    Executor-->>Client: ApiResponse<T> with StatusCode, Headers, Data
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • evansims
  • rhamzeh
  • ewanharris
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.24% 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
Title check ✅ Passed The title 'feat: ApiExecutor for raw requests' clearly and concisely describes the main change: introduction of a new ApiExecutor feature for performing raw API requests.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/raw-requests-api-executor

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.

@socket-security
Copy link

socket-security bot commented Jan 28, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedtestcontainers@​3.10.09610090100100

View full report

@socket-security
Copy link

socket-security bot commented Jan 28, 2026

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
License policy violation: nuget microsoft.bcl.hashcode under Unicode-3.0

License: Unicode-3.0 - the applicable license policy does not allow this license (4) (THIRD-PARTY-NOTICES.TXT)

License: W3C-20150513 - the applicable license policy does not allow this license (4) (THIRD-PARTY-NOTICES.TXT)

From: src/OpenFga.Sdk.Test/OpenFga.Sdk.Test.csprojnuget/testcontainers@3.10.0nuget/microsoft.bcl.hashcode@1.1.1

ℹ Read more on: This package | This alert | What is a license policy violation?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Find a package that does not violate your license policy or adjust your policy to allow this package's license.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore nuget/microsoft.bcl.hashcode@1.1.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
License policy violation: nuget microsoft.extensions.logging.abstractions under Unicode-3.0

License: Unicode-3.0 - the applicable license policy does not allow this license (4) (THIRD-PARTY-NOTICES.TXT)

License: W3C-20150513 - the applicable license policy does not allow this license (4) (THIRD-PARTY-NOTICES.TXT)

License: LicenseRef-ietf - the applicable license policy does not allow this license (4) (THIRD-PARTY-NOTICES.TXT)

From: src/OpenFga.Sdk.Test/OpenFga.Sdk.Test.csprojnuget/testcontainers@3.10.0nuget/microsoft.extensions.logging.abstractions@6.0.4

ℹ Read more on: This package | This alert | What is a license policy violation?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Find a package that does not violate your license policy or adjust your policy to allow this package's license.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore nuget/microsoft.extensions.logging.abstractions@6.0.4. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
License policy violation: nuget system.buffers under Unicode-3.0

License: Unicode-3.0 - the applicable license policy does not allow this license (4) (THIRD-PARTY-NOTICES.TXT)

From: src/OpenFga.Sdk.Test/OpenFga.Sdk.Test.csprojnuget/testcontainers@3.10.0nuget/system.buffers@4.5.1

ℹ Read more on: This package | This alert | What is a license policy violation?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Find a package that does not violate your license policy or adjust your policy to allow this package's license.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore nuget/system.buffers@4.5.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Copy link
Contributor

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

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

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

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 an ApiExecutor feature that allows developers to make custom API requests to OpenFGA endpoints using a fluent builder pattern. The implementation provides both typed and raw JSON response handling while automatically leveraging the SDK's authentication, retry logic, and error handling infrastructure.

Changes:

  • Added ApiExecutor class for executing arbitrary API requests with automatic auth/retry handling
  • Added ApiExecutorRequestBuilder for fluent request construction with path params, query params, headers, and body
  • Added ApiResponse<T> class to encapsulate response data including status, headers, raw response, and typed data
  • Exposed internal ApiClient through OpenFgaApi to enable ApiExecutor functionality
  • Added comprehensive unit tests, integration tests, and documentation

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 31 comments.

Show a summary per file
File Description
src/OpenFga.Sdk/Client/Client.cs Adds ApiExecutor property with lazy initialization and disposal handling
src/OpenFga.Sdk/Client/ApiExecutor/ApiResponse.cs Response wrapper providing status, headers, raw and typed response data
src/OpenFga.Sdk/Client/ApiExecutor/ApiExecutorRequestBuilder.cs Fluent builder for constructing API requests with validation
src/OpenFga.Sdk/Client/ApiExecutor/ApiExecutor.cs Executes requests with typed or raw responses using SDK infrastructure
src/OpenFga.Sdk/ApiClient/ApiClient.cs Adds SendRequestWithWrapperAsync to expose raw response alongside typed data
src/OpenFga.Sdk/Api/OpenFgaApi.cs Exposes internal ApiClient for ApiExecutor usage
src/OpenFga.Sdk.Test/OpenFga.Sdk.Test.csproj Adds Testcontainers dependency for integration testing
src/OpenFga.Sdk.Test/Client/ApiExecutor/*.cs Comprehensive unit and integration tests for all ApiExecutor components
README.md Documents ApiExecutor usage with examples and feature descriptions
CHANGELOG.md Records new ApiExecutor feature in unreleased section

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

@SoulPancake SoulPancake changed the title feat: Api Executor for raw requests feat: ApiExecutor for raw requests Jan 29, 2026
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 101 out of 102 changed files in this pull request and generated 2 comments.


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

@SoulPancake SoulPancake marked this pull request as ready for review February 4, 2026 12:25
@SoulPancake SoulPancake requested review from a team as code owners February 4, 2026 12:25
@dosubot
Copy link

dosubot bot commented Feb 4, 2026

Related Documentation

Checked 5 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

Copy link

@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: 3

🤖 Fix all issues with AI agents
In `@example/ApiExecutorExample/Makefile`:
- Line 1: Add the missing run-all target to the .PHONY declaration so the
Makefile treats the run-all target as phony; update the .PHONY line (which
currently lists help start-openfga stop-openfga run clean) to include run-all to
prevent accidental conflicts with a filesystem entry named "run-all".

In `@example/ApiExecutorExample/README.md`:
- Line 66: Update the fenced code block that contains "=== OpenFGA ApiExecutor
Example ===" to include a language specifier (e.g., add ```text or ```console)
so the block renders consistently; locate the markdown fenced block in README.md
and replace the opening ``` with ```text (or ```console) ensuring the closing
``` remains unchanged.

In `@src/OpenFga.Sdk/Api/OpenFgaApi.cs`:
- Around line 44-48: The ApiClientInternal property will be overwritten by
codegen; remove it from the generated OpenFgaApi class and instead add it to a
new partial class declaration (e.g., class OpenFgaApi in OpenFgaApi.partial.cs)
in the same namespace so the generated class and your partial class merge at
compile time; implement the internal ApiClientInternal => _apiClient property in
that partial file and ensure the new file is kept outside the generator's
overwrite scope (or add the generated file to the generator ignore list if you
intend to fully manage it manually).
🧹 Nitpick comments (6)
example/README.md (1)

7-18: Documentation additions look good.

Minor grammar nit: "bare bones" on line 8 should be hyphenated as "bare-bones" when used as a compound adjective.

📝 Suggested fix
 **Example 1:**
-A bare bones example. It creates a store, and runs a set of calls against it including creating a model, writing tuples and checking for access.
+A bare-bones example. It creates a store, and runs a set of calls against it including creating a model, writing tuples and checking for access.
src/OpenFga.Sdk/ApiClient/ApiClient.cs (1)

127-153: Header validation is bypassed for additionalHeaders.

The SendRequestWithWrapperAsync method calls BuildHeaders with null for options (line 135), then manually merges additionalHeaders (lines 138-142). This bypasses the Configuration.ValidateHeaders call that occurs in BuildHeaders for per-request headers.

Additionally, the manual merge allows additionalHeaders to override the Authorization header set by the SDK's authentication flow. While this may be intentional for advanced use cases, it could also inadvertently allow callers to corrupt authentication.

Consider:

  1. Validating additionalHeaders before merging
  2. Preventing override of security-sensitive headers like Authorization unless explicitly intended
♻️ Suggested validation
     internal async Task<ResponseWrapper<TRes>> SendRequestWithWrapperAsync<TReq, TRes>(
         RequestBuilder<TReq> requestBuilder,
         string apiName,
         IDictionary<string, string>? additionalHeaders = null,
         CancellationToken cancellationToken = default) {
         var sw = Stopwatch.StartNew();

         var authToken = await GetAuthenticationTokenAsync(apiName);
         var mergedHeaders = BuildHeaders(_configuration, authToken, null);

+        // Validate additional headers
+        Configuration.Configuration.ValidateHeaders(additionalHeaders, "additionalHeaders");
+
         // Merge additional headers (from ApiExecutor) with auth headers
         if (additionalHeaders != null) {
             foreach (var header in additionalHeaders) {
+                // Optionally protect Authorization header from being overwritten
+                // if (string.Equals(header.Key, "Authorization", StringComparison.OrdinalIgnoreCase)) continue;
                 mergedHeaders[header.Key] = header.Value;
             }
         }
src/OpenFga.Sdk/Client/Client.cs (1)

61-66: Minor: Redundant null-conditional operator.

On line 63, _apiExecutor.Value?.Dispose() uses a null-conditional operator, but Lazy<T>.Value will never be null when IsValueCreated is true. This is harmless but slightly misleading.

🔧 Optional simplification
 public void Dispose() {
     if (_apiExecutor.IsValueCreated) {
-        _apiExecutor.Value?.Dispose();
+        _apiExecutor.Value.Dispose();
     }
     api.Dispose();
 }
example/ApiExecutorExample/Makefile (1)

17-19: Fixed sleep duration may cause flaky behavior.

A fixed 3-second sleep may not be enough on slower systems or may be unnecessarily long on fast systems. Consider a retry loop with the health check instead.

🔧 Suggested improvement with retry loop
 start-openfga:
 	`@echo` "Starting OpenFGA server on localhost:8080..."
 	`@docker` run -d --name openfga-example -p 8080:8080 openfga/openfga:latest run
 	`@echo` "Waiting for OpenFGA to be ready..."
-	`@sleep` 3
-	`@curl` -s http://localhost:8080/healthz || (echo "OpenFGA failed to start" && exit 1)
+	`@for` i in 1 2 3 4 5 6 7 8 9 10; do \
+		curl -s http://localhost:8080/healthz && break || sleep 1; \
+	done || (echo "OpenFGA failed to start within 10 seconds" && exit 1)
 	`@echo` "✅ OpenFGA is ready!"
src/OpenFga.Sdk/Client/ApiExecutor/ApiExecutor.cs (1)

83-115: Non-generic SendAsync will fail if response is not valid JSON.

The use of JsonElement as an intermediate type means deserialization is attempted, which will throw if the response body is not valid JSON (e.g., plain text error messages or empty responses). If the goal is truly "raw" response handling, consider catching deserialization errors or using a different approach that doesn't require JSON parsing.

src/OpenFga.Sdk.Test/Client/ApiExecutor/ApiExecutorTests.cs (1)

298-327: Consider using mocked HttpClient for consistency.

These tests create OpenFgaClient without a mocked HttpClient. While they work correctly (the null request test throws before network access, and the singleton test doesn't make requests), using mocked clients would be more consistent with the other tests and prevent any accidental network calls if the implementation changes.

Copy link
Member

@rhamzeh rhamzeh left a comment

Choose a reason for hiding this comment

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

This SDK already includes an early version of the API executor called RequestBuilder + SendRequestAsync that handles the retry logic, auth and standardizes the interface, e.g.

var requestBuilder = new RequestBuilder<BatchCheckRequest> {
    Method = new HttpMethod("POST"),
    BasePath = _configuration.BasePath,
    PathTemplate = "/stores/{store_id}/batch-check",
    PathParameters = pathParams,
    Body = body,
    QueryParameters = queryParams,
};

return await _apiClient.SendRequestAsync<BatchCheckRequest, BatchCheckResponse>(requestBuilder,
    "BatchCheck", options, cancellationToken);

What the API Executor should be doing is either building on top of that work fully or replacing it. Currently, it is building on top of RequestBuilder, but not fully.

It is duplicating the logic by introducing ApiExecutorRequestBuilder, that is separate from RequestBuilder, duplicating some of the work.

It should just be an improved iteration/abstraction on top of RequestBuilder + SendRequestAsync.

Basically what I expect to see is:

  1. OpenFgaApi using ApiExecutor for all API interactions instead of directly calling RequestBuilder (if it offers a material improvement), otherwise enhancing RequestBuilder to include what is needed.
  2. The ApiExecutor should be part of the ApiClient, rather than Client. The difference is that ApiClient is the core building block that both OpenFgaApi and Client share and the executor belong there alongside/replacing RequestBuilder
  3. I would expect it to have a streaming variant that works for StreamedListObjects too, but I'm OK with that being a separate PR

@SoulPancake
Copy link
Member Author

@rhamzeh
Thanks a lot!
Do you think it would be worth adding the fluent builder methods for method chaining in the existing RequestBuilder
( These are all non-breaking ) I believe they are

  1. cleaner to use as a dev
  2. consistent with the other SDKs ( Go,Java )

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.

2 participants