Skip to main content

commonware_storage/qmdb/keyless/operation/
mod.rs

1use crate::qmdb::{any::value::ValueEncoding, operation::Committable};
2use commonware_codec::{Encode as _, Error as CodecError, Read, Write};
3use commonware_runtime::{Buf, BufMut};
4use commonware_utils::hex;
5use core::fmt::Display;
6
7pub(crate) mod fixed;
8pub(crate) mod variable;
9
10// Context byte prefixes for identifying the operation type.
11const COMMIT_CONTEXT: u8 = 0;
12const APPEND_CONTEXT: u8 = 1;
13
14/// Delegates Operation-level codec (Write, Read) to the value encoding.
15///
16/// Fixed and variable encodings have different wire formats. Fixed pads to a uniform size,
17/// variable does not. A single blanket `impl Write for Operation<V>` dispatches here, while the
18/// two impls of this trait (on FixedEncoding and VariableEncoding) live on different Self types
19/// and therefore do not overlap.
20pub trait Codec: ValueEncoding + Sized {
21    type ReadCfg: Clone + Send + Sync + 'static;
22
23    fn write_operation(op: &Operation<Self>, buf: &mut impl BufMut);
24    fn read_operation(
25        buf: &mut impl Buf,
26        cfg: &Self::ReadCfg,
27    ) -> Result<Operation<Self>, CodecError>;
28}
29
30/// Operations for keyless stores.
31#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
32pub enum Operation<V: ValueEncoding> {
33    /// Wraps the value appended to the database by this operation.
34    Append(V::Value),
35
36    /// Indicates the database has been committed.
37    Commit(Option<V::Value>),
38}
39
40impl<V: ValueEncoding> Operation<V> {
41    /// Returns the value (if any) wrapped by this operation.
42    pub fn into_value(self) -> Option<V::Value> {
43        match self {
44            Self::Append(value) => Some(value),
45            Self::Commit(value) => value,
46        }
47    }
48}
49
50impl<V: Codec> Write for Operation<V> {
51    fn write(&self, buf: &mut impl BufMut) {
52        V::write_operation(self, buf)
53    }
54}
55
56impl<V: Codec> Committable for Operation<V> {
57    fn is_commit(&self) -> bool {
58        matches!(self, Self::Commit(_))
59    }
60}
61
62impl<V: Codec> Read for Operation<V> {
63    type Cfg = <V as Codec>::ReadCfg;
64
65    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
66        V::read_operation(buf, cfg)
67    }
68}
69
70impl<V: ValueEncoding> Display for Operation<V> {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        match self {
73            Self::Append(value) => write!(f, "[append value:{}]", hex(&value.encode())),
74            Self::Commit(value) => {
75                if let Some(value) = value {
76                    write!(f, "[commit {}]", hex(&value.encode()))
77                } else {
78                    write!(f, "[commit]")
79                }
80            }
81        }
82    }
83}
84
85#[cfg(feature = "arbitrary")]
86impl<V: ValueEncoding> arbitrary::Arbitrary<'_> for Operation<V>
87where
88    V::Value: for<'a> arbitrary::Arbitrary<'a>,
89{
90    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
91        let choice = u.int_in_range(0..=1)?;
92        match choice {
93            0 => Ok(Self::Append(V::Value::arbitrary(u)?)),
94            1 => Ok(Self::Commit(Option::<V::Value>::arbitrary(u)?)),
95            _ => unreachable!(),
96        }
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::qmdb::any::value::VariableEncoding;
104    use commonware_codec::Encode;
105    use commonware_utils::{hex, sequence::U64};
106
107    #[test]
108    fn display_append() {
109        let op = Operation::<VariableEncoding<U64>>::Append(U64::new(12345));
110        assert_eq!(
111            format!("{op}"),
112            format!("[append value:{}]", hex(&U64::new(12345).encode()))
113        );
114    }
115
116    #[test]
117    fn display_commit_some() {
118        let op = Operation::<VariableEncoding<U64>>::Commit(Some(U64::new(42)));
119        assert_eq!(
120            format!("{op}"),
121            format!("[commit {}]", hex(&U64::new(42).encode()))
122        );
123    }
124
125    #[test]
126    fn display_commit_none() {
127        let op = Operation::<VariableEncoding<U64>>::Commit(None);
128        assert_eq!(format!("{op}"), "[commit]");
129    }
130
131    #[cfg(feature = "arbitrary")]
132    mod conformance {
133        use super::Operation;
134        use crate::qmdb::any::value::{FixedEncoding, VariableEncoding};
135        use commonware_codec::conformance::CodecConformance;
136        use commonware_utils::sequence::U64;
137
138        commonware_conformance::conformance_tests! {
139            CodecConformance<Operation<VariableEncoding<U64>>>,
140            CodecConformance<Operation<FixedEncoding<U64>>>
141        }
142    }
143}