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>
33 lines
1.3 KiB
TOML
33 lines
1.3 KiB
TOML
[package]
|
|
name = "invoiceninjars"
|
|
version = "0.1.0"
|
|
edition = "2021"
|
|
# Set by the dependency graph rather than by this crate's own code: reqwest
|
|
# pulls in url -> idna -> idna_adapter -> icu_*, and the icu crates require
|
|
# 1.88. Verified by running the test suite against it; see the `msrv`
|
|
# devShell in flake.nix.
|
|
rust-version = "1.88"
|
|
description = "An async Rust client for the Invoice Ninja v5 API"
|
|
license = "MIT"
|
|
repository = "https://github.com/awkawb1/invoiceninja-sdk"
|
|
|
|
[dependencies]
|
|
reqwest = { version = "0.12", default-features = false, features = ["json", "native-tls", "multipart"] }
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
thiserror = "2"
|
|
# Already in the tree via reqwest's `url`; used to escape ids before they are
|
|
# interpolated into request paths.
|
|
percent-encoding = "2"
|
|
# Already in the tree as reqwest's runtime; only `time` is needed, for the
|
|
# sleep between retries.
|
|
tokio = { version = "1", features = ["time"] }
|
|
|
|
[dev-dependencies]
|
|
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
|
# Used to assert that ListParams urlencodes the way reqwest will send it.
|
|
serde_urlencoded = "0.7"
|
|
# Loopback HTTP server for exercising the request plumbing (pagination, path
|
|
# escaping, headers, error mapping) without reaching the network.
|
|
wiremock = "0.6"
|