Skip to main content

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}