1use super::frame::MessageKind;
8use serde_json::{Map as JsonMap, Value as JsonValue};
9use std::fmt;
10
11#[derive(Debug, Clone, PartialEq)]
12pub struct InsertDispatchPayload {
13 pub collection: String,
14 pub payload: Option<JsonValue>,
15 pub payloads: Option<Vec<JsonValue>>,
16 pub idempotency_key: Option<String>,
17 pub batch: bool,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct KeyPayload {
22 pub collection: String,
23 pub id: String,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct BulkOkPayload {
28 pub affected: u64,
29 pub rids: Vec<String>,
30 pub ids: Vec<String>,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum OperationPayloadError {
35 InvalidJson { op: &'static str, message: String },
36 ExpectedObject { op: &'static str },
37 MissingCollection { op: &'static str },
38 MissingId { op: &'static str },
39 TruncatedBulkOkCount,
40}
41
42impl fmt::Display for OperationPayloadError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 Self::InvalidJson { op, message } => write!(f, "{op}: invalid JSON: {message}"),
46 Self::ExpectedObject { op } => write!(f, "{op}: payload must be a JSON object"),
47 Self::MissingCollection { op } => {
48 write!(f, "{op}: missing 'collection' string")
49 }
50 Self::MissingId { op } => write!(f, "{op}: missing 'id' string"),
51 Self::TruncatedBulkOkCount => write!(f, "BulkOk truncated: expected 8-byte count"),
52 }
53 }
54}
55
56impl std::error::Error for OperationPayloadError {}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum OperationReplyError {
60 Engine(String),
61 UnexpectedKind {
62 expected: &'static str,
63 actual: MessageKind,
64 },
65}
66
67impl fmt::Display for OperationReplyError {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 Self::Engine(message) => write!(f, "{message}"),
71 Self::UnexpectedKind { expected, actual } => {
72 write!(f, "expected {expected}, got {actual:?}")
73 }
74 }
75 }
76}
77
78impl std::error::Error for OperationReplyError {}
79
80pub fn encode_insert_payload(collection: &str, payload: JsonValue) -> Vec<u8> {
81 let mut obj = JsonMap::new();
82 obj.insert(
83 "collection".into(),
84 JsonValue::String(collection.to_string()),
85 );
86 obj.insert("payload".into(), payload);
87 serde_json::to_vec(&JsonValue::Object(obj)).expect("insert payload JSON is serializable")
88}
89
90pub fn encode_bulk_insert_payload(collection: &str, payloads: Vec<JsonValue>) -> Vec<u8> {
91 let mut obj = JsonMap::new();
92 obj.insert(
93 "collection".into(),
94 JsonValue::String(collection.to_string()),
95 );
96 obj.insert("payloads".into(), JsonValue::Array(payloads));
97 serde_json::to_vec(&JsonValue::Object(obj)).expect("bulk insert payload JSON is serializable")
98}
99
100pub fn decode_insert_dispatch_payload(
101 bytes: &[u8],
102) -> Result<InsertDispatchPayload, OperationPayloadError> {
103 let obj = object_from_payload("Insert", bytes)?;
104 let collection = required_collection("Insert", &obj)?;
105 let payload = obj.get("payload").cloned();
106 let payloads = obj
107 .get("payloads")
108 .and_then(JsonValue::as_array)
109 .map(|items| items.to_vec());
110 let idempotency_key = obj
111 .get("idempotency_key")
112 .and_then(JsonValue::as_str)
113 .map(String::from);
114 let batch = obj
115 .get("batch")
116 .and_then(JsonValue::as_bool)
117 .unwrap_or(false);
118 Ok(InsertDispatchPayload {
119 collection,
120 payload,
121 payloads,
122 idempotency_key,
123 batch,
124 })
125}
126
127pub fn encode_key_payload(collection: &str, id: &str) -> Vec<u8> {
128 let mut obj = JsonMap::new();
129 obj.insert(
130 "collection".into(),
131 JsonValue::String(collection.to_string()),
132 );
133 obj.insert("id".into(), JsonValue::String(id.to_string()));
134 serde_json::to_vec(&JsonValue::Object(obj)).expect("key payload JSON is serializable")
135}
136
137pub fn decode_get_payload(bytes: &[u8]) -> Result<KeyPayload, OperationPayloadError> {
138 decode_key_payload("Get", bytes)
139}
140
141pub fn decode_delete_payload(bytes: &[u8]) -> Result<KeyPayload, OperationPayloadError> {
142 decode_key_payload("Delete", bytes)
143}
144
145pub fn encode_query_result_summary_payload(statement: &str, affected: u64) -> Vec<u8> {
146 let statement = serde_json::to_string(statement).expect("statement string is serializable");
147 format!(r#"{{"affected":{affected},"ok":true,"statement":{statement}}}"#).into_bytes()
148}
149
150pub fn decode_query_result_payload(bytes: &[u8]) -> Result<JsonValue, OperationPayloadError> {
151 json_value_from_payload("QueryResult", bytes)
152}
153
154pub fn encode_get_result_payload(found: bool) -> Vec<u8> {
155 let mut obj = JsonMap::new();
156 obj.insert("ok".into(), JsonValue::Bool(true));
157 obj.insert("found".into(), JsonValue::Bool(found));
158 serde_json::to_vec(&JsonValue::Object(obj)).expect("get result payload JSON is serializable")
159}
160
161pub fn decode_get_result_payload(bytes: &[u8]) -> Result<JsonValue, OperationPayloadError> {
162 json_value_from_payload("GetResult", bytes)
163}
164
165pub fn decode_text_payload(bytes: &[u8]) -> String {
166 String::from_utf8_lossy(bytes).into_owned()
167}
168
169pub fn decode_error_payload(bytes: &[u8]) -> String {
170 decode_text_payload(bytes)
171}
172
173pub fn expect_result_or_error(
174 kind: MessageKind,
175 payload: &[u8],
176) -> Result<&[u8], OperationReplyError> {
177 expect_payload_or_error(kind, payload, MessageKind::Result, "Result/Error")
178}
179
180pub fn expect_bulk_ok_or_error(
181 kind: MessageKind,
182 payload: &[u8],
183) -> Result<&[u8], OperationReplyError> {
184 expect_payload_or_error(kind, payload, MessageKind::BulkOk, "BulkOk/Error")
185}
186
187pub fn expect_delete_ok_or_error(
188 kind: MessageKind,
189 payload: &[u8],
190) -> Result<&[u8], OperationReplyError> {
191 expect_payload_or_error(kind, payload, MessageKind::DeleteOk, "DeleteOk/Error")
192}
193
194pub fn expect_pong_reply(kind: MessageKind) -> Result<(), OperationReplyError> {
195 if kind == MessageKind::Pong {
196 Ok(())
197 } else {
198 Err(OperationReplyError::UnexpectedKind {
199 expected: "Pong",
200 actual: kind,
201 })
202 }
203}
204
205pub fn encode_bulk_ok_payload(affected: u64, ids: Vec<JsonValue>) -> Vec<u8> {
206 let mut obj = JsonMap::new();
207 obj.insert("affected".into(), JsonValue::Number(affected.into()));
208 obj.insert("ids".into(), JsonValue::Array(ids));
209 serde_json::to_vec(&JsonValue::Object(obj)).expect("bulk ok payload JSON is serializable")
210}
211
212pub fn encode_bulk_ok_payload_from_json_ids_bytes(affected: u64, ids: &[u8]) -> Vec<u8> {
213 let ids = match serde_json::from_slice::<JsonValue>(ids) {
214 Ok(JsonValue::Array(items)) => items,
215 _ => Vec::new(),
216 };
217 encode_bulk_ok_payload(affected, ids)
218}
219
220pub fn encode_bulk_ok_payload_from_json_id_literals<I, S>(affected: u64, ids: I) -> Vec<u8>
221where
222 I: IntoIterator<Item = S>,
223 S: AsRef<str>,
224{
225 let ids = ids
226 .into_iter()
227 .map(|id| {
228 serde_json::from_str::<JsonValue>(id.as_ref())
229 .unwrap_or_else(|_| JsonValue::String(id.as_ref().to_string()))
230 })
231 .collect();
232 encode_bulk_ok_payload(affected, ids)
233}
234
235pub fn decode_bulk_ok_payload(bytes: &[u8]) -> Result<BulkOkPayload, OperationPayloadError> {
236 let obj = object_from_payload("BulkOk", bytes)?;
237 let affected = obj.get("affected").and_then(JsonValue::as_u64).unwrap_or(0);
238 let rids: Vec<String> = obj
239 .get("rids")
240 .or_else(|| obj.get("ids"))
241 .and_then(JsonValue::as_array)
242 .map(|items| items.iter().filter_map(json_id_to_string).collect())
243 .unwrap_or_default();
244 let ids: Vec<String> = obj
245 .get("ids")
246 .and_then(JsonValue::as_array)
247 .map(|items| items.iter().filter_map(json_id_to_string).collect())
248 .unwrap_or_else(|| rids.clone());
249 Ok(BulkOkPayload {
250 affected,
251 rids,
252 ids,
253 })
254}
255
256pub fn encode_bulk_ok_count_payload(count: u64) -> Vec<u8> {
257 count.to_le_bytes().to_vec()
258}
259
260pub fn decode_bulk_ok_count_payload(bytes: &[u8]) -> Result<u64, OperationPayloadError> {
261 if bytes.len() < 8 {
262 return Err(OperationPayloadError::TruncatedBulkOkCount);
263 }
264 let mut count = [0u8; 8];
265 count.copy_from_slice(&bytes[..8]);
266 Ok(u64::from_le_bytes(count))
267}
268
269pub fn decode_delete_ok_affected(bytes: &[u8]) -> Result<u64, OperationPayloadError> {
270 let obj = object_from_payload("DeleteOk", bytes)?;
271 Ok(obj.get("affected").and_then(JsonValue::as_u64).unwrap_or(0))
272}
273
274pub fn encode_delete_ok_payload(affected: u64) -> Vec<u8> {
275 let mut obj = JsonMap::new();
276 obj.insert("affected".into(), JsonValue::Number(affected.into()));
277 serde_json::to_vec(&JsonValue::Object(obj)).expect("delete ok payload JSON is serializable")
278}
279
280fn expect_payload_or_error<'a>(
281 actual: MessageKind,
282 payload: &'a [u8],
283 ok: MessageKind,
284 expected: &'static str,
285) -> Result<&'a [u8], OperationReplyError> {
286 match actual {
287 kind if kind == ok => Ok(payload),
288 MessageKind::Error => Err(OperationReplyError::Engine(decode_error_payload(payload))),
289 other => Err(OperationReplyError::UnexpectedKind {
290 expected,
291 actual: other,
292 }),
293 }
294}
295
296fn decode_key_payload(op: &'static str, bytes: &[u8]) -> Result<KeyPayload, OperationPayloadError> {
297 let obj = object_from_payload(op, bytes)?;
298 let collection = required_collection(op, &obj)?;
299 let id = match obj.get("id").and_then(JsonValue::as_str) {
300 Some(value) if !value.is_empty() => value.to_string(),
301 _ => return Err(OperationPayloadError::MissingId { op }),
302 };
303 Ok(KeyPayload { collection, id })
304}
305
306fn json_value_from_payload(
307 op: &'static str,
308 bytes: &[u8],
309) -> Result<JsonValue, OperationPayloadError> {
310 let value: JsonValue =
311 serde_json::from_slice(bytes).map_err(|err| OperationPayloadError::InvalidJson {
312 op,
313 message: err.to_string(),
314 })?;
315 match value {
316 JsonValue::Object(_) => Ok(value),
317 _ => Err(OperationPayloadError::ExpectedObject { op }),
318 }
319}
320
321fn object_from_payload(
322 op: &'static str,
323 bytes: &[u8],
324) -> Result<JsonMap<String, JsonValue>, OperationPayloadError> {
325 let value: JsonValue =
326 serde_json::from_slice(bytes).map_err(|err| OperationPayloadError::InvalidJson {
327 op,
328 message: err.to_string(),
329 })?;
330 match value {
331 JsonValue::Object(obj) => Ok(obj),
332 _ => Err(OperationPayloadError::ExpectedObject { op }),
333 }
334}
335
336fn required_collection(
337 op: &'static str,
338 obj: &JsonMap<String, JsonValue>,
339) -> Result<String, OperationPayloadError> {
340 match obj.get("collection").and_then(JsonValue::as_str) {
341 Some(value) if !value.is_empty() => Ok(value.to_string()),
342 _ => Err(OperationPayloadError::MissingCollection { op }),
343 }
344}
345
346fn json_id_to_string(value: &JsonValue) -> Option<String> {
347 value
348 .as_str()
349 .map(String::from)
350 .or_else(|| value.as_u64().map(|n| n.to_string()))
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn insert_payload_round_trips_single_and_bulk_shapes() {
359 let single = decode_insert_dispatch_payload(&encode_insert_payload(
360 "users",
361 serde_json::json!({"name":"Ada"}),
362 ))
363 .unwrap();
364 assert_eq!(single.collection, "users");
365 assert_eq!(single.payload.unwrap(), serde_json::json!({"name":"Ada"}));
366 assert!(single.payloads.is_none());
367
368 let bulk = decode_insert_dispatch_payload(&encode_bulk_insert_payload(
369 "users",
370 vec![serde_json::json!({"name":"Ada"})],
371 ))
372 .unwrap();
373 assert_eq!(bulk.collection, "users");
374 assert_eq!(bulk.payloads.unwrap().len(), 1);
375 assert!(bulk.payload.is_none());
376 }
377
378 #[test]
379 fn key_payload_round_trips_get_and_delete_contracts() {
380 let bytes = encode_key_payload("users", "42");
381 assert_eq!(
382 decode_get_payload(&bytes).unwrap(),
383 KeyPayload {
384 collection: "users".into(),
385 id: "42".into(),
386 }
387 );
388 assert_eq!(
389 decode_delete_payload(&bytes).unwrap(),
390 KeyPayload {
391 collection: "users".into(),
392 id: "42".into(),
393 }
394 );
395 }
396
397 #[test]
398 fn bulk_ok_decodes_ids_and_affected_count() {
399 let payload = encode_bulk_ok_payload(2, vec![JsonValue::Number(1.into()), "2".into()]);
400 assert_eq!(
401 decode_bulk_ok_payload(&payload).unwrap(),
402 BulkOkPayload {
403 affected: 2,
404 rids: vec!["1".into(), "2".into()],
405 ids: vec!["1".into(), "2".into()],
406 }
407 );
408
409 let payload = encode_bulk_ok_payload_from_json_ids_bytes(2, br#"[1,"2"]"#);
410 assert_eq!(decode_bulk_ok_payload(&payload).unwrap().ids.len(), 2);
411
412 let payload = encode_bulk_ok_payload_from_json_id_literals(2, ["1", r#""2""#]);
413 assert_eq!(
414 decode_bulk_ok_payload(&payload).unwrap().ids,
415 vec!["1".to_string(), "2".to_string()]
416 );
417 }
418
419 #[test]
420 fn operation_reply_payloads_encode_wire_visible_json_contracts() {
421 let query_bytes = encode_query_result_summary_payload("INSERT", 3);
422 assert_eq!(
423 query_bytes,
424 br#"{"affected":3,"ok":true,"statement":"INSERT"}"#
425 );
426 let query = decode_query_result_payload(&query_bytes).unwrap();
427 assert_eq!(query["ok"], JsonValue::Bool(true));
428 assert_eq!(query["statement"], JsonValue::String("INSERT".into()));
429 assert_eq!(query["affected"], JsonValue::Number(3.into()));
430
431 let get = decode_get_result_payload(&encode_get_result_payload(false)).unwrap();
432 assert_eq!(get["ok"], JsonValue::Bool(true));
433 assert_eq!(get["found"], JsonValue::Bool(false));
434 assert_eq!(decode_text_payload(b"raw result"), "raw result");
435 assert_eq!(decode_error_payload(b"engine failed"), "engine failed");
436 assert_eq!(
437 expect_result_or_error(MessageKind::Result, b"ok").unwrap(),
438 b"ok"
439 );
440 assert_eq!(
441 expect_bulk_ok_or_error(MessageKind::Error, b"failed").unwrap_err(),
442 OperationReplyError::Engine("failed".to_string())
443 );
444 assert_eq!(
445 expect_delete_ok_or_error(MessageKind::Pong, b"").unwrap_err(),
446 OperationReplyError::UnexpectedKind {
447 expected: "DeleteOk/Error",
448 actual: MessageKind::Pong
449 }
450 );
451 assert!(expect_pong_reply(MessageKind::Pong).is_ok());
452
453 assert_eq!(
454 decode_delete_ok_affected(&encode_delete_ok_payload(7)).unwrap(),
455 7
456 );
457 }
458
459 #[test]
460 fn bulk_ok_count_payload_round_trips_legacy_binary_shape() {
461 let payload = encode_bulk_ok_count_payload(42);
462 assert_eq!(payload.len(), 8);
463 assert_eq!(decode_bulk_ok_count_payload(&payload).unwrap(), 42);
464 assert_eq!(
465 decode_bulk_ok_count_payload(&payload[..7])
466 .unwrap_err()
467 .to_string(),
468 "BulkOk truncated: expected 8-byte count"
469 );
470 }
471}