Skip to main content

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}