Skip to content

feat(sdk): expose response metadata and status on successful calls - #420

Merged
benbenbang merged 4 commits into
mainfrom
feat/417-success-headers
Oct 6, 2026
Merged

benbenbang merged 4 commits into
mainfrom
feat/417-success-headers

Conversation

@benbenbang

Copy link
Copy Markdown
Member

Callers previously could only inspect response headers and status when a request failed, forcing them to drop to the transport layer to read proxy cache status and remaining daily budget on success. This change surfaces that metadata on successful responses across all client surfaces.

  • Added a metadata-aware execution path to the Rust client via execute_with_metadata and a public Response type, with execute now delegating to it so existing behaviour is unchanged.
  • Added an optional on_response observer to the Rust ClientBuilder and a matching on_response callback to the Python FmpClient, each fired once per successful call with the endpoint id, final HTTP status, and allowlisted, redacted headers.
  • Added ResponseMetadata and WithResponseMetadata to the Go SDK so callers can attach metadata to a call's context and read it after a successful decode.
  • Kept header handling consistent across all surfaces by reusing the same allowlist as errors (retry-after, x-proxy-, x-ratelimit-) and leaving error paths untouched, since errors already carry their own headers.
  • Documented the new capabilities in the relevant READMEs, doc comments, and type stubs, and added tests covering success metadata, observers, and callbacks, including silence across status, provider-message, and decode errors.

close #417

This change introduces a metadata-aware execution path so callers can read
the HTTP status and allowlisted response headers on success, not only on
failure.

**New capability:**
* Added `Client::execute_with_metadata`, which runs the same request,
  redirects and decoding as `execute` but returns a `Response<T>` carrying the
  decoded `data`, the final `status`, and the redacted `SafeHeaders`.
* Added the public `Response<T>` struct and re-exported it from the crate root
  alongside `Client` and `ClientBuilder`.
* `execute` now delegates to `execute_with_metadata` and returns just the data,
  so existing behaviour is unchanged; the internal header filtering uses the
  same allowlist as `Error::headers` (`retry-after`, `x-proxy-*`,
  `x-ratelimit-*`).

**Why:**
* This lets callers inspect proxy cache status and remaining daily budget
  (`x-proxy-cache`, `x-proxy-daily-remaining`) on successful responses without
  dropping down to the transport layer.

**Docs and tests:**
* Documented the metadata form in the README and on the method, showing how to
  read cache and budget headers.
* Added `tests/success_headers.rs` covering the headers surfaced on success.
This commit adds a way to read the proxy's cache status and remaining
daily budget after a successful call. Callers attach a ResponseMetadata
to the call's context with WithResponseMetadata, and the client fills it
once the response decodes successfully.

**New capability:**
* Added response_metadata.go introducing ResponseMetadata and
  WithResponseMetadata, which carry a *ResponseMetadata through the
  call's context.
* On a decoded success the client records the endpoint id, the HTTP
  status and the allowlisted, redacted headers (the same allowlist as
  errors: Retry-After, X-Proxy-*, X-RateLimit-*) via a new
  recordResponseMetadata call in binary.go, client.go and csv.go.
* Errors leave the metadata untouched since *Error already carries its
  own headers. Each call needs its own ctx/meta pair; sharing one meta
  across concurrent calls races on it.

**Documentation and tests:**
* Documented the feature in README.md and doc.go, including the
  X-Proxy-Cache and X-Proxy-Daily-Remaining use case.
* Added response_metadata_test.go covering the new behaviour.
This pull request adds an optional response observer to the client so
callers can watch every successful response made through it, including
those from endpoint methods. The main changes introduce the registration
API, the data passed to observers, and the firing logic.

**Observer registration and types:**
* Added `ClientBuilder::on_response`, which stores a `ResponseObserver`
  closure and replaces any previously registered observer, carried through
  the builder into the client configuration.
* Added the `ResponseObserver` type alias and the `ResponseInfo` struct,
  exposing the endpoint id, the final HTTP status, and the allowlisted,
  redacted headers, and re-exported both from the crate root.

**Firing behaviour:**
* The observer runs inline once per call, only after the body decodes
  successfully, and never fires on status, provider-message, decode, or
  transport errors, since those already carry their headers on the error.
* Included the observer state in the builder and client `Debug` output as
  a presence flag.

**Documentation and tests:**
* Documented the observer in the README and on the builder method, noting
  it cannot fail the call and should stay cheap and non-blocking.
* Added tests covering that the observer sees endpoint-method successes
  with the expected endpoint, status, and headers, and stays silent across
  status, provider-message, and decode errors.
This pull request adds an optional `on_response` callback to `FmpClient`
so callers can read the proxy's cache status and remaining daily budget
after every successful request, across all namespace methods.

**New response callback:**
* `FmpClient(on_response=...)` accepts a callable invoked as
  `on_response(endpoint_id, status, headers)` after each successful
  response, before the method returns.
* `headers` carries only the allowlisted response headers (`retry-after`,
  `x-proxy-*`, `x-ratelimit-*`), lowercase and redacted, keeping the first
  value for a repeated name, and is `{}` rather than `None` when none were
  sent, mirroring an error's `headers`.
* The callback runs on the calling thread with the GIL held, making it safe
  to use from several threads at once; it is never invoked for errors, which
  already carry their own `headers`, and if it raises, the method raises that
  exception and discards the decoded data.

**Implementation:**
* Added `response_hook` module to capture and deliver allowlisted headers,
  wired through `ClientHandle` and `block_on`.
* Validated that `on_response` is callable or `None` at construction time.

**Docs and tests:**
* Documented the callback in the README and the `__init__.pyi` and Rust
  docstrings.
* Added `tests/test_on_response.py` covering the new behaviour.

close #417
@benbenbang
benbenbang enabled auto-merge (rebase) October 6, 2026 11:01
@github-actions github-actions Bot added documentation Improvements or additions to documentation enhancement New feature or request labels Oct 6, 2026
@benbenbang
benbenbang merged commit 5bf943d into main Oct 6, 2026
10 checks passed
@benbenbang
benbenbang deleted the feat/417-success-headers branch October 6, 2026 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose response headers on success (execute_with_metadata)

1 participant