commonware-storage 2026.4.0

Persist and retrieve data from an abstract store.
Documentation
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);

        // Test Set operation
        let set_op = VarOp::Set(key, value.clone());
        let encoded = set_op.encode();
        let decoded = VarOp::decode(encoded).unwrap();
        assert_eq!(set_op, decoded);

        // Test Commit operation with value
        let commit_op = VarOp::Commit(Some(value));
        let encoded = commit_op.encode();
        let decoded = VarOp::decode(encoded).unwrap();
        assert_eq!(commit_op, decoded);

        // Test Commit operation without value
        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), ()), ());

        // Test Set with variable-length key
        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);

        // Test Commit (key-independent, should work the same)
        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);

        // Test empty key
        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>
        }
    }
}