Skip to main content

laser_wire/
mutation.rs

1use serde::{Deserialize, Serialize};
2
3pub const MANAGED_REQUEST_VERSION: u32 = 1;
4
5#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
6pub struct ManagedRequestEnvelope {
7    pub v: u32,
8    pub operation_id: u128,
9    #[serde(with = "crate::encoding::bin_bytes")]
10    pub payload: Vec<u8>,
11}
12
13impl ManagedRequestEnvelope {
14    pub fn validate(&self) -> Result<(), &'static str> {
15        if self.v != MANAGED_REQUEST_VERSION {
16            return Err("unsupported managed request version");
17        }
18        if self.operation_id == 0 {
19            return Err("managed request operation id must not be zero");
20        }
21        Ok(())
22    }
23}
24
25/// One managed command stored on a mutation topic. `payload` is the canonical
26/// typed request encoded with the normal wire framing for `command_code`, so
27/// append and fold share one request schema instead of maintaining a second
28/// mutation representation.
29#[derive(Clone, Debug, Serialize, Deserialize)]
30pub struct MutationCommandEnvelope {
31    pub v: u32,
32    pub operation_id: u128,
33    pub timestamp_micros: u64,
34    pub command_code: u32,
35    #[serde(with = "crate::encoding::bin_bytes")]
36    pub payload: Vec<u8>,
37}
38
39impl MutationCommandEnvelope {
40    pub fn validate(&self) -> Result<(), &'static str> {
41        if self.v == 0 {
42            return Err("mutation command version must not be zero");
43        }
44        if self.operation_id == 0 {
45            return Err("mutation command operation id must not be zero");
46        }
47        Ok(())
48    }
49}
50
51#[cfg(all(test, feature = "cbor"))]
52mod tests {
53    use super::*;
54    use crate::codes::{AGDX_KV_SET_CODE, KV_OP_VERSION};
55    use crate::framing::{decode_named, encode_named};
56
57    #[test]
58    fn given_a_command_envelope_when_round_tripped_then_should_preserve_request_bytes() {
59        let envelope = MutationCommandEnvelope {
60            v: KV_OP_VERSION,
61            operation_id: 42,
62            timestamp_micros: 1_700_000_000_000_000,
63            command_code: AGDX_KV_SET_CODE,
64            payload: vec![0, 1, 2, 255],
65        };
66        let bytes = encode_named(&envelope).expect("encodes");
67        let back: MutationCommandEnvelope = decode_named(&bytes).expect("decodes");
68        back.validate().expect("the envelope is valid");
69        assert_eq!(back.v, KV_OP_VERSION);
70        assert_eq!(back.operation_id, 42);
71        assert_eq!(back.timestamp_micros, 1_700_000_000_000_000);
72        assert_eq!(back.command_code, AGDX_KV_SET_CODE);
73        assert_eq!(back.payload, vec![0, 1, 2, 255]);
74    }
75
76    #[test]
77    fn given_a_managed_request_when_round_tripped_then_should_preserve_operation_identity() {
78        let request = ManagedRequestEnvelope {
79            v: MANAGED_REQUEST_VERSION,
80            operation_id: u128::MAX,
81            payload: vec![4, 3, 2, 1],
82        };
83
84        let bytes = encode_named(&request).expect("managed request encodes");
85        let back: ManagedRequestEnvelope = decode_named(&bytes).expect("managed request decodes");
86
87        assert_eq!(back, request);
88        back.validate().expect("the request is valid");
89    }
90
91    #[test]
92    fn given_a_zero_operation_identity_when_validated_then_should_reject_it() {
93        let request = ManagedRequestEnvelope {
94            v: MANAGED_REQUEST_VERSION,
95            operation_id: 0,
96            payload: Vec::new(),
97        };
98        let command = MutationCommandEnvelope {
99            v: KV_OP_VERSION,
100            operation_id: 0,
101            timestamp_micros: 1,
102            command_code: AGDX_KV_SET_CODE,
103            payload: Vec::new(),
104        };
105
106        assert!(request.validate().is_err());
107        assert!(command.validate().is_err());
108    }
109}