commonware_storage/qmdb/keyless/
operation.rs

1use crate::qmdb::{any::VariableValue, operation::Committable};
2use bytes::{Buf, BufMut};
3use commonware_codec::{EncodeSize, Error as CodecError, Read, ReadExt, Write};
4use commonware_utils::hex;
5use core::fmt::Display;
6
7// Context byte prefixes for identifying the operation type.
8const COMMIT_CONTEXT: u8 = 0;
9const APPEND_CONTEXT: u8 = 1;
10
11/// Operations for keyless stores.
12#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
13pub enum Operation<V: VariableValue> {
14    /// Wraps the value appended to the database by this operation.
15    Append(V),
16
17    /// Indicates the database has been committed.
18    Commit(Option<V>),
19}
20
21impl<V: VariableValue> Operation<V> {
22    /// Returns the value (if any) wrapped by this operation.
23    pub fn into_value(self) -> Option<V> {
24        match self {
25            Self::Append(value) => Some(value),
26            Self::Commit(value) => value,
27        }
28    }
29}
30
31impl<V: VariableValue> EncodeSize for Operation<V> {
32    fn encode_size(&self) -> usize {
33        1 + match self {
34            Self::Append(v) => v.encode_size(),
35            Self::Commit(v) => v.encode_size(),
36        }
37    }
38}
39
40impl<V: VariableValue> Write for Operation<V> {
41    fn write(&self, buf: &mut impl BufMut) {
42        match &self {
43            Self::Append(value) => {
44                APPEND_CONTEXT.write(buf);
45                value.write(buf);
46            }
47            Self::Commit(metadata) => {
48                COMMIT_CONTEXT.write(buf);
49                metadata.write(buf);
50            }
51        }
52    }
53}
54
55impl<V: VariableValue> Committable for Operation<V> {
56    fn is_commit(&self) -> bool {
57        matches!(self, Self::Commit(_))
58    }
59}
60
61impl<V: VariableValue> Read for Operation<V> {
62    type Cfg = <V as Read>::Cfg;
63
64    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
65        match u8::read(buf)? {
66            APPEND_CONTEXT => Ok(Self::Append(V::read_cfg(buf, cfg)?)),
67            COMMIT_CONTEXT => Ok(Self::Commit(Option::<V>::read_cfg(buf, cfg)?)),
68            e => Err(CodecError::InvalidEnum(e)),
69        }
70    }
71}
72
73impl<V: VariableValue> Display for Operation<V> {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        match self {
76            Self::Append(value) => write!(f, "[append value:{}]", hex(&value.encode())),
77            Self::Commit(value) => {
78                if let Some(value) = value {
79                    write!(f, "[commit {}]", hex(&value.encode()))
80                } else {
81                    write!(f, "[commit]")
82                }
83            }
84        }
85    }
86}
87
88#[cfg(feature = "arbitrary")]
89impl<V: VariableValue> arbitrary::Arbitrary<'_> for Operation<V>
90where
91    V: for<'a> arbitrary::Arbitrary<'a>,
92{
93    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
94        let choice = u.int_in_range(0..=1)?;
95        match choice {
96            0 => Ok(Self::Append(V::arbitrary(u)?)),
97            1 => Ok(Self::Commit(Option::<V>::arbitrary(u)?)),
98            _ => unreachable!(),
99        }
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use commonware_codec::{DecodeExt, Encode, FixedSize as _};
107    use commonware_utils::{hex, sequence::U64};
108
109    #[test]
110    fn test_operation_keyless_append() {
111        let append_op = Operation::Append(U64::new(12345));
112
113        let encoded = append_op.encode();
114        assert_eq!(encoded.len(), 1 + U64::SIZE);
115
116        let decoded = Operation::<U64>::decode(encoded).unwrap();
117        assert_eq!(append_op, decoded);
118        assert_eq!(
119            format!("{append_op}"),
120            format!("[append value:{}]", hex(&U64::new(12345).encode()))
121        );
122    }
123
124    #[test]
125    fn test_operation_keyless_commit() {
126        let metadata = Some(U64::new(12345));
127        let commit_op = Operation::Commit(metadata.clone());
128
129        let encoded = commit_op.encode();
130        assert_eq!(encoded.len(), 1 + metadata.encode_size());
131
132        let decoded = Operation::<U64>::decode(encoded).unwrap();
133        let Operation::Commit(metadata_decoded) = decoded else {
134            panic!("expected commit operation");
135        };
136        assert_eq!(metadata, metadata_decoded);
137    }
138
139    #[test]
140    fn test_operation_keyless_invalid_context() {
141        let invalid = vec![0xFF; 1];
142        let decoded = Operation::<U64>::decode(invalid.as_ref());
143        assert!(matches!(
144            decoded.unwrap_err(),
145            CodecError::InvalidEnum(0xFF)
146        ));
147    }
148
149    #[cfg(feature = "arbitrary")]
150    mod conformance {
151        use super::*;
152        use commonware_codec::conformance::CodecConformance;
153
154        commonware_conformance::conformance_tests! {
155            CodecConformance<Operation<U64>>
156        }
157    }
158}