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::OwnedVersionedData;
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 => {
40 wire::versioned::HttpActionRequest::wrap_latest(wire::HttpActionRequest {
41 args: serde_cbor::to_vec(&args.to_vec())?,
42 })
43 .serialize_with_embedded_version(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 OwnedVersionedData>::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::wrap_latest(wire::HttpQueueSendRequest {
103 body: serde_cbor::to_vec(body)?,
104 name: Some(name.to_owned()),
105 wait: Some(wait),
106 timeout,
107 })
108 .serialize_with_embedded_version(wire::PROTOCOL_VERSION)
109 }
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub enum QueueSendStatus {
115 Completed,
116 TimedOut,
117 Other(String),
118}
119
120#[derive(Debug, Clone)]
121pub struct QueueSendResult {
122 pub status: QueueSendStatus,
123 pub response: Option<JsonValue>,
124}
125
126pub fn decode_http_queue_response(
127 encoding: EncodingKind,
128 payload: &[u8],
129) -> Result<QueueSendResult> {
130 let (status, response) = match encoding {
131 EncodingKind::Json => {
132 let value: JsonValue = serde_json::from_slice(payload)?;
133 let status = value
134 .get("status")
135 .and_then(JsonValue::as_str)
136 .ok_or_else(|| anyhow!("queue response missing status"))?
137 .to_owned();
138 let response = value.get("response").cloned();
139 (status, response)
140 }
141 EncodingKind::Cbor => {
142 let value: JsonValue = serde_cbor::from_slice(payload)?;
143 let status = value
144 .get("status")
145 .and_then(JsonValue::as_str)
146 .ok_or_else(|| anyhow!("queue response missing status"))?
147 .to_owned();
148 let response = value.get("response").cloned();
149 (status, response)
150 }
151 EncodingKind::Bare => {
152 let response =
153 <wire::versioned::HttpQueueSendResponse as OwnedVersionedData>::deserialize_with_embedded_version(
154 payload,
155 )
156 .context("decode bare queue response")?;
157 let body = response
158 .response
159 .map(|payload| serde_cbor::from_slice(&payload))
160 .transpose()?;
161 (response.status, body)
162 }
163 };
164
165 let status = match status.as_str() {
166 "completed" => QueueSendStatus::Completed,
167 "timedOut" => QueueSendStatus::TimedOut,
168 _ => QueueSendStatus::Other(status),
169 };
170
171 Ok(QueueSendResult { status, response })
172}
173
174pub fn decode_http_error(
175 encoding: EncodingKind,
176 payload: &[u8],
177) -> Result<(String, String, String, Option<JsonValue>)> {
178 match encoding {
179 EncodingKind::Json => {
180 let value: JsonValue = serde_json::from_slice(payload)?;
181 error_from_json_value(&value)
182 }
183 EncodingKind::Cbor => {
184 let value: JsonValue = serde_cbor::from_slice(payload)?;
185 error_from_json_value(&value)
186 }
187 EncodingKind::Bare => {
188 match <wire::versioned::HttpResponseError as OwnedVersionedData>::deserialize_with_embedded_version(
192 payload,
193 ) {
194 Ok(error) => {
195 let metadata = error
196 .metadata
197 .map(|payload| serde_cbor::from_slice(&payload))
198 .transpose()?;
199 Ok((error.group, error.code, error.message, metadata))
200 }
201 Err(bare_err) => serde_json::from_slice::<JsonValue>(payload)
202 .ok()
203 .and_then(|value| error_from_json_value(&value).ok())
204 .ok_or(bare_err)
205 .context("decode bare http error"),
206 }
207 }
208 }
209}
210
211fn to_server_json_value(value: &to_server::ToServer) -> Result<JsonValue> {
212 let body = match &value.body {
213 to_server::ToServerBody::ActionRequest(request) => json!({
214 "tag": "ActionRequest",
215 "val": {
216 "id": request.id,
217 "name": request.name,
218 "args": serde_cbor::from_slice::<JsonValue>(&request.args)
219 .context("decode websocket action args for json/cbor transport")?,
220 },
221 }),
222 to_server::ToServerBody::SubscriptionRequest(request) => json!({
223 "tag": "SubscriptionRequest",
224 "val": {
225 "eventName": request.event_name,
226 "subscribe": request.subscribe,
227 },
228 }),
229 };
230 Ok(json!({ "body": body }))
231}
232
233fn to_client_from_json_value(value: &JsonValue) -> Result<to_client::ToClient> {
234 let body = value
235 .get("body")
236 .and_then(JsonValue::as_object)
237 .ok_or_else(|| anyhow!("actor websocket response missing body"))?;
238 let tag = body
239 .get("tag")
240 .and_then(JsonValue::as_str)
241 .ok_or_else(|| anyhow!("actor websocket response missing tag"))?;
242 let value = body
243 .get("val")
244 .and_then(JsonValue::as_object)
245 .ok_or_else(|| anyhow!("actor websocket response missing val"))?;
246
247 let body = match tag {
248 "Init" => to_client::ToClientBody::Init(to_client::Init {
249 actor_id: json_string(value, "actorId")?,
250 connection_id: json_string(value, "connectionId")?,
251 connection_token: value
252 .get("connectionToken")
253 .and_then(JsonValue::as_str)
254 .map(ToOwned::to_owned),
255 }),
256 "Error" => to_client::ToClientBody::Error(to_client::Error {
257 group: json_string(value, "group")?,
258 code: json_string(value, "code")?,
259 message: json_string(value, "message")?,
260 metadata: value.get("metadata").map(serde_cbor::to_vec).transpose()?,
261 action_id: value.get("actionId").map(parse_json_u64).transpose()?,
262 }),
263 "ActionResponse" => to_client::ToClientBody::ActionResponse(to_client::ActionResponse {
264 id: parse_json_u64(
265 value
266 .get("id")
267 .ok_or_else(|| anyhow!("action response missing id"))?,
268 )?,
269 output: serde_cbor::to_vec(
270 value
271 .get("output")
272 .ok_or_else(|| anyhow!("action response missing output"))?,
273 )?,
274 }),
275 "Event" => to_client::ToClientBody::Event(to_client::Event {
276 name: json_string(value, "name")?,
277 args: serde_cbor::to_vec(
278 value
279 .get("args")
280 .ok_or_else(|| anyhow!("event response missing args"))?,
281 )?,
282 }),
283 other => return Err(anyhow!("unknown actor websocket response tag `{other}`")),
284 };
285
286 Ok(to_client::ToClient { body })
287}
288
289fn encode_to_server_bare(value: &to_server::ToServer) -> Result<Vec<u8>> {
290 let body = match &value.body {
291 to_server::ToServerBody::ActionRequest(request) => {
292 wire::ToServerBody::ActionRequest(wire::ActionRequest {
293 id: serde_bare::Uint(request.id),
294 name: request.name.clone(),
295 args: request.args.clone(),
296 })
297 }
298 to_server::ToServerBody::SubscriptionRequest(request) => {
299 wire::ToServerBody::SubscriptionRequest(wire::SubscriptionRequest {
300 event_name: request.event_name.clone(),
301 subscribe: request.subscribe,
302 })
303 }
304 };
305
306 wire::versioned::ToServer::wrap_latest(wire::ToServer { body })
307 .serialize_with_embedded_version(wire::PROTOCOL_VERSION)
308}
309
310fn decode_to_client_bare(payload: &[u8]) -> Result<to_client::ToClient> {
311 let message =
312 <wire::versioned::ToClient as OwnedVersionedData>::deserialize_with_embedded_version(
313 payload,
314 )
315 .context("decode bare actor websocket response")?;
316
317 let body = match message.body {
318 wire::ToClientBody::Init(init) => to_client::ToClientBody::Init(to_client::Init {
319 actor_id: init.actor_id,
320 connection_id: init.connection_id,
321 connection_token: None,
322 }),
323 wire::ToClientBody::Error(error) => to_client::ToClientBody::Error(to_client::Error {
324 group: error.group,
325 code: error.code,
326 message: error.message,
327 metadata: error.metadata,
328 action_id: error.action_id.map(|id| id.0),
329 }),
330 wire::ToClientBody::ActionResponse(response) => {
331 to_client::ToClientBody::ActionResponse(to_client::ActionResponse {
332 id: response.id.0,
333 output: response.output,
334 })
335 }
336 wire::ToClientBody::Event(event) => to_client::ToClientBody::Event(to_client::Event {
337 name: event.name,
338 args: event.args,
339 }),
340 };
341
342 Ok(to_client::ToClient { body })
343}
344
345fn json_string(value: &serde_json::Map<String, JsonValue>, key: &str) -> Result<String> {
346 value
347 .get(key)
348 .and_then(JsonValue::as_str)
349 .map(ToOwned::to_owned)
350 .ok_or_else(|| anyhow!("json object missing string field `{key}`"))
351}
352
353fn parse_json_u64(value: &JsonValue) -> Result<u64> {
354 match value {
355 JsonValue::Number(number) => number
356 .as_u64()
357 .ok_or_else(|| anyhow!("json number is not an unsigned integer")),
358 JsonValue::Array(values) if values.len() == 2 => {
359 let tag = values[0]
360 .as_str()
361 .ok_or_else(|| anyhow!("json bigint tag is not a string"))?;
362 let raw = values[1]
363 .as_str()
364 .ok_or_else(|| anyhow!("json bigint value is not a string"))?;
365 if tag != "$BigInt" {
366 return Err(anyhow!("unsupported json bigint tag `{tag}`"));
367 }
368 raw.parse::<u64>().context("parse json bigint")
369 }
370 _ => Err(anyhow!("invalid json unsigned integer")),
371 }
372}
373
374fn error_from_json_value(value: &JsonValue) -> Result<(String, String, String, Option<JsonValue>)> {
375 let value = value
376 .as_object()
377 .ok_or_else(|| anyhow!("http error response is not an object"))?;
378 Ok((
379 json_string(value, "group")?,
380 json_string(value, "code")?,
381 json_string(value, "message")?,
382 value.get("metadata").cloned(),
383 ))
384}
385
386#[cfg(test)]
387mod tests {
388 use serde_json::json;
389
390 use super::*;
391
392 #[test]
393 fn bare_action_response_round_trips() {
394 let payload = wire::versioned::HttpActionResponse::wrap_latest(wire::HttpActionResponse {
395 output: serde_cbor::to_vec(&json!({ "ok": true })).unwrap(),
396 })
397 .serialize_with_embedded_version(wire::PROTOCOL_VERSION)
398 .unwrap();
399
400 let output = decode_http_action_response(EncodingKind::Bare, &payload).unwrap();
401 assert_eq!(output, json!({ "ok": true }));
402 }
403
404 #[test]
405 fn bare_queue_request_has_embedded_version() {
406 let payload = encode_http_queue_request(
407 EncodingKind::Bare,
408 "jobs",
409 &json!({ "id": 1 }),
410 true,
411 Some(50),
412 )
413 .unwrap();
414 assert_eq!(
415 u16::from_le_bytes([payload[0], payload[1]]),
416 wire::PROTOCOL_VERSION
417 );
418 }
419}