upcloud-api 0.1.5

The UpCloud API 1.3 surface the nordisk estates use, as ONE trait (`UpCloudApi`) with ONE wire implementation. Which cloud a run talks to (the account, or a mock-upcloud on loopback) is an `Endpoint` decided once at the edge, and a mock endpoint cannot be pointed off this machine. The fake that answers the trait in-process lives beside mock-upcloud's state machine.
Documentation

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-upcloud therefore exercises the exact URL, body and header construction a run against the account does.
  • The base comes from an [Endpoint], and an Endpoint is 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 — FakeUpCloud over mock-upcloud's Estate — lives beside that state machine in the mock-upcloud crate, 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.