Repository navigation
feat(error): retain allowlisted response headers on status errors across SDKs - #419
Merged
Merged
Conversation
This commit keeps the allowlisted `Retry-After`, `X-Proxy-*` and `X-RateLimit-*` response headers on `Status` errors so callers can read retry and proxy information without exposing anything sensitive. **Error metadata:** * Added a `Headers` field to `Error` that holds the allowlisted response headers, redacted and bounded; it is nil when none arrived and never appears in the `Error` text. * `RetryAfter` returns the retry delay (delta-seconds or HTTP-date, with a past date treated as zero) and `ProxyError` returns the proxy's `X-Proxy-Error` reason. **Transport wiring:** * Introduced `headers.go` with the retention allowlist and the logic that filters and redacts the retained headers. * `statusError` and the new `providerMessageError` method now populate the `Headers` field from the buffered response. * Added `headers_test.go` to cover the allowlist and redaction behaviour, and documented the retained headers in the README.
This commit adds `SafeHeaders`, a redacted and bounded view of the response headers kept for callers on status errors, and wires it into the client so every status failure carries the allowlisted headers. **Header retention contract:** * Introduced `SafeHeaders`, which keeps only names accepted by the shared `is_retained_header_name` allowlist (`retry-after`, `x-proxy-*`, `x-ratelimit-*`, compared case-insensitively), lowercased and in response order. * Values are passed through the `Redactor`, dropped when they are not visible ASCII or exceed `MAX_SAFE_HEADER_VALUE_BYTES`, and capped at `MAX_SAFE_HEADERS` entries; `set-cookie`, `authorization` and `apikey` are never retained. * Added `get`, `iter`, `len` and `is_empty` accessors for inspecting the retained pairs. **Retry and proxy accessors:** * Added `retry_after` and `retry_after_at`, which parse delta-seconds and the IMF-fixdate, RFC 850 and asctime HTTP-date forms, yielding `Duration::ZERO` for a past date and `None` when absent or unparsable. * Added `Error::headers`, `Error::retry_after` and `Error::proxy_error` (from `X-Proxy-Error`), keeping headers out of the `Display` output. **Client wiring and tests:** * Attached `SafeHeaders` to every status error raised by the client via `with_headers` and a `safe_headers` helper over the transport response. * Added `tests/safe_headers.rs` covering the retention and accessor behaviour.
This commit adds client-level tests verifying that status and provider-message errors retain only an allowlisted set of response headers, and documents the behaviour in the libfmp README. **Testing:** * Added `crates/libfmp/tests/error_headers.rs` with client-level tests confirming that status errors and provider-message errors keep only the allowlisted headers (`Retry-After`, `X-Proxy-*`, `X-RateLimit-*`) and store no other header. **Documentation:** * Updated `crates/libfmp/README.md` to describe the allowlisted headers retained by a status error, the `err.retry_after()`, `err.proxy_error()` and `err.headers()` accessors, and the fact that headers never appear in the error's `Display` text.
This commit adds three structured attributes to the Python error hierarchy so callers can react to rate-limiting proxies without parsing `str(error)`. **New error attributes:** * `FmpError` now carries `retry_after`, the `Retry-After` delay in seconds as a float (delta-seconds or HTTP-date, with a past date reported as `0.0`). * `headers` is a dict of the lowercase allowlisted response headers (`retry-after`, `x-proxy-*`, `x-ratelimit-*`), redacted and `None` when none were sent; no other header is ever stored or shown in `str(error)`. * `proxy_error` surfaces the proxy's `X-Proxy-Error` reason, such as `"rate_limited"`. **Implementation and stubs:** * `errors.rs` populates the new attributes from the native error, retaining only the allowlisted headers and setting defaults to `None` on the base. * The `.pyi` stub and member metadata declare the three properties with docs. **Tests and docs:** * `conftest.py` routes can now send extra headers for exercising these cases. * Added `test_error_headers.py` and extended the import and typecheck contract tests to cover the new attributes. * Documented the attributes in the README error table. close #416
benbenbang
enabled auto-merge (rebase)
October 6, 2026 10:13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Callers hitting rate-limiting or proxy-mediated failures previously had to parse error strings to learn when to retry or why a request was rejected. This change exposes that information as structured data while ensuring no sensitive headers leak.
Across the Go SDK, the Rust libfmp crate, and the Python bindings, status errors now retain a small allowlist of response headers (
Retry-After,X-Proxy-*,X-RateLimit-*), compared case-insensitively, lowercased, redacted, bounded in size and count, and kept out of any error display text. Headers such asSet-Cookie,AuthorizationandApiKeyare never retained.What changed:
X-Proxy-Error, and the raw allowlisted headers, surfaced idiomatically in each language (GoRetryAfter/ProxyError/Headers, Rustretry_after/proxy_error/headers, Pythonretry_after/proxy_error/headers).Closes #416