rivetkit_client/protocol/
codec.rs1use anyhow::{anyhow, Context, Result};
2use rivetkit_client_protocol as wire;
3use serde::Serialize;
4use serde_json::{json, Value as JsonValue};
5use vbare::Versioned;
6
7use crate::EncodingKind;
8
9use super::{to_client, to_server};
10
11pub fn encode_to_server(encoding: EncodingKind, value: &to_server::ToServer) -> Result<Vec<u8>> {
12 match encoding {
13 EncodingKind::Json => Ok(serde_json::to_vec(&to_server_json_value(value)?)?),
14 EncodingKind::Cbor => Ok(serde_cbor::to_vec(&to_server_json_value(value)?)?),
15 EncodingKind::Bare => encode_to_server_bare(value),
16 }
17}
18
19pub fn decode_to_client(encoding: EncodingKind, payload: &[u8]) -> Result<to_client::ToClient> {
20 match encoding {
21 EncodingKind::Json => {
22 let value: JsonValue =
23 serde_json::from_slice(payload).context("decode actor websocket json response")?;
24 to_client_from_json_value(&value)
25 }
26 EncodingKind::Cbor => {
27 let value: JsonValue =
28 serde_cbor::from_slice(payload).context("decode actor websocket cbor response")?;
29 to_client_from_json_value(&value)
30 }
31 EncodingKind::Bare => decode_to_client_bare(payload),
32 }
33}
34
35pub fn encode_http_action_request(encoding: EncodingKind, args: &[JsonValue]) -> Result<Vec<u8>> {
36 match encoding {
37 EncodingKind::Json => Ok(serde_json::to_vec(&json!({ "args": args }))?),
38 EncodingKind::Cbor => Ok(serde_cbor::to_vec(&json!({ "args": args }))?),
39 EncodingKind::Bare => wire::versioned::HttpActionRequest::serialize_with_embedded_version(
40 &wire::HttpActionRequest {
41 args: serde_cbor::to_vec(&args.to_vec())?.into(),
42 },
43 wire::PROTOCOL_VERSION,
44 ),
45 }
46}
47
48pub fn decode_http_action_response(encoding: EncodingKind, payload: &[u8]) -> Result<JsonValue> {
49 match encoding {
50 EncodingKind::Json => {
51 let value: JsonValue = serde_json::from_slice(payload)?;
52 value
53 .get("output")
54 .cloned()
55 .ok_or_else(|| anyhow!("action response missing output"))
56 }
57 EncodingKind::Cbor => {
58 let value: JsonValue = serde_cbor::from_slice(payload)?;
59 value
60 .get("output")
61 .cloned()
62 .ok_or_else(|| anyhow!("action response missing output"))
63 }
64 EncodingKind::Bare => {
65 let response =
66 <wire::versioned::HttpActionResponse as Versioned>::deserialize_with_embedded_version(
67 payload,
68 )
69 .context("decode bare action response")?;
70 Ok(serde_cbor::from_slice(&response.output)?)
71 }
72 }
73}
74
75pub fn encode_http_queue_request<T: Serialize>(
76 encoding: EncodingKind,
77 name: &str,
78 body: &T,
79 wait: bool,
80 timeout: Option<u64>,
81) -> Result<Vec<u8>> {
82 #[derive(Serialize)]
83 struct JsonQueueRequest<'a, T: Serialize + ?Sized> {
84 name: &'a str,
85 body: &'a T,
86 wait: bool,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 timeout: Option<u64>,
89 }
90
91 let request = JsonQueueRequest {
92 name,
93 body,
94 wait,
95 timeout,
96 };
97
98 match encoding {
99 EncodingKind::Json => Ok(serde_json::to_vec(&request)?),
100 EncodingKind::Cbor => Ok(serde_cbor::to_vec(&request)?),
101 EncodingKind::Bare => {
102 wire::versioned::HttpQueueSendRequest::serialize_with_embedded_version(
103 &wire::HttpQueueSendRequest {
104 body: serde_cbor::to_vec(body)?.into(),
105 name: Some(name.to_owned().into()),
106 wait: Some(wait),
107 timeout,
108 },
109 wire::PROTOCOL_VERSION,
110 )
111 }
112 }
113}
114
115#[derive(Debug, Clone, PartialEq, Eq)]
116pub enum QueueSendStatus {
117 Completed,
118 TimedOut,
119 Other(String),
120}
121
122#[derive(Debug, Clone)]
123pub struct QueueSendResult {
124 pub status: QueueSendStatus,
125 pub response: Option<JsonValue>,
126}
127
128pub fn decode_http_queue_response(
129 encoding: EncodingKind,
130 payload: &[u8],
131) -> Result<QueueSendResult> {
132 let (status, response) = match encoding {
133 EncodingKind::Json => {
134 let value: JsonValue = serde_json::from_slice(payload)?;
135 let status = value
136 .get("status")
137 .and_then(JsonValue::as_str)
138 .ok_or_else(|| anyhow!("queue response missing status"))?
139 .to_owned();
140 let response = value.get("response").cloned();
141 (status, response)
142 }
143 EncodingKind::Cbor => {
144 let value: JsonValue = serde_cbor::from_slice(payload)?;
145 let status = value
146 .get("status")
147 .and_then(JsonValue::as_str)
148 .ok_or_else(|| anyhow!("queue response missing status"))?
149 .to_owned();
150 let response = value.get("response").cloned();
151 (status, response)
152 }
153 EncodingKind::Bare => {
154 let response =
155 <wire::versioned::HttpQueueSendResponse as Versioned>::deserialize_with_embedded_version(
156 payload,
157 )
158 .context("decode bare queue response")?;
159 let body = response
160 .response
161 .map(|payload| serde_cbor::from_slice(&payload))
162 .transpose()?;
163 (response.status.to_string(), body)
164 }
165 };
166
167 let status = match status.as_str() {
168 "completed" => QueueSendStatus::Completed,
169 "timedOut" => QueueSendStatus::TimedOut,
170 _ => QueueSendStatus::Other(status),
171 };
172
173 Ok(QueueSendResult { status, response })
174}
175
176pub fn decode_http_error(
177 encoding: EncodingKind,
178 payload: &[u8],
179) -> Result<(String, String, String, Option<JsonValue>)> {
180 match encoding {
181 EncodingKind::Json => {
182 let value: JsonValue = serde_json::from_slice(payload)?;
183 error_from_json_value(&value)
184 }
185 EncodingKind::Cbor => {
186 let value: JsonValue = serde_cbor::from_slice(payload)?;
187 error_from_json_value(&value)
188 }
189 EncodingKind::Bare => {
190 match <wire::versioned::HttpResponseError as Versioned>::deserialize_with_embedded_version(
194 payload,
195 ) {
196 Ok(error) => {
197 let metadata = error
198 .metadata
199 .map(|payload| serde_cbor::from_slice(&payload))
200 .transpose()?;
201 Ok((error.group.to_string(), error.code.to_string(), error.message.to_string(), metadata))
202 }
203 Err(bare_err) => serde_json::from_slice::<JsonValue>(payload)
204 .ok()
205 .and_then(|value| error_from_json_value(&value).ok())
206 .ok_or(bare_err)
207 .context("decode bare http error"),
208 }
209 }
210 }
211}
212
213fn to_server_json_value(value: &to_server::ToServer) -> Result<JsonValue> {
214 let body = match &value.body {
215 to_server::ToServerBody::ActionRequest(request) => json!({
216 "tag": "ActionRequest",
217 "val": {
218 "id": request.id,
219 "name": request.name,
220 "args": serde_cbor::from_slice::<JsonValue>(&request.args)
221 .context("decode websocket action args for json/cbor transport")?,
222 },
223 }),
224 to_server::ToServerBody::SubscriptionRequest(request) => json!({
225 "tag": "SubscriptionRequest",
226 "val": {
227 "eventName": request.event_name,
228 "subscribe": request.subscribe,
229 },
230 }),
231 };
232 Ok(json!({ "body": body }))
233}
234
235fn to_client_from_json_value(value: &JsonValue) -> Result<to_client::ToClient> {
236 let body = value
237 .get("body")
238 .and_then(JsonValue::as_object)
239 .ok_or_else(|| anyhow!("actor websocket response missing body"))?;
240 let tag = body
241 .get("tag")
242 .and_then(JsonValue::as_str)
243 .ok_or_else(|| anyhow!("actor websocket response missing tag"))?;
244 let value = body
245 .get("val")
246 .and_then(JsonValue::as_object)
247 .ok_or_else(|| anyhow!("actor websocket response missing val"))?;
248
249 let body = match tag {
250 "Init" => to_client::ToClientBody::Init(to_client::Init {
251 actor_id: json_string(value, "actorId")?,
252 connection_id: json_string(value, "connectionId")?,
253 connection_token: value
254 .get("connectionToken")
255 .and_then(JsonValue::as_str)
256 .map(ToOwned::to_owned),
257 }),
258 "Error" => to_client::ToClientBody::Error(to_client::Error {
259 group: json_string(value, "group")?,
260 code: json_string(value, "code")?,
261 message: json_string(value, "message")?,
262 metadata: value.get("metadata").map(serde_cbor::to_vec).transpose()?,
263 action_id: value.get("actionId").map(parse_json_u64).transpose()?,
264 }),
265 "ActionResponse" => to_client::ToClientBody::ActionResponse(to_client::ActionResponse {
266 id: parse_json_u64(
267 value
268 .get("id")
269 .ok_or_else(|| anyhow!("action response missing id"))?,
270 )?,
271 output: serde_cbor::to_vec(
272 value
273 .get("output")
274 .ok_or_else(|| anyhow!("action response missing output"))?,
275 )?,
276 }),
277 "Event" => to_client::ToClientBody::Event(to_client::Event {
278 name: json_string(value, "name")?,
279 args: serde_cbor::to_vec(
280 value
281 .get("args")
282 .ok_or_else(|| anyhow!("event response missing args"))?,
283 )?,
284 }),
285 other => return Err(anyhow!("unknown actor websocket response tag `{other}`")),
286 };
287
288 Ok(to_client::ToClient { body })
289}
290
291fn encode_to_server_bare(value: &to_server::ToServer) -> Result<Vec<u8>> {
292 let body = match &value.body {
293 to_server::ToServerBody::ActionRequest(request) => {
294 wire::ToServerBody::ActionRequest(wire::ActionRequest {
295 id: request.id,
296 name: request.name.clone().into(),
297 args: request.args.clone().into(),
298 })
299 }
300 to_server::ToServerBody::SubscriptionRequest(request) => {
301 wire::ToServerBody::SubscriptionRequest(wire::SubscriptionRequest {
302 event_name: request.event_name.clone().into(),
303 subscribe: request.subscribe,
304 })
305 }
306 };
307
308 wire::versioned::ToServer::serialize_with_embedded_version(
309 &wire::ToServer { body },
310 wire::PROTOCOL_VERSION,
311 )
312}
313
314fn decode_to_client_bare(payload: &[u8]) -> Result<to_client::ToClient> {
315 let message =
316 <wire::versioned::ToClient as Versioned>::deserialize_with_embedded_version(payload)
317 .context("decode bare actor websocket response")?;
318
319 let body = match message.body {
320 wire::ToClientBody::Init(init) => to_client::ToClientBody::Init(to_client::Init {
321 actor_id: init.actor_id.to_string(),
322 connection_id: init.connection_id.to_string(),
323 connection_token: None,
324 }),
325 wire::ToClientBody::Error(error) => to_client::ToClientBody::Error(to_client::Error {
326 group: error.group.to_string(),
327 code: error.code.to_string(),
328 message: error.message.to_string(),
329 metadata: error.metadata.map(|value| value.to_vec()),
330 action_id: error.action_id,
331 }),
332 wire::ToClientBody::ActionResponse(response) => {
333 to_client::ToClientBody::ActionResponse(to_client::ActionResponse {
334 id: response.id,
335 output: response.output.to_vec(),
336 })
337 }
338 wire::ToClientBody::Event(event) => to_client::ToClientBody::Event(to_client::Event {
339 name: event.name.to_string(),
340 args: event.args.to_vec(),
341 }),
342 };
343
344 Ok(to_client::ToClient { body })
345}
346
347fn json_string(value: &serde_json::Map<String, JsonValue>, key: &str) -> Result<String> {
348 value
349 .get(key)
350 .and_then(JsonValue::as_str)
351 .map(ToOwned::to_owned)
352 .ok_or_else(|| anyhow!("json object missing string field `{key}`"))
353}
354
355fn parse_json_u64(value: &JsonValue) -> Result<u64> {
356 match value {
357 JsonValue::Number(number) => number
358 .as_u64()
359 .ok_or_else(|| anyhow!("json number is not an unsigned integer")),
360 JsonValue::Array(values) if values.len() == 2 => {
361 let tag = values[0]
362 .as_str()
363 .ok_or_else(|| anyhow!("json bigint tag is not a string"))?;
364 let raw = values[1]
365 .as_str()
366 .ok_or_else(|| anyhow!("json bigint value is not a string"))?;
367 if tag != "$BigInt" {
368 return Err(anyhow!("unsupported json bigint tag `{tag}`"));
369 }
370 raw.parse::<u64>().context("parse json bigint")
371 }
372 _ => Err(anyhow!("invalid json unsigned integer")),
373 }
374}
375
376fn error_from_json_value(value: &JsonValue) -> Result<(String, String, String, Option<JsonValue>)> {
377 let value = value
378 .as_object()
379 .ok_or_else(|| anyhow!("http error response is not an object"))?;
380 Ok((
381 json_string(value, "group")?,
382 json_string(value, "code")?,
383 json_string(value, "message")?,
384 value.get("metadata").cloned(),
385 ))
386}
387
388