Skip to main content

rivetkit_client/protocol/
codec.rs

1use 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			// Routing-level errors from the gateway (e.g. actor not found) are
191			// emitted as JSON regardless of the client encoding, so fall back to
192			// parsing JSON when the payload is not valid BARE.
193			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