commonware_storage/qmdb/keyless/
operation.rs1use 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
7const COMMIT_CONTEXT: u8 = 0;
9const APPEND_CONTEXT: u8 = 1;
10
11#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
13pub enum Operation<V: VariableValue> {
14 Append(V),
16
17 Commit(Option<V>),
19}
20
21impl<V: VariableValue> Operation<V> {
22 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}