commonware_storage/qmdb/immutable/
operation.rs1use crate::{
7 mmr::Location,
8 qmdb::{any::VariableValue, operation::Operation as OperationTrait},
9};
10use bytes::{Buf, BufMut};
11use commonware_codec::{EncodeSize, Error as CodecError, Read, ReadExt as _, Write};
12use commonware_utils::{hex, Array};
13use core::fmt::Display;
14
15const SET_CONTEXT: u8 = 0;
17const COMMIT_CONTEXT: u8 = 1;
18
19#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
24pub enum Operation<K: Array, V: VariableValue> {
25 Set(K, V),
27
28 Commit(Option<V>),
30}
31
32impl<K: Array, V: VariableValue> Operation<K, V> {
33 pub const fn key(&self) -> Option<&K> {
35 match self {
36 Self::Set(key, _) => Some(key),
37 Self::Commit(_) => None,
38 }
39 }
40
41 pub const fn is_commit(&self) -> bool {
43 matches!(self, Self::Commit(_))
44 }
45}
46
47impl<K: Array, V: VariableValue> EncodeSize for Operation<K, V> {
48 fn encode_size(&self) -> usize {
49 1 + match self {
50 Self::Set(_, v) => K::SIZE + v.encode_size(),
51 Self::Commit(v) => v.encode_size(),
52 }
53 }
54}
55
56impl<K: Array, V: VariableValue> OperationTrait for Operation<K, V> {
57 type Key = K;
58
59 fn key(&self) -> Option<&Self::Key> {
60 self.key()
61 }
62
63 fn is_delete(&self) -> bool {
64 false
66 }
67
68 fn is_update(&self) -> bool {
69 matches!(self, Self::Set(_, _))
70 }
71
72 fn has_floor(&self) -> Option<Location> {
73 None
75 }
76}
77
78impl<K: Array, V: VariableValue> Write for Operation<K, V> {
79 fn write(&self, buf: &mut impl BufMut) {
80 match &self {
81 Self::Set(k, v) => {
82 SET_CONTEXT.write(buf);
83 k.write(buf);
84 v.write(buf);
85 }
86 Self::Commit(v) => {
87 COMMIT_CONTEXT.write(buf);
88 v.write(buf);
89 }
90 }
91 }
92}
93
94impl<K: Array, V: VariableValue> Read for Operation<K, V> {
95 type Cfg = <V as Read>::Cfg;
96
97 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
98 match u8::read(buf)? {
99 SET_CONTEXT => {
100 let key = K::read(buf)?;
101 let value = V::read_cfg(buf, cfg)?;
102 Ok(Self::Set(key, value))
103 }
104 COMMIT_CONTEXT => Ok(Self::Commit(Option::<V>::read_cfg(buf, cfg)?)),
105 e => Err(CodecError::InvalidEnum(e)),
106 }
107 }
108}
109
110impl<K: Array, V: VariableValue> Display for Operation<K, V> {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 match self {
113 Self::Set(key, value) => write!(f, "[key:{key} value:{}]", hex(&value.encode())),
114 Self::Commit(value) => {
115 if let Some(value) = value {
116 write!(f, "[commit {}]", hex(&value.encode()))
117 } else {
118 write!(f, "[commit]")
119 }
120 }
121 }
122 }
123}
124
125#[cfg(feature = "arbitrary")]
126impl<K: Array, V: VariableValue> arbitrary::Arbitrary<'_> for Operation<K, V>
127where
128 K: for<'a> arbitrary::Arbitrary<'a>,
129 V: for<'a> arbitrary::Arbitrary<'a>,
130{
131 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
132 let choice = u.int_in_range(0..=1)?;
133 match choice {
134 0 => {
135 let key = K::arbitrary(u)?;
136 let value = V::arbitrary(u)?;
137 Ok(Self::Set(key, value))
138 }
139 1 => Ok(Self::Commit(Option::<V>::arbitrary(u)?)),
140 _ => unreachable!(),
141 }
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use commonware_codec::{DecodeExt, Encode, EncodeSize, FixedSize as _};
149 use commonware_utils::sequence::U64;
150
151 #[test]
152 fn test_operation_key() {
153 let key = U64::new(1234);
154 let value = U64::new(56789);
155
156 let set_op = Operation::Set(key.clone(), value.clone());
157 assert_eq!(&key, set_op.key().unwrap());
158
159 let commit_op = Operation::<U64, U64>::Commit(Some(value));
160 assert_eq!(None, commit_op.key());
161
162 let commit_op_none = Operation::<U64, U64>::Commit(None);
163 assert_eq!(None, commit_op_none.key());
164 }
165
166 #[test]
167 fn test_operation_is_commit() {
168 let key = U64::new(1234);
169 let value = U64::new(56789);
170
171 let set_op = Operation::Set(key, value.clone());
172 assert!(!set_op.is_commit());
173
174 let commit_op = Operation::<U64, U64>::Commit(Some(value));
175 assert!(commit_op.is_commit());
176
177 let commit_op_none = Operation::<U64, U64>::Commit(None);
178 assert!(commit_op_none.is_commit());
179 }
180
181 #[test]
182 fn test_operation_encode_decode() {
183 let key = U64::new(1234);
184 let value = U64::new(56789);
185
186 let set_op = Operation::Set(key, value.clone());
188 let encoded = set_op.encode();
189 let decoded = Operation::<U64, U64>::decode(encoded).unwrap();
190 assert_eq!(set_op, decoded);
191
192 let commit_op = Operation::<U64, U64>::Commit(Some(value));
194 let encoded = commit_op.encode();
195 let decoded = Operation::<U64, U64>::decode(encoded).unwrap();
196 assert_eq!(commit_op, decoded);
197
198 let commit_op = Operation::<U64, U64>::Commit(None);
200 let encoded = commit_op.encode();
201 let decoded = Operation::<U64, U64>::decode(encoded).unwrap();
202 assert_eq!(commit_op, decoded);
203 }
204
205 #[test]
206 fn test_operation_encode_size() {
207 let key = U64::new(1234);
208 let value = U64::new(56789);
209
210 let set_op = Operation::Set(key, value.clone());
212 assert_eq!(set_op.encode_size(), 1 + U64::SIZE + value.encode_size());
213 assert_eq!(set_op.encode().len(), set_op.encode_size());
214
215 let commit_op = Operation::<U64, U64>::Commit(Some(value.clone()));
217 assert_eq!(commit_op.encode_size(), 1 + Some(value).encode_size());
218 assert_eq!(commit_op.encode().len(), commit_op.encode_size());
219
220 let commit_op = Operation::<U64, U64>::Commit(None);
222 assert_eq!(
223 commit_op.encode_size(),
224 1 + Option::<U64>::None.encode_size()
225 );
226 assert_eq!(commit_op.encode().len(), commit_op.encode_size());
227 }
228
229 #[test]
230 fn test_operation_display() {
231 let key = U64::new(1234);
232 let value = U64::new(56789);
233
234 let set_op = Operation::Set(key.clone(), value.clone());
236 assert_eq!(
237 format!("{set_op}"),
238 format!("[key:{key} value:{}]", hex(&value.encode()))
239 );
240
241 let commit_op = Operation::<U64, U64>::Commit(Some(value.clone()));
243 assert_eq!(
244 format!("{commit_op}"),
245 format!("[commit {}]", hex(&value.encode()))
246 );
247
248 let commit_op = Operation::<U64, U64>::Commit(None);
250 assert_eq!(format!("{commit_op}"), "[commit]");
251 }
252
253 #[test]
254 fn test_operation_invalid_context() {
255 let invalid = vec![0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0];
256 let decoded = Operation::<U64, U64>::decode(invalid.as_ref());
257 assert!(matches!(
258 decoded.unwrap_err(),
259 CodecError::InvalidEnum(0xFF)
260 ));
261 }
262
263 #[test]
264 fn test_operation_insufficient_buffer() {
265 let invalid = vec![SET_CONTEXT];
267 let decoded = Operation::<U64, U64>::decode(invalid.as_ref());
268 assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
269
270 let invalid = vec![COMMIT_CONTEXT];
272 let decoded = Operation::<U64, U64>::decode(invalid.as_ref());
273 assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
274 }
275
276 #[test]
277 fn test_operation_roundtrip_all_variants() {
278 let key = U64::new(100);
279 let value = U64::new(1000);
280
281 let operations: Vec<Operation<U64, U64>> = vec![
283 Operation::Set(key, value.clone()),
284 Operation::Commit(Some(value)),
285 Operation::Commit(None),
286 ];
287
288 for op in operations {
289 let encoded = op.encode();
290 let decoded = Operation::<U64, U64>::decode(encoded.clone()).unwrap();
291 assert_eq!(op, decoded, "Failed to roundtrip: {op:?}");
292 assert_eq!(encoded.len(), op.encode_size(), "Size mismatch for: {op:?}");
293 }
294 }
295
296 #[cfg(feature = "arbitrary")]
297 mod conformance {
298 use super::*;
299 use commonware_codec::conformance::CodecConformance;
300
301 commonware_conformance::conformance_tests! {
302 CodecConformance<Operation<U64, U64>>
303 }
304 }
305}