Keep the response headers: paginated listings say there's more, failures carry a request id - #3
Merged
Merged
Conversation
This was referenced Sep 14, 2026
`bytes()` and `text()` consume a `reqwest` response, so a header not read before the body is gone for good. `Content-Type` was the only one taken, which left two things unreachable. **Paginated listings truncated silently.** The API answers with one page and a `Link` header naming the next; nothing read it, so both output modes looked complete — while the generated `--help` told the reader to use that header. Listings now print the flags that fetch the next page, on stderr in both modes, because the result is equally partial under `-o json` and the API's document cannot carry the fact without an envelope the output contract forbids. `--id` on a paged listing also stops saying "No row has the id" as though the row did not exist. The flags are derived by matching the next URL's query against `op.query_params`, so whatever a spec calls its paging parameters is what the tip names. The access token cannot appear in it: `dispatch` adds it to the query rather than declaring it, *and* it is refused by name — because a spec is free to declare a parameter called `access_token`, which a test caught while it was still only excluded "by construction". **A failure carried no request id.** Now in every `-o json` failure, and in `-o text` for a 5xx only. Measured rather than assumed: no Mapbox endpoint sends `x-request-id` — styles, tokens, fonts and geocoding v6 all answer without it — so `REQUEST_ID_HEADERS` also reads CloudFront's `x-amz-cf-id`, which every response carries. Looking only for the conventional name would have shipped a feature that never fired. `agent_skills` is exempt on purpose: GitHub codeload's id is not one Mapbox support can look up, and `source_guards.rs` fails a fourth send path that forgets to decide. `src/link.rs` parses the header per RFC 8288 rather than matching a substring, because both things that break a shortcut occur in real headers: a URI may contain a comma, which is also the delimiter between links, and `rel` may carry a space-separated list. `Retry-After` is deliberately not read yet — nothing retries, so a field with no consumer would be dead code. It is the next consumer to add, and the reason this is a struct rather than three reads at the call site. Ported from mapbox/mapbox-cli-private#133, which cannot merge there now that `oss/` is a submodule (mapbox/mapbox-cli-private#132). That PR also edited `AGENTS.md`, which has no counterpart in this repository, so the invariant notes stay in the private repo.
mattpodwysocki
force-pushed
the
response-headers
branch
from
September 14, 2026 17:39
e903a41 to
b4589d8
Compare
zmofei
approved these changes
Sep 14, 2026
zmofei
left a comment
Member
There was a problem hiding this comment.
Reviewed the diff (994/+47) and CI (all green). Pagination handling (src/link.rs, RFC 8288 parsing), the request-id header fallback (measured against real Mapbox endpoints rather than assumed), and the access-token exclusion from pagination tips (double-covered by tests) all look correct. The new source_guards.rs test guarding future send paths is a good defensive addition. No issues found.
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.
Ported from mapbox-cli-private#133, which can no longer merge there now that
oss/is a submodule (#132). It was reviewed and CI-green there; re-verified here.bytes()andtext()consume areqwestresponse, so a header not read before the body is gone for good.Content-Typewas the only one taken, which left two separate things unreachable.Paginated listings truncated silently
The API answers a listing with one page and a
Linkheader naming the next. Nothing read it, so both output modes looked complete — and the generated help text told the reader to use that header, advice the CLI made impossible to follow. Listings now say so:The flags are derived, not hardcoded: the next URL's query is matched against
op.query_params, so whatever a spec calls its paging parameters is what the line names.The note goes to stderr in both modes, including
-o json: the result is equally partial there, and the API's own document cannot carry the fact without an envelope the output contract forbids.--idon a paged listing also stops sayingNo row has the idas though the row did not exist — true of the page, possibly false of the listing.Failures carried no request id
Now in every
-o jsonfailure asrequest_id, and in-o textfor a 5xx only, where the server is at fault and a 404 on a mistyped id stays uncluttered.Measured, not assumed. I had asserted Mapbox APIs return
X-Request-Id. They don't: styles, tokens, fonts and geocoding v6 all answer without it, on success and on a 404. What every response carries isx-amz-cf-id, because the API is fronted by CloudFront.REQUEST_ID_HEADERSreads both, preferring a service's own. Looking only for the conventional name would have shipped a feature that never fired once, with green unit tests throughout.agent_skillsis exempt on purpose — GitHub codeload's id is not one Mapbox support can look up — andtests/source_guards.rsfails a fourth send path that forgets to decide either way.src/link.rsParses
Linkper RFC 8288 rather than matching a substring, because both things that break a shortcut occur in real headers: a URI may contain a comma, which is also the delimiter between links (bbox=-1,2,-3,4is ordinary here), andrelmay carry a space-separated list. The multi-link test caught a real bug in my first version, where the delimiter comma stayed attached andrel="next",didn't match.The security property has two tests, deliberately
The access token rides in the query string, so the API echoes it back inside the
LinkURL this feature parses. Two things keep it out: it is added bydispatchrather than declared inop.query_params, and it is refused by name.The second exists because the first is not enough, and a test showed me that: I wrote
a_declared_parameter_named_access_token_is_still_withheldexpecting it to pass on "by construction" reasoning, and it failed. A spec is free to declare a parameter calledaccess_token, and on the day one did, the CLI would have printed a live credential. Please don't collapse those two tests into one.Verified here
569 tests pass,
cargo fmt --checkandcargo clippy --all-targetsclean. Against the live API before the port: following the printed tip returned a genuinely different page, a real 404 carriedrequest_id, andaccess_token/sk.ey/pk.eyappear zero times across every stderr capture.Not included
Retry-Afterand retry — a field with no consumer would be dead code, which is why the headers are a struct rather than three reads at the call site.--allauto-pagination, which needs a decision about how combined pages are emitted. Tracked in mapbox-cli-private#117.The original PR also edited
AGENTS.md, which has no counterpart in this repository, so those invariant notes stay in the private repo.