use super::{Operation, COMMIT_CONTEXT, SET_CONTEXT};
use crate::qmdb::{
any::{value::VariableEncoding, VariableValue},
operation::Key,
};
use commonware_codec::{EncodeSize, Error as CodecError, Read, ReadExt as _, Write};
use commonware_runtime::{Buf, BufMut};
impl<K: Key, V: VariableValue> EncodeSize for Operation<K, VariableEncoding<V>> {
fn encode_size(&self) -> usize {
1 + match self {
Self::Set(k, v) => k.encode_size() + v.encode_size(),
Self::Commit(v) => v.encode_size(),
}
}
}
impl<K: Key, V: VariableValue> Write for Operation<K, VariableEncoding<V>> {
fn write(&self, buf: &mut impl BufMut) {
match &self {
Self::Set(k, v) => {
SET_CONTEXT.write(buf);
k.write(buf);
v.write(buf);
}
Self::Commit(v) => {
COMMIT_CONTEXT.write(buf);
v.write(buf);
}
}
}
}
impl<K: Key, V: VariableValue> Read for Operation<K, VariableEncoding<V>> {
type Cfg = (<K as Read>::Cfg, <V as Read>::Cfg);
fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
match u8::read(buf)? {
SET_CONTEXT => {
let key = K::read_cfg(buf, &cfg.0)?;
let value = V::read_cfg(buf, &cfg.1)?;
Ok(Self::Set(key, value))
}
COMMIT_CONTEXT => Ok(Self::Commit(Option::<V>::read_cfg(buf, &cfg.1)?)),
e => Err(CodecError::InvalidEnum(e)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use commonware_codec::{DecodeExt, Encode, EncodeSize, FixedSize as _};
use commonware_utils::sequence::U64;
type VarOp = Operation<U64, VariableEncoding<U64>>;
#[test]
fn test_operation_encode_decode() {
let key = U64::new(1234);
let value = U64::new(56789);
let set_op = VarOp::Set(key, value.clone());
let encoded = set_op.encode();
let decoded = VarOp::decode(encoded).unwrap();
assert_eq!(set_op, decoded);
let commit_op = VarOp::Commit(Some(value));
let encoded = commit_op.encode();
let decoded = VarOp::decode(encoded).unwrap();
assert_eq!(commit_op, decoded);
let commit_op = VarOp::Commit(None);
let encoded = commit_op.encode();
let decoded = VarOp::decode(encoded).unwrap();
assert_eq!(commit_op, decoded);
}
#[test]
fn test_operation_encode_size() {
let key = U64::new(1234);
let value = U64::new(56789);
let set_op = VarOp::Set(key, value.clone());
assert_eq!(set_op.encode_size(), 1 + U64::SIZE + value.encode_size());
assert_eq!(set_op.encode().len(), set_op.encode_size());
let commit_op = VarOp::Commit(Some(value.clone()));
assert_eq!(commit_op.encode_size(), 1 + Some(value).encode_size());
assert_eq!(commit_op.encode().len(), commit_op.encode_size());
let commit_op = VarOp::Commit(None);
assert_eq!(
commit_op.encode_size(),
1 + Option::<U64>::None.encode_size()
);
assert_eq!(commit_op.encode().len(), commit_op.encode_size());
}
#[test]
fn test_operation_invalid_context() {
let invalid = vec![0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let decoded = VarOp::decode(invalid.as_ref());
assert!(matches!(
decoded.unwrap_err(),
CodecError::InvalidEnum(0xFF)
));
}
#[test]
fn test_operation_insufficient_buffer() {
let invalid = vec![SET_CONTEXT];
let decoded = VarOp::decode(invalid.as_ref());
assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
let invalid = vec![COMMIT_CONTEXT];
let decoded = VarOp::decode(invalid.as_ref());
assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
}
#[test]
fn test_operation_roundtrip_all_variants() {
let key = U64::new(100);
let value = U64::new(1000);
let operations: Vec<VarOp> = vec![
VarOp::Set(key, value.clone()),
VarOp::Commit(Some(value)),
VarOp::Commit(None),
];
for op in operations {
let encoded = op.encode();
let decoded = VarOp::decode(encoded.clone()).unwrap();
assert_eq!(op, decoded, "Failed to roundtrip: {op:?}");
assert_eq!(encoded.len(), op.encode_size(), "Size mismatch for: {op:?}");
}
}
#[test]
fn test_operation_variable_key_roundtrip() {
use commonware_codec::Decode as _;
let key = vec![1u8, 2, 3, 4, 5];
let cfg = ((commonware_codec::RangeCfg::from(0..=100usize), ()), ());
let set_op = Operation::Set(key, U64::new(42));
let encoded = set_op.encode();
assert_eq!(encoded.len(), set_op.encode_size());
let decoded =
Operation::<Vec<u8>, VariableEncoding<U64>>::decode_cfg(encoded, &cfg).unwrap();
assert_eq!(set_op, decoded);
let commit_op = Operation::<Vec<u8>, VariableEncoding<U64>>::Commit(Some(U64::new(42)));
let encoded = commit_op.encode();
let decoded =
Operation::<Vec<u8>, VariableEncoding<U64>>::decode_cfg(encoded, &cfg).unwrap();
assert_eq!(commit_op, decoded);
let empty_key_op = Operation::Set(vec![], U64::new(99));
let encoded = empty_key_op.encode();
let decoded =
Operation::<Vec<u8>, VariableEncoding<U64>>::decode_cfg(encoded, &cfg).unwrap();
assert_eq!(empty_key_op, decoded);
}
#[cfg(feature = "arbitrary")]
mod conformance {
use super::*;
use commonware_codec::conformance::CodecConformance;
type VarKeyOp = Operation<Vec<u8>, VariableEncoding<U64>>;
commonware_conformance::conformance_tests! {
CodecConformance<VarOp>,
CodecConformance<VarKeyOp>
}
}
}