cratestack_client_rust/envelope_error.rs
1//! Why a signed call failed (ADR 0006, cratestack#1007).
2
3use cratestack_core::CratestackError;
4
5/// A failure that belongs to the signed transport, not to the call.
6///
7/// A client that has an envelope never falls back to a plain call: every one
8/// of these is an error, and none of them carries a decoded body. In
9/// particular an answer that arrives without a seal is
10/// [`Unsigned`](Self::Unsigned) whatever its status, so a proxy that strips
11/// the envelope, or a server that was never asked to sign, cannot turn a
12/// `Required` client into a plain one.
13#[derive(Debug, thiserror::Error)]
14#[non_exhaustive]
15pub enum EnvelopeError {
16 /// The response was not a COSE message. Nothing in its body was read.
17 ///
18 /// The layer's own refusals (a wrong audience, a stale
19 /// `iat`, an unsupported media type, an oversized body) are always
20 /// unsigned (ADR 0006 D4), so a `401` here usually means the server did
21 /// not accept the request, not that a proxy tampered with the answer.
22 #[error("the server answered {status} without a COSE envelope; the body was not read")]
23 Unsigned { status: u16 },
24 /// The response is a COSE message that failed verification: a bad
25 /// signature or tag, an unknown key, or an answer sealed for a different
26 /// request, status, route or key. The reason is deliberately not
27 /// reported (ADR 0006 ยง10).
28 #[error("the response failed COSE verification")]
29 Unverified,
30 /// The server answered the unsigned `426` whose body code is
31 /// `contract_unsupported` (RPC) or `CONTRACT_UNSUPPORTED` (REST): it no
32 /// longer serves the wire shape this client has for `op` (a breaking
33 /// change to that op since this client was built), so the call was
34 /// refused before any key was looked up. **The answer is unsigned**, so
35 /// it is a hint and never proof: anyone on the path could send it, and a
36 /// caller should offer "update the app" for that feature, not treat the
37 /// server's contract as known. Its body is read only for that code,
38 /// which is unauthenticated; a `426` with any other code is
39 /// [`Unsigned`](Self::Unsigned). Calls to other ops are unaffected. `op`
40 /// is `"METHOD /template"` on REST and the op id on RPC.
41 #[error("the server no longer accepts this client's contract for `{op}`; update the client")]
42 ContractUnsupported { op: String },
43 /// Sealing the request failed: the signer (a keystore, a KMS) refused or
44 /// failed, or the envelope was misconfigured. Nothing was sent.
45 #[error("sealing the request failed: {0}")]
46 Seal(#[source] CratestackError),
47 /// Opening the response failed for a reason that is not the message's:
48 /// the key resolver's backend is down.
49 #[error("opening the response failed: {0}")]
50 Open(#[source] CratestackError),
51 /// The response was sealed under a payload type the call did not ask
52 /// for (its `Cratestack-Payload-Type`, absent meaning
53 /// `application/cbor`, is not among the types the request advertised).
54 /// The body was not opened, let alone decoded: a client never decodes a
55 /// type it did not request. `got` is what the server named, truncated.
56 #[error("the response was sealed as `{got}`, a payload type this call did not ask for")]
57 UnexpectedPayloadType { got: String },
58 /// Streams and subscriptions cannot be sealed yet (ADR 0006 P1); the
59 /// call was refused locally instead of being sent in plain.
60 #[error("sealed streams are not supported yet; call a unary operation")]
61 StreamsUnsupported,
62}
63
64impl EnvelopeError {
65 /// A stable identifier, for a caller that must tell the failures apart
66 /// without matching on this enum (the FFI bridge reports it as the
67 /// error's `remote_code`).
68 pub const fn code(&self) -> &'static str {
69 match self {
70 EnvelopeError::Unsigned { .. } => "envelope_unsigned",
71 EnvelopeError::Unverified => "envelope_unverified",
72 EnvelopeError::ContractUnsupported { .. } => "envelope_contract_unsupported",
73 EnvelopeError::Seal(_) => "envelope_seal",
74 EnvelopeError::Open(_) => "envelope_open",
75 EnvelopeError::UnexpectedPayloadType { .. } => "envelope_unexpected_payload_type",
76 EnvelopeError::StreamsUnsupported => "envelope_streams_unsupported",
77 }
78 }
79}