upcloud-api — the UpCloud API 1.3 surface as ONE trait, ONE wire, ONE
decision about which cloud answers.
Why this crate exists
On 2026-09-21 the nordisk estates had five UpCloud clients:
private-gunnar-ops's xtask (two of them, until lane T3 made them one),
gunnar/deploy/upcloud (the re-image procedure, with its OWN scripted fake),
gunnar-loadbench, private-holger-ops's xtask, and monetize-cloud-impl.
Each one spelled https://api.upcloud.com/1.3 for itself. Each one decided
for itself whether a run was aimed at a fake. Four bugs that day were the
same bug: a mock run reached the account because one client in the chain
decided differently from the rest — the worst of them was
gunnar-upcloud's Api::new(DEFAULT_BASE), so cargo xtask mock reimage --apply would have re-imaged the LIVE appliance with the estate's token
while the banner said FAKE.
A test against the fake is only worth something if the code under test is the SAME code that runs against the account. So:
- [
UpCloudApi] is the only surface. No method takes a path, a query string or a base URL, so no caller can name an endpoint. - There is one wire implementation. It builds every request for the
account and for a mock the same way, from the same code — only the base
differs. A run against
mock-upcloudtherefore exercises the exact URL, body and header construction a run against the account does. - The base comes from an [
Endpoint], and anEndpointis decided ONCE, at the edge of the program, from what the operator TYPED ([Endpoint::account] / [Endpoint::mock]). A mock endpoint is loopback-only by construction; the account endpoint refuses to exist while the shell carries a mock variable ([MOCK_ENVS]). - The in-process fake —
FakeUpCloudovermock-upcloud'sEstate— lives beside that state machine in themock-upcloudcrate, so a fault armed once applies to terraform (the HTTP face) and to Rust callers (the trait face) alike: one world, two faces. - [
over] is where a typed call becomes ONE method, path and body: the wire and every fake are an [Exchange] under [Over], so a fake answers the exact request the account would have been sent. - [
guard] is the test every consuming repository runs: nothing outside the files it names may spell the provider or build an API path.
The method set is DERIVED, not designed
Every method exists because a real call site in one of the ported clients calls that endpoint. The doc on each names the caller. A general-purpose UpCloud client is a second thing to keep true; this is not one.