Expand description
The wire shapes — including the inconsistencies, which are the point.
UpCloud stringifies numbers inconsistently: a storage’s size is a JSON
number, a server’s core_number and memory_amount are strings, a device’s
storage_size is a number in one place and a string in the next, and a stop
request’s timeout is a string that is accepted-and-ignored when sent as a
number. monetize-cloud-impl’s Num exists only because of this. A mock
that rendered everything as a number would let a client that cannot read
"10" pass, and that client would then fail at the provider.
So the rendering here is hand-built serde_json rather than derived structs,
and every place the real API sends a string, this sends a string.
§Two label shapes, and they are not the same shape
A storage carries "labels": [{key,value}]. A server carries
"labels": {"label": [{key,value}]}. Both are filtered with
?label=key%3Dvalue. This is not a simplification anybody would invent; it
is what the API does, and a client written against one shape breaks on the
other.
§Behaviour 4: the list is not the detail
GET /1.3/server carries no storage_devices, no ip_addresses — those
exist only in GET /1.3/server/{uuid}. GET /1.3/storage DOES carry
labels. A caller that lists servers and reads attachments off the rows gets
an empty list and concludes there is nothing attached.
§created IS sent — behaviour 10 as first written was wrong
MEASURED against the live account 2026-09-20, two volumes, both endpoints:
GET /1.3/storage/private and GET /1.3/storage/{uuid} each carried
"created". This crate previously omitted it from both and claimed the
provider did too, which made a young volume and an old orphan the same row —
a verdict path the real account cannot produce.
with_created is now true unless crate::Fault::WithholdCreatedField is
armed by name. What behaviour 9 actually turns on is the TITLE, not the
date: the Resize Backup carries no product name, and the label is what
recognises it.
Constants§
- PLANS
- ★ The plan table, ONCE.
GET /1.3/planrenders this list andPOST /1.3/serverrefuses anything not in it, so the endpoint can never advertise a plan the create would then callINVALID_PLAN.
Functions§
- auth_
failed - Behaviour 1 — the other error envelope, and the whole reason it is here.
- cores_
of - The plan table. Small on purpose: these are the plans this estate buys, and a plan name that is not here is refused by name rather than invented — an invented plan would price at zero and make a spend cap untestable.
- error
- The ORDINARY error envelope:
{"error": {"error_code", "error_message"}}. - import
- The direct-upload import object, exactly as the live one answers.
- iso8601
- Virtual milliseconds as an RFC 3339 stamp. The epoch is arbitrary and fixed (2026-01-01T00:00:00Z) so a replayed seed produces byte-identical stamps.
- labels_
enveloped - labels_
flat - memory_
of - not_
implemented - The mock’s own refusal for a path it does not implement. Loud, and it names the path: an unimplemented call must never be able to read as a working one.
- plan_
known - price
GET /1.3/price.- server
- One server row.
- storage
- One storage row.
with_createdis the fault;detailadds theserversenvelope that onlyGET /1.3/storage/{uuid}carries.