api_client
REST client of the chat server for the apps and the console: the API generated from
docs/api/openapi.yaml (openapi-generator dart-dio with built_value, never changed by hand)
and a small hand-written layer that every client package uses (docs/spec/06-api.md 14).
Kind: pure Dart package, a member of the Dart workspace at the repository root
(resolution: workspace, publish_to: none).
Dependencies
Allowed members (docs/spec/15-client-architecture.md, 3.1; tools/arch-check/rules.yaml): none: no arrow of the diagram leaves this package.
Every other member is out of reach; make arch fails on a dependency or an import of it.
Further rules checked by make arch:
- no import of
package:flutter/,dart:ui(rule 1). - no native source (rule 4).
Packages: dio, built_value, built_collection, one_of, one_of_serializer and
http_parser, which the generated code imports; the hand layer needs nothing more.
Layout
| Path | Content |
|---|---|
lib/api_client.dart |
the public library: exports the generated API and the hand layer |
lib/src/generated/ |
generated: the API classes (api/), the models (model/), the serializers (*.g.dart from build_runner) and the generator's library entry generated_api.dart; out of the doc lint, the style lint, the format check and coverage (docs/DECISIONS.md D-M4-01-4) |
lib/src/hand/ |
the hand layer, see below; every public symbol has a doc comment |
test/ |
tests of the hand layer and the tolerance test G5 (test/fixtures/g5/) |
tool/generate.sh |
the generation and the gate check, called by the Makefile |
.openapi-generator-ignore |
paths the generator must never write |
Regenerate
After a change of docs/api/openapi.yaml, from the repository root (needs Docker and Flutter):
make -C packages/api_client generate
The generator runs from the image pinned by digest in the Makefile, without network, into a
temporary folder; only lib/src/generated/ is replaced, then build_runner builds the
serializers and dart format formats them. Commit the result with the spec change.
make -C packages/api_client generate-check (called by make api-clients, gate G4) does the
same in a temporary copy, compares every generated file byte for byte with the checked-in state
(a missing, extra or changed file fails with its path), and runs dart analyze: an error in
generated code or any finding in hand-written code fails. It never writes into the working tree.
Temporary folders come from $TMPDIR (default /tmp) and are removed on exit; Docker must be
able to mount them (Colima mounts only the home folder by default, so set TMPDIR below it).
Hand layer
createApiClientbuilds the generatedApiClientfor one server address: base URL plus/api/v1, JSON responses, and the interceptors in a fixed order: headers, the JSON problem body of a download (BinaryProblemInterceptor, M4-22), theauthInterceptorsslot, problem mapping, read retry.- Headers:
traceparenton every request from the caller'sTraceContext, with a new span ID per request;Idempotency-KeyonPOST,PATCH,PUTandDELETEfrom the request optionidempotencyKeyExtra, never invented;ResponseMeta.ofexposesCorrelation-Id. - Read retry: only
GETandHEAD, only after a network error, a timeout,429or503(not503 setup_required), waitingRetry-Afteror a full-jitter backoff fromReadRetryPolicy.fromBackoff(base, cap), which the apps fill fromclient.outbox_retry_baseandclient.outbox_retry_cap. Writes are retried by the outbox, never here. - Problems: every response error reaches the caller as a
DioExceptioncarrying anApiProblem(ApiProblem.of(e)) withstatus,code(orhttp_<status>without a problem body),reason,hint,errors,current,correlationIdandretryAfter. - Events:
EventsPageDecoder.decodeandfetchEventsread an event page element by element; an unknown event type becomesUnknownEventinstead of failing the page.EventsPageDecoder.decodeEnvelopereads thedmember of a gatewaydispatchframe the same way.
Request bodies carry no JSON null: the generated serializers leave absent optional members
out (test/null_free_test.dart).
What M4-09 plugs in
The auth package passes its interceptors as authInterceptors: DPoP proofs, the nonce retry
of use_dpop_nonce, token refresh and step-up. They run after the headers (so a retry gets a
fresh traceparent) and see the raw error before the problem mapping. The generated auth
interceptors of openapi-generator are not installed.
Run from the repository root: make lint test coverage arch api-clients COMPONENTS=apps. The
API pages come from make docs (Dart API reference).
Libraries
- api_client
- REST client of the chat server: the API generated from
docs/api/openapi.yaml(openapi-generatordart-dio, never changed by hand) and the hand-written layer every client package uses (docs/spec/06-api.md 14).