Skip to main content

cratestack_client_rust/runtime/
wire.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5use crate::envelope_error::EnvelopeError;
6use crate::error::ClientError;
7
8#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
9pub enum RuntimeCodecConfig {
10    #[default]
11    Cbor,
12    Json,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
16pub enum RuntimeEnvelopeConfig {
17    #[default]
18    None,
19    /// Seal requests and open responses as COSE_Sign1 (cratestack#1007).
20    /// Needs `RuntimeHandle::with_envelope` (feature `cose`).
21    CoseSign1,
22    /// The same with COSE_Mac0 (an HMAC key shared with the server).
23    CoseMac0,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
27pub struct RuntimeTransportConfig {
28    pub codec: RuntimeCodecConfig,
29    pub envelope: RuntimeEnvelopeConfig,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33pub struct RuntimeHeader {
34    pub name: String,
35    pub value: String,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
39pub struct RuntimeRequestWire {
40    pub method: String,
41    pub path: String,
42    pub canonical_query: Option<String>,
43    pub headers: Vec<RuntimeHeader>,
44    pub body: Vec<u8>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
48pub struct RuntimeResponseWire {
49    pub status_code: u16,
50    pub headers: Vec<RuntimeHeader>,
51    pub body: Vec<u8>,
52}
53
54#[repr(u32)]
55#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
56pub enum RuntimeErrorCode {
57    Transport = 1,
58    Codec = 2,
59    State = 3,
60    InvalidResponse = 4,
61    Remote = 5,
62    BadInput = 6,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
66pub struct RuntimeErrorWire {
67    pub code: RuntimeErrorCode,
68    pub http_status: Option<u16>,
69    pub message: String,
70    pub remote_code: Option<String>,
71    pub remote_body: Option<Vec<u8>>,
72}
73
74impl From<ClientError> for RuntimeErrorWire {
75    fn from(value: ClientError) -> Self {
76        match value {
77            ClientError::Transport(error) => Self {
78                code: RuntimeErrorCode::Transport,
79                http_status: None,
80                message: error.to_string(),
81                remote_code: None,
82                remote_body: None,
83            },
84            // cratestack#926. Deliberately reported as `Transport`
85            // rather than a seventh `RuntimeErrorCode`: this enum's
86            // discriminants are a serialized FFI contract every bridge
87            // consumer (Dart, Swift, Kotlin) switches on, so adding a
88            // code breaks them, and from the far side of the bridge a
89            // middleware failure *is* "the HTTP call did not complete"
90            // — retries exhausted, a circuit breaker open, a token
91            // refresh that failed. The middleware's own message is
92            // preserved verbatim in `message`.
93            #[cfg(feature = "middleware")]
94            ClientError::Middleware(error) => Self {
95                code: RuntimeErrorCode::Transport,
96                http_status: None,
97                message: error.to_string(),
98                remote_code: None,
99                remote_body: None,
100            },
101            ClientError::Codec(error) => Self {
102                code: RuntimeErrorCode::Codec,
103                http_status: Some(error.status_code().as_u16()),
104                message: error.to_string(),
105                remote_code: Some(error.code().to_owned()),
106                remote_body: None,
107            },
108            ClientError::State(message) => Self {
109                code: RuntimeErrorCode::State,
110                http_status: None,
111                message,
112                remote_code: None,
113                remote_body: None,
114            },
115            ClientError::InvalidResponse(message) => Self {
116                code: RuntimeErrorCode::InvalidResponse,
117                http_status: None,
118                message,
119                remote_code: None,
120                remote_body: None,
121            },
122            ClientError::BadInput(message) => Self {
123                code: RuntimeErrorCode::BadInput,
124                http_status: None,
125                message,
126                remote_code: None,
127                remote_body: None,
128            },
129            // cratestack#1007. No new `RuntimeErrorCode` (see the
130            // middleware arm above): a response that was not sealed or did
131            // not verify is an invalid response, a refused stream is bad
132            // input, and a signer that failed means the call never left
133            // (`Transport`). `EnvelopeError::code` tells them apart.
134            ClientError::Envelope(error) => Self {
135                code: match &error {
136                    EnvelopeError::Unsigned { .. }
137                    | EnvelopeError::ContractUnsupported { .. }
138                    | EnvelopeError::Unverified
139                    | EnvelopeError::Open(_) => RuntimeErrorCode::InvalidResponse,
140                    EnvelopeError::StreamsUnsupported => RuntimeErrorCode::BadInput,
141                    _ => RuntimeErrorCode::Transport,
142                },
143                http_status: match &error {
144                    EnvelopeError::Unsigned { status } => Some(*status),
145                    EnvelopeError::ContractUnsupported { .. } => Some(426),
146                    _ => None,
147                },
148                remote_code: Some(error.code().to_owned()),
149                message: error.to_string(),
150                remote_body: None,
151            },
152            // The challenges stay on the Rust side: the FFI error wire has
153            // no slot for headers.
154            ClientError::Remote {
155                status,
156                error,
157                message,
158                ..
159            } => Self {
160                code: RuntimeErrorCode::Remote,
161                http_status: Some(status.as_u16()),
162                remote_code: error.as_ref().map(|value| value.code.clone()),
163                remote_body: error
164                    .as_ref()
165                    .and_then(|value| serde_json::to_vec(value).ok()),
166                message,
167            },
168        }
169    }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub enum RuntimeStateStoreConfig {
174    InMemory,
175    JsonFile { path: PathBuf },
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct RuntimeConfigWire {
180    pub base_url: String,
181    pub state_store: RuntimeStateStoreConfig,
182    pub transport: RuntimeTransportConfig,
183}