The fespalier preset
(Since 0.16.0, cratestack#1204.)
--preset fespalier is the riverpod preset sending through fespalier’s HttpTransport (package:fespalier_transport) instead of Dio. An app builds its transport chain once, for example SessionTransport(… HttpClientTransport(client)), and every generated call sends through it, so whatever the chain does (a session, request signing, a write guard, spans) applies to the generated client with no Dio in front of it.
It sits on the HTTP transport seam: CratestackFespalierTransport is one more implementation of CratestackHttpTransport, and the generated CratestackHttpAdapter / CratestackRpcHttpAdapter do the codec work above it.
What is different from riverpod
Everything else is the riverpod preset’s, file for file. Only these differ:
-
No Dio. The preset does not emit
lib/src/dio_transport.dart, and its pubspec has nodio. -
lib/src/fespalier_transport.dartholdsCratestackFespalierTransport, the adapter fromHttpTransportto the generatedCratestackHttpTransportseam. -
A git dependency.
fespalier_transportis not on pub.dev. fespalier packages are git dependencies by design, so the pubspec pins it by git at the--fespalier-ref:The pinned ref must be reachable for the package to resolve. Where it is not (an unreleased fespalier), pointfespalier_transportat a local checkout with apubspec_overrides.yaml. - Two providers the app overrides, and a changed default for the adapter provider (below).
-
The generated README gains a fespalier section, and the barrel export,
client.dartand the adapter-construction doc comments inruntime.dartchange to match.
The providers
<prefix> is the library name in camel case: --library-name orders_client gives ordersClient.
<prefix>BaseUriProvider is the server’s origin, with an optional path prefix (https://api.example.com or https://example.com/v1). <prefix>BasePathProvider is the API path prefix of the request. They are different things.
The app overrides the first two once, at the root:
CratestackRpcHttpAdapter for a transport rpc schema.)
What CratestackFespalierTransport does
-
The request goes out with the exact body bytes the adapter encoded (a signer layered above the transport sees those and nothing else), the headers, and
CratestackHttpRequest.extraasTransportRequest.attributes. Attributes are never sent on the wire. -
The abort is merged. The call option’s
abortand the abort trigger of the currentTransportZoneare combined withTransportZone.merge, and either one aborts. That is howref.cancellableinfespalier_cratestackaborts a page’s loads with no extra wiring, even for a call that carries its ownabort. -
Every status is an answer. A
TransportStatusErrorthrown by a decorator that wraps error answers in exceptions is treated as the answer it carries, so it still becomesCratestackRestExceptionorCratestackRpcException. -
Failures map onto the seam’s kinds:
The seam has no kind for a captive portal or an untrusted network, so those two become
CratestackTransportBroken. Checkerror.cause is TransportPortal(orTransportUntrusted) to tell them apart. -
Any other error a decorator throws reaches the caller unchanged.
AuthRejectedis the example: it is not a transport failure, so the adapter does not rewrap it.
Worked example
fespalier’sexamples/cratestack is a small orders app that puts this preset together with device-signed sessions (fespalier_sign_keypair): no password, no token and no Authorization header. It has:
- a standalone Rust server whose
schema.cstackis the contract, with public enrolment procedures and signed ones that it verifies on every other request; - the Flutter client that
cratestack generate-dart --preset fespalierwrote from that schema, committed, linking no Dio; - an app whose startup builds the transport chain once and overrides
<prefix>TransportProviderand<prefix>BaseUriProviderwith it; - tests on both sides and a cross-language end-to-end test where Dart signs and Rust verifies.
Known gaps
- A generated call cannot hand the transport chain a per-call attribute.
CratestackHttpRequest.extrais forwarded asTransportRequest.attributes, but neither call options type can fill it, so a call such as a payment cannot ask the chain for a user-present signing key. Tracked in cratestack#1207. - On 0.17.0 and earlier, an
AuthProviderrefusal cannot carry a machine-readable reason in its body, so a client cannot tell “renew the attestation” from “wipe the keys” by a body member. (Unreleased, cratestack#1208.)AuthRefusaladds such members next tocodeandmessage(see Refusing a request). A fespalierRequestAuthenticatorreads them from the raw response, before the generated client decodes it. The generated client itself keeps them only inCratestackRestException.body(REST): its RPCRpcErrorBodyreadscode,messageanddetailsalone, until cratestack#1243 types them.
See also
- Dart HTTP transport — the seam, the adapters, typed errors and call options this preset builds on
- Dart client generation — presets and flags
- Migrating the Dart client to the HTTP transport