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