Skip to content

Conversation

@rhamzeh
Copy link
Member

@rhamzeh rhamzeh commented Sep 15, 2025

Description

This PR adds support for sending custom headers per request.

It is complete, however it's kept in draft as it will have many conflicts with the other Go SDK PRs that will need to be resolved once the others are merged.

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

    • Support per-request custom HTTP headers across SDK calls via request options.
    • Configure default headers applied to every request; per-request headers override defaults.
  • Bug Fixes

    • Default headers no longer overwrite existing request headers.
  • Documentation

    • Added Custom Headers guidance and examples in README.
    • Updated Unreleased changelog: “feat: add support for custom headers per request.”
  • Tests

    • Added extensive test coverage validating header precedence and propagation.
  • Examples

    • Example updated to show sending a request with a custom X-Request-ID header.

@coderabbitai
Copy link

coderabbitai bot commented Sep 15, 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.

Walkthrough

Adds per-request custom HTTP headers support via a new RequestOptions type and Options methods across request builders. Merges per-request headers with default headers without overriding existing values. Updates client propagation, documentation, examples, and adds comprehensive tests. Also adjusts prepareRequest to apply default headers only when not already set.

Changes

Cohort / File(s) Summary
Public per-request options API
api_open_fga.go, client/client.go
Introduces RequestOptions (Headers, MaxRetry, MinWaitInMs); adds options field and fluent Options(...) methods to API request types; aliases RequestOptions in client; embeds it into client option structs; propagates per-request options into all SDK calls.
Header merge behavior
api_client.go
Changes default header application to conditional: apply cfg.DefaultHeaders only if header not already set, preventing overrides by defaults.
Tests: SDK and client headers
api_headers_test.go, client/client_headers_test.go
Adds end-to-end tests covering default headers, per-request overrides, empty/nil header scenarios, and propagation across many endpoints; uses mock server to capture/verify headers.
Docs and examples
CHANGELOG.md, README.md, example/example1/example1.go
Documents custom headers (default and per-request) and updates example to show per-request header usage; adds unreleased changelog entry.
Generator manifest
.openapi-generator/FILES
Registers new test files in the manifest.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Caller
  participant C as High-level Client
  participant SDK as SDK API (api_open_fga.go)
  participant HTTP as HTTP Layer (api_client.go)
  participant S as OpenFGA Server

  Dev->>C: Invoke Operation(options.Headers, default headers configured)
  Note over C: Build request body<br/>Assemble RequestOptions (embedded)

  C->>SDK: api.Xxx(ctx).Options(RequestOptions).Execute(...)
  Note over SDK: Merge per-request headers into local header map

  SDK->>HTTP: prepareRequest(req, headerParams)
  Note over HTTP: Apply cfg.DefaultHeaders only if header not already set

  HTTP->>S: HTTP request with final headers
  S-->>HTTP: Response
  HTTP-->>SDK: Response
  SDK-->>C: Result
  C-->>Dev: Result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning Most modifications are in-scope for adding per-request header support, but the diff also introduces additional elements beyond the linked issue's header-only objective: RequestOptions gained MaxRetry and MinWaitInMs fields that are not visibly wired into per-request retry behavior, the public API surface was adjusted via a ClientRequestOptions → RequestOptions alias change, and README contains duplicated "Custom Headers" sections which look accidental; these items represent scope creep or incidental documentation noise relative to the original issue. Recommend either removing or extracting the MaxRetry/MinWaitInMs and related type-alias changes into a separate PR (or update the PR description to explicitly include and justify them), wire and test the retry/connection fields if they are intended to be functional now, and remove the duplicate README sections before merging.
Docstring Coverage ⚠️ Warning Docstring coverage is 21.21% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title is concise and accurately highlights the main change (support for per-request headers) implemented across the API and client layers; the added RequestOptions type and Options(...) methods make the header capability clear from the title. The appended phrase "and connection options" corresponds to the new MaxRetry/MinWaitInMs fields present in RequestOptions, which are included in the diff even though they are not clearly wired into per-request retry logic, but this does not obscure the primary purpose of the PR. Overall the title is relevant and readable for teammates scanning history.
Linked Issues Check ✅ Passed The changes satisfy the linked issue #236: RequestOptions.Headers was added, per-request Options(...) methods were introduced on request builders, header values are applied to outgoing requests (with merge/override semantics), the client was updated to propagate per-request options, and tests plus README/example verify the behavior, collectively implementing the requested per-request custom HTTP header capability. These code and test changes directly address the objective of allowing callers to inject custom headers per API call.

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.

@codecov-commenter
Copy link

codecov-commenter commented Sep 15, 2025

Codecov Report

❌ Patch coverage is 96.47059% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 33.81%. Comparing base (70e3fe7) to head (c8ebc36).

Files with missing lines Patch % Lines
api_open_fga.go 94.64% 5 Missing and 1 partial ⚠️
client/client.go 97.85% 2 Missing and 1 partial ⚠️

❌ Your project status has failed because the head coverage (33.81%) 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     #233      +/-   ##
==========================================
+ Coverage   28.29%   33.81%   +5.52%     
==========================================
  Files         111      111              
  Lines       12000    12223     +223     
==========================================
+ Hits         3395     4133     +738     
+ Misses       8325     7740     -585     
- Partials      280      350      +70     

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

@rhamzeh rhamzeh marked this pull request as ready for review September 18, 2025 14:04
@rhamzeh rhamzeh requested review from a team as code owners September 18, 2025 14:04
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
client/client.go (4)

1032-1047: WriteAuthorizationModelExecute can nil-deref Body

Add a nil check before dereferencing.

 func (client *OpenFgaClient) WriteAuthorizationModelExecute(request SdkClientWriteAuthorizationModelRequestInterface) (*ClientWriteAuthorizationModelResponse, error) {
     storeId, err := client.getStoreId(request.GetStoreIdOverride())
     if err != nil {
         return nil, err
     }
+    if request.GetBody() == nil {
+        return nil, FgaRequiredParamError{param: "body"}
+    }
     requestOptions := RequestOptions{}
     if request.GetOptions() != nil {
         requestOptions = request.GetOptions().RequestOptions
     }

1631-1641: WriteExecute: add nil Body guard to avoid panic

Multiple dereferences of request.GetBody() assume it’s set. Guard early.

 func (client *OpenFgaClient) WriteExecute(request SdkClientWriteRequestInterface) (*ClientWriteResponse, error) {
     options := request.GetOptions()
+    if request.GetBody() == nil {
+        return nil, FgaRequiredParamError{param: "body"}
+    }
     transactionOptionsSet := options != nil && options.Transaction != nil

1774-1778: Bug: wrong chunk-size variable used for deletes

writeChunkSize is used when slicing deletes. Use deleteChunkSize; otherwise future divergence between the two sizes will slice incorrectly.

-            end := int(math.Min(float64(i+writeChunkSize), float64(len(request.GetBody().Deletes))))
+            end := int(math.Min(float64(i+deleteChunkSize), float64(len(request.GetBody().Deletes))))

2978-2986: Compile-time issue: taking address of field on a temporary

&fgaSdk.NewContextualTupleKeys(...).TupleKeys is not addressable. Bind to a variable first and take its address.

-    body := fgaSdk.ListUsersRequest{
+    tupleKeys := fgaSdk.NewContextualTupleKeys(contextualTuples).TupleKeys
+    body := fgaSdk.ListUsersRequest{
         Object:               request.GetBody().Object,
         Relation:             request.GetBody().Relation,
         UserFilters:          request.GetBody().UserFilters,
-        ContextualTuples:     &fgaSdk.NewContextualTupleKeys(contextualTuples).TupleKeys,
+        ContextualTuples:     &tupleKeys,
         Context:              request.GetBody().Context,
         AuthorizationModelId: authorizationModelId,
     }
🧹 Nitpick comments (9)
CHANGELOG.md (1)

5-5: Clarify scope: headers shipped; per-request retry options not wired

Entry reads well. If per-request “connection options” (MaxRetry/MinWaitInMs) won’t ship in this release, call that out (or drop mention elsewhere) to avoid implying they’re active.

README.md (1)

220-259: Docs compile/use-style nits

  • Keep import style consistent. Other sections use a dot import; this snippet uses the client. alias. Prefer one style across the README.
  • Consider noting that User-Agent isn’t overridden by per-request headers (current code sets it unconditionally), to avoid confusion.

Example (alias style):

import (
  "context"
  client "github.com/openfga/go-sdk/client"
)

fgaClient, err := client.NewSdkClient(&client.ClientConfiguration{ /* ... */ })
api_open_fga.go (1)

1045-1047: Factor header application to reduce duplication (optional)

Consider a small helper to apply RequestOptions.Headers to a map[string]string to cut repetition and future drift.

example/example1/example1.go (2)

239-241: Fix format string: don’t use %w with a string

%w is for wrapping errors and should be used with an error, not err.Error(). Use %v and pass err.

-    fmt.Printf("Failed due to: %w\n", err.Error())
+    fmt.Printf("Failed due to: %v\n", err)

87-87: Incorrect Println formatting

Println ignores formatting verbs. Use Printf and a proper verb.

-    fmt.Println("Current Store Name: %v\n" + currentStore.Name)
+    fmt.Printf("Current Store Name: %s\n", currentStore.Name)
api_headers_test.go (2)

25-26: Gitleaks false positive: annotate the test ULID

The constant resembles a key to scanners. Add an inline allow annotation to quiet false positives in CI.

-    apiTestStoreId           = "01H0H015178Y2V4CX10C2KGHF4"
+    apiTestStoreId           = "01H0H015178Y2V4CX10C2KGHF4" // gitleaks:allow - test ULID, not a secret

272-315: Optional: make the BatchCheck stub match the real shape

Consider returning {"result": {"test-correlation-id": {"allowed": true}}} to mirror the API more closely, reducing future drift.

client/client_headers_test.go (1)

118-156: Great coverage of per-request vs default headers across client methods

Thorough checks for overrides, absence, and empty headers. Consider adding one test that sets Transaction.Disable = true with small MaxPerChunk to ensure headers propagate across chunked writes.

Also applies to: 157-186, 187-218, 219-246, 248-322, 324-364, 366-405, 407-447, 449-494, 496-540, 542-579, 580-629, 631-770, 772-810, 812-892

client/client.go (1)

1446-1453: Minor DRY opportunity: extract helper to pull RequestOptions

Many methods repeat the same pattern to extract RequestOptions and set consistency. Consider a small helper to reduce duplication.

Also applies to: 1472-1476, 2558-2594, 2686-2723, 2963-3002, 3069-3095, 3204-3238

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e3106c and 242e239.

📒 Files selected for processing (9)
  • .openapi-generator/FILES (1 hunks)
  • CHANGELOG.md (1 hunks)
  • README.md (1 hunks)
  • api_client.go (1 hunks)
  • api_headers_test.go (1 hunks)
  • api_open_fga.go (38 hunks)
  • client/client.go (49 hunks)
  • client/client_headers_test.go (1 hunks)
  • example/example1/example1.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
api_headers_test.go (5)
api_client.go (2)
  • APIClient (52-59)
  • NewAPIClient (68-99)
configuration.go (1)
  • NewConfiguration (55-102)
api_open_fga.go (2)
  • RequestOptions (35-39)
  • OpenFgaApi (41-899)
client/client.go (1)
  • RequestOptions (121-121)
utils.go (2)
  • PtrInt (25-25)
  • PtrString (40-40)
client/client_headers_test.go (3)
client/client.go (30)
  • OpenFgaClient (75-79)
  • ClientConfiguration (40-58)
  • NewSdkClient (81-119)
  • ClientCheckOptions (1988-1994)
  • RequestOptions (121-121)
  • ClientCheckRequest (1980-1986)
  • ClientWriteRequest (1504-1507)
  • ClientTupleKey (132-132)
  • ClientWriteOptions (1518-1524)
  • ClientReadRequest (1387-1391)
  • ClientReadOptions (1393-1400)
  • ClientExpandRequest (2494-2498)
  • ClientExpandOptions (2500-2506)
  • ClientListObjectsRequest (2621-2627)
  • ClientListObjectsOptions (2629-2635)
  • ClientListUsersRequest (2897-2904)
  • ClientListUsersOptions (2906-2912)
  • ClientBatchCheckClientOptions (2116-2123)
  • ClientReadAuthorizationModelOptions (1085-1090)
  • ClientWriteAuthorizationModelOptions (991-995)
  • ClientListStoresOptions (579-585)
  • ClientCreateStoreRequest (655-657)
  • ClientCreateStoreOptions (659-661)
  • ClientGetStoreOptions (736-740)
  • ClientDeleteStoreOptions (813-817)
  • ClientReadChangesRequest (1272-1275)
  • ClientReadChangesOptions (1277-1283)
  • ClientReadAssertionsOptions (3022-3027)
  • ClientAssertion (3118-3125)
  • ClientWriteAssertionsOptions (3147-3152)
api_open_fga.go (1)
  • RequestOptions (35-39)
utils.go (2)
  • PtrInt (25-25)
  • PtrString (40-40)
example/example1/example1.go (2)
client/client.go (3)
  • ClientCheckRequest (1980-1986)
  • ClientCheckOptions (1988-1994)
  • RequestOptions (121-121)
api_open_fga.go (1)
  • RequestOptions (35-39)
api_open_fga.go (1)
client/client.go (1)
  • RequestOptions (121-121)
client/client.go (4)
api_open_fga.go (1)
  • RequestOptions (35-39)
model_create_store_request.go (1)
  • CreateStoreRequest (22-24)
model_consistency_preference.go (1)
  • ConsistencyPreference (21-21)
client/errors.go (1)
  • FgaRequiredParamError (16-19)
🪛 Gitleaks (8.27.2)
api_headers_test.go

[high] 25-25: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (16)
.openapi-generator/FILES (1)

16-16: Tests listed — LGTM

The manifest includes the new header-behavior tests. No action needed.

Also applies to: 20-20

api_open_fga.go (2)

917-921: Fluent Options builder — LGTM

The Options(RequestOptions) builder reads well and keeps call sites clean. Applies consistently across endpoints.

Also applies to: 1164-1167, 1494-1497, 1672-1675, 1848-1851, 2211-2214, 2398-2401, 2608-2611, 2989-3004, 3299-3302, 3488-3491, 3730-3733, 3979-3982, 4183-4186, 4418-4421, 4606-4609


1001-1149: Per-request retries: ensure resolved RetryParams are used for telemetry and backoff

rg shows all retry backoff calls use retryParams := a.client.cfg.RetryParams (e.g. GetTimeToWait at api_open_fga.go:1059, 1076; BuildTelemetryAttributes at 1124). Confirm retryParams is the resolved/merged value (client defaults + any r.options per-request overrides) before calling retryutils.GetTimeToWait and before passing retry-related attrs into telemetry; if it isn’t, merge/apply per-request overrides and use that merged value.

api_client.go (1)

287-289: Allow per-request User-Agent to override default

Replace the unconditional set at api_client.go:284 with a conditional set so per-request User-Agent headers are preserved: if localVarRequest.Header.Get("User-Agent") == "" && c.cfg.UserAgent != "" { localVarRequest.Header.Set("User-Agent", c.cfg.UserAgent) }
Note: telemetry/attributes_test.go:14 sets User-Agent — run tests after this change.

example/example1/example1.go (1)

257-274: Good example of per-request headers usage

The example correctly demonstrates passing RequestOptions with custom headers to a single call.

api_headers_test.go (3)

32-48: Test server/header capture is solid

Helper neatly captures and exposes headers for assertions; JSON stubs and OK responses keep tests focused.


67-114: RequestOptions struct coverage looks good

Covers headers, MaxRetry, and MinWaitInMs, including nil vs empty map cases.


116-166: Header override/merge behavior tests are comprehensive

Broad surface coverage and clear expectations for overrides vs defaults. Nicely done.

Also applies to: 169-270, 272-315, 317-359, 361-399, 401-438, 440-476, 478-519, 521-552, 553-586, 587-617, 618-649, 650-681, 683-728, 730-762, 764-838

client/client_headers_test.go (1)

34-49: Helpers are clean and reusable

Server/client helpers are minimal and effective; good separation for repeated use.

Also applies to: 51-67

client/client.go (7)

121-121: Public alias for RequestOptions is the right call

Keeps the public surface consistent with the core SDK type.


127-130: Consistent options embedding

Embedding RequestOptions in wrapper structs keeps a uniform API.

Also applies to: 156-163


579-585: Option structs updated to embed RequestOptions — LGTM

Uniform embedding across the surface improves discoverability and composition.

Also applies to: 659-661, 736-740, 813-817, 892-899, 991-996, 1085-1091, 1186-1190, 1277-1284, 1393-1400, 1518-1524, 2116-2123, 2500-2506, 2629-2636, 2755-2762, 2906-2912, 3022-3027, 3147-3152


606-611: Correct propagation of RequestOptions into underlying API calls

Passing RequestOptions to each underlying request ensures headers/timeouts/etc. flow per-call. Well applied.

Also applies to: 701-705, 779-783, 857-859, 943-946, 1043-1047, 1152-1155, 1229-1231, 1341-1344, 1472-1476, 1674-1678, 1750-1751, 1795-1796, 2589-2593, 2714-2718, 2850-2851, 2993-2997, 3087-3090, 3227-3231


2049-2051: Good addition: nil Body check in CheckExecute

Prevents panic and returns a clear error to the caller.


2182-2189: ClientBatchCheck: per-request options and limits handled correctly

Properly threads RequestOptions and concurrency parameters, and reuses Check options for each item.

Also applies to: 2205-2210, 2383-2387


2850-2853: ListRelationsExecute: options plumbing looks correct

Forwarding RequestOptions, consistency, and max parallel requests is consistent with the rest.

Copy link
Contributor

@jimmyjames jimmyjames left a comment

Choose a reason for hiding this comment

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

Couple questions around a potential breaking change and the intent of adding retry options to the new RequestOptions along with the headers support.

@rhamzeh rhamzeh requested a review from jimmyjames October 2, 2025 13:02
jimmyjames
jimmyjames previously approved these changes Oct 6, 2025
ewanharris
ewanharris previously approved these changes Oct 7, 2025
@rhamzeh rhamzeh added this pull request to the merge queue Oct 7, 2025
Merged via the queue into main with commit e0e47c0 Oct 7, 2025
16 checks passed
@rhamzeh rhamzeh deleted the feat/per-request-headers branch October 7, 2025 14:07
github-merge-queue bot pushed a commit that referenced this pull request Oct 8, 2025
feat: support per-request headers and connection options (#233)
@rhamzeh rhamzeh changed the title feat: support per-request headers and connection options feat: support per-request headers options Oct 16, 2025
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.

[Feature] Add support for per-request custom HTTP headers in SDK API calls

5 participants