Second hardening pass, over the code added in 23c660d.
Error reporting:
- Decode failures now carry an excerpt of the body that arrived. The
serde message alone ("expected value at line 1 column 1") gives no hint
that the response was an HTML error page from a proxy or a login
redirect from a wrong base URL. The excerpt is collapsed to one line
and truncated so a large body cannot become a large error.
- API errors with an empty or message-less body report the status reason
("Not Found") rather than rendering blank.
- `Error` is `#[non_exhaustive]`, so future variants are not breaking.
Retry bounds:
- `RetryPolicy::max_elapsed` (120s default) bounds the time one call may
spend across attempts. The request timeout is per attempt, so the
previous defaults could spend four minutes on a call that kept timing
out — a surprise for anyone reading `with_timeout(60)` as a bound on
the call.
- `backoff` uses saturating arithmetic; a large `max_delay` could
overflow the jitter span and panic in a debug build.
- Response bodies are drained before a retry so the connection returns to
the pool instead of the next attempt opening a fresh one.
Credentials:
- Test pinning that neither the token nor the password appears in `Debug`
output. `reqwest`'s `Client` prints its default headers, so the
sensitive flag set when building them is the only thing keeping the
token out of logs, and nothing enforced that.
Also drops `Option::is_none_or`, which raised the minimum toolchain to
1.82 for no benefit over a match.
91 tests, up from 80.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Expands coverage to every `Route::resource` group in the v5 API plus
`users` (265 methods), and fixes several defects in the shared request
path that the new breadth made more exposed.
Coverage:
- 25 new models, each keeping the `extra` flatten map and the
`null_to_default` convention so unmodeled fields round-trip.
- `crud_methods!` split out of `resource_methods!` for the three groups
with no `/bulk` route, where a generated `bulk_*` would only 405.
- COVERAGE.md added as the detailed inventory, verified against
`routes/api.php` on v5-stable.
Fixes, each confirmed against upstream source or an observed request:
- `list_all_*` silently truncated. `BaseController` caps page size at
`min(abs(per_page), 5000)`, so the "short page means last page" rule
stopped early and returned a partial list as if complete. Termination
now prefers `meta.pagination.total_pages` (decoded but previously
discarded), and `per_page` is clamped so the fallback stays sound.
- `per_page(0)` paged forever: the check `got < 0` has no solution. It
now falls back to the default size, with a MAX_PAGES backstop that
raises `Error::PaginationLimit` rather than quietly truncating.
- Ids were interpolated into paths raw, so a `/` or `?` in an id
retargeted the request. All sites now use `seg(id)?`.
Escaping alone is insufficient: the WHATWG rules `url` implements
decode `%2E` to `.` before resolving dot segments, so an escaped `..`
still collapses the path. Empty and dot-only ids are rejected.
- No request timeout. reqwest applies none, so a stalled connection hung
the caller indefinitely. Adds a 60s default and `with_timeout`.
- Base URL is validated at construction; a missing scheme used to 404
on every call instead of failing once.
Adds tests/client_requests.rs: 26 tests over the request plumbing
against a wiremock loopback server, including regressions for the
truncation and non-termination bugs. No network or credentials needed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Covers Clients, Invoices, Quotes, Credits, Payments, Products, Expenses,
Projects, Tasks, and Vendors, each with list, list_all, get, create,
update, and delete, generated by the resource_methods! macro.
Every model carries a #[serde(flatten)] extra map so fields the API
returns but the crate does not model survive a get/update round-trip
instead of being silently dropped.
Includes a Nix dev shell (Rust via rust-overlay), a GitHub Actions CI
workflow running build/test/fmt/clippy, and round-trip tests for the
extra-preservation invariant.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>