Wrap all 34 resource groups, then harden the request plumbing

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>
This commit is contained in:
2026-10-23 18:08:38 -04:00
co-authored by Claude Opus 5
parent ff9a859d0b
commit 9976249210
44 changed files with 4003 additions and 175 deletions
+49 -1
View File
@@ -1,19 +1,67 @@
mod bank_integration;
mod bank_transaction;
mod bank_transaction_rule;
mod client;
mod client_gateway_token;
mod common;
mod company;
mod company_gateway;
mod company_token;
mod design;
mod document;
mod expense;
mod expense_category;
mod group_setting;
mod invoice;
mod location;
mod payment;
mod payment_term;
mod product;
mod project;
mod purchase_order;
mod recurring_expense;
mod recurring_invoice;
mod subscription;
mod system_log;
mod tag;
mod task;
mod task_scheduler;
mod task_status;
mod tax_rate;
mod user;
mod vendor;
mod webhook;
pub use bank_integration::BankIntegration;
pub use bank_transaction::BankTransaction;
pub use bank_transaction_rule::BankTransactionRule;
pub use client::{Client, ClientContact};
pub use common::{ItemResponse, ListParams, ListResponse, Meta, Pagination};
pub use client_gateway_token::ClientGatewayToken;
pub use common::{BulkRequest, ItemResponse, ListParams, ListResponse, Meta, Pagination};
pub use company::Company;
pub use company_gateway::CompanyGateway;
pub use company_token::CompanyToken;
pub use design::Design;
pub use document::Document;
pub use expense::Expense;
pub use expense_category::ExpenseCategory;
pub use group_setting::GroupSetting;
pub use invoice::{Credit, Invoice, InvoiceItem, Quote};
pub use location::Location;
pub use payment::{Payment, PaymentInvoice};
pub use payment_term::PaymentTerm;
pub use product::Product;
pub use project::Project;
pub use purchase_order::PurchaseOrder;
pub use recurring_expense::RecurringExpense;
pub use recurring_invoice::{RecurringInvoice, RecurringQuote};
pub use subscription::Subscription;
pub use system_log::SystemLog;
pub use tag::Tag;
pub use task::Task;
pub use task_scheduler::TaskScheduler;
pub use task_status::TaskStatus;
pub use tax_rate::TaxRate;
pub use user::User;
pub use vendor::{Vendor, VendorContact};
pub use webhook::Webhook;