laser_wire/batch.rs
1use serde::{Deserialize, Serialize};
2
3/// The mixed-operation batch request
4/// ([`AGDX_BATCH_CODE`](crate::codes::AGDX_BATCH_CODE)): up to
5/// [`MAX_BATCH_OPS`](crate::limits::MAX_BATCH_OPS) managed requests in one
6/// round trip, each carrying its own command code and encoded payload. The
7/// batch amortizes the round trip and nothing else: items execute
8/// independently in order, each yields its own result, and a failed item
9/// fails alone (explicitly NOT a transaction). A nested batch (an item whose
10/// code is the batch code) is rejected.
11#[derive(Clone, Debug, Serialize, Deserialize)]
12pub struct BatchRequest {
13 /// Op version, [`BATCH_OP_VERSION`](crate::codes::BATCH_OP_VERSION).
14 pub v: u32,
15 /// The managed requests, executed in order.
16 pub ops: Vec<BatchItem>,
17}
18
19/// One managed request inside a [`BatchRequest`]: the command code it would
20/// have been sent under on its own, and its encoded request bytes verbatim.
21#[derive(Clone, Debug, Serialize, Deserialize)]
22pub struct BatchItem {
23 /// The managed command code (e.g. `AGDX_KV_GET_CODE`).
24 pub code: u32,
25 /// The op's own encoded request frame, exactly what a standalone send
26 /// would carry.
27 #[serde(with = "crate::encoding::bin_bytes")]
28 pub payload: Vec<u8>,
29}
30
31/// The batch reply: each op's own reply bytes, in request order, exactly what
32/// a standalone round trip would have returned (including a typed error
33/// reply for an item that failed, so the caller decodes each slot with the
34/// item's own reply type).
35#[derive(Clone, Debug, Serialize, Deserialize)]
36pub struct BatchReply {
37 /// Per-op reply frames, index-aligned with the request's `ops`.
38 #[serde(with = "crate::encoding::vec_bin_bytes")]
39 pub results: Vec<Vec<u8>>,
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use crate::framing::{decode_named, encode_named};
46
47 #[test]
48 fn given_a_batch_when_round_tripped_then_should_preserve_items_and_results() {
49 let request = BatchRequest {
50 v: crate::codes::BATCH_OP_VERSION,
51 ops: vec![
52 BatchItem {
53 code: crate::codes::AGDX_KV_GET_CODE,
54 payload: b"\xa1av\x01".to_vec(),
55 },
56 BatchItem {
57 code: crate::codes::AGDX_QUERY_CODE,
58 payload: Vec::new(),
59 },
60 ],
61 };
62 let decoded: BatchRequest =
63 decode_named(&encode_named(&request).expect("encodes")).expect("decodes");
64 assert_eq!(decoded.ops.len(), 2);
65 assert_eq!(decoded.ops[0].code, crate::codes::AGDX_KV_GET_CODE);
66 assert_eq!(decoded.ops[0].payload, request.ops[0].payload);
67
68 let reply = BatchReply {
69 results: vec![b"ok".to_vec(), Vec::new()],
70 };
71 let decoded: BatchReply =
72 decode_named(&encode_named(&reply).expect("encodes")).expect("decodes");
73 assert_eq!(decoded.results, reply.results);
74 }
75}