Updates the three coverage summaries to 325 methods so they agree, moves the
per-resource extras out of the gap list in `COVERAGE.md` now that they are
wrapped, and records why the fixtures exist and how to refresh them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First item on the GOALS.md roadmap. `upload_{x}(id, files, is_public)`
attaches files through `/{resource}/{id}/upload` and returns the
refreshed record. Verified against upstream that all 16 controllers
answer with `itemResponse($x->fresh())`.
Three details that look wrong but are not, each checked in the upstream
source rather than taken from the published spec:
- The request is a POST carrying `_method=PUT`. The route is registered
PUT, but PHP does not parse a multipart body on a real PUT, so the
files never arrive. Laravel's kernel calls
`enableHttpMethodParameterOverride()`, turning the spoofed method back
into a PUT before routing. The spec documents POST with `_method=POST`,
which would 405 against these routes.
- `is_public` is sent as 1/0. Laravel's `boolean` rule accepts only
true, false, 0, 1, '0' and '1' under strict comparison, so "true"
fails validation.
- Uploads are never retried. An upload adds a document, so it is not
idempotent whatever its verb, and a replay after a lost response would
attach the file twice. This falls out of reqwest's multipart body being
un-cloneable, which nothing in the types enforces, so a test pins it
for 500, 503 and 429.
An upload with no files, a nameless file or an unparseable MIME type is
refused locally (`Error::InvalidUpload`), since the server answers an
empty upload with a success that changed nothing.
Corrects two earlier claims:
- GOALS.md and COVERAGE.md said uploads would make `create_document`
possible. They do not: `DocumentController::store` is an empty stub,
and an upload is the only way a document is created. There is no
`create_document`.
- The documented method count (265) had been stale since the typed
`statics`/`search`/`health_check` commit, which added three `_as`
methods without updating it. Recounted from the source: 284 = 210 +
18 + 12 + 16 uploads + 28 hand-written.
Also fixes a README example, `let statics: Value = client.statics()`,
that stopped compiling when `statics` lost its type parameter.
`edocument/upload` is left out: it imports an expense from an
e-document rather than attaching a file, so it moves to the
import/export roadmap item.
Enabling `multipart` adds mime, mime_guess and unicase; MSRV stays 1.88,
re-verified by running the suite on it. 101 tests, up from 91.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Cargo.toml` named github.com/bryant/invoiceninjars, a guess from the
git author name that was never a real repository. It now points at the
mirror that actually exists, so the crates.io "Repository" link will
resolve.
Gitea is the source of truth and a push mirror copies every commit to
GitHub. Anything written to the GitHub copy is overwritten by the next
sync, so a merged pull request there would silently vanish. README and
GOALS.md say so, and direct issues and pull requests to Gitea.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"No ORM, session, or caching layer" bundled several decisions with very
different costs, and read as refusing things that are in fact fine.
Split into the three that genuinely conflict, each with its reason:
lazy loading (models would have to hold a client handle, breaking
`Default`-constructibility and the `extra` round trip), dirty tracking
(cannot diff a flattened map of unmodelled fields), and transparent
entity caching.
The caching reason is the one worth recording: `BaseController`'s shared
response path sends no `ETag`, `If-None-Match`, or `Cache-Control`, so a
cached entity cannot be cheaply revalidated. On an API that handles money
that is a correctness hazard rather than a missed optimisation. Writing
the evidence down so the question is not reopened without it.
Two things the old wording excluded by accident are now open questions
instead: typed `include` helpers, since eager loading is a stringly
parameter where a typo is a silent no-op; and an opt-in cache for
`/statics`, which is near-constant and not money. Also parks the
`updated_at` delta-sync parameter as the shape a local mirror would
need, noted as a different product rather than a plan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Several decisions in here look like oversights until you know the
reasoning: the curated model subset with everything else in `extra`, the
still-generic `chart`, not retrying `POST`, the deliberate divergences
from the official PHP SDK. That reasoning was spread across code
comments and commit messages, where it is easy to miss and easy to
"fix".
GOALS.md states:
- What the crate is for: complete coverage, and not quietly losing data,
truncating results, or succeeding when it did not.
- Seven goals, each tied to a decision visible in the code, so the
rationale survives the next refactor.
- Non-goals, including no sync API, no caching or ORM layer, no
modelling of every field, and no retrying of non-idempotent requests.
- Publication posture: on crates.io, pre-1.0, no stability promise, pin
an exact version. MSRV may rise with dependencies.
- A roadmap ordering the COVERAGE.md gaps by value over effort, starting
with multipart uploads, which is the only thing blocking
`create_document`.
- Open questions, left open rather than papered over: whether auth routes
belong here at all given they are a different auth model, whether
per-chart types earn their keep, streaming for large exports, and the
untested `Error::PaginationLimit`.
README, COVERAGE.md, and CLAUDE.md link to it. No changelog entry: this
adds no behaviour, per the rule in CLAUDE.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>