use crate::{
merkle::{Family, Location},
qmdb::{any::value::ValueEncoding, operation::Committable},
};
use commonware_codec::{Encode as _, Error as CodecError, Read, Write};
use commonware_formatting::hex;
use commonware_runtime::{Buf, BufMut};
use core::fmt::Display;
pub(crate) mod fixed;
pub(crate) mod variable;
const COMMIT_CONTEXT: u8 = 0;
const APPEND_CONTEXT: u8 = 1;
pub trait Codec: ValueEncoding + Sized {
type ReadCfg: Clone + Send + Sync + 'static;
fn write_operation<F: Family>(op: &Operation<F, Self>, buf: &mut impl BufMut);
fn read_operation<F: Family>(
buf: &mut impl Buf,
cfg: &Self::ReadCfg,
) -> Result<Operation<F, Self>, CodecError>;
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum Operation<F: Family, V: ValueEncoding> {
Append(V::Value),
Commit(Option<V::Value>, Location<F>),
}
impl<F: Family, V: ValueEncoding> Operation<F, V> {
pub fn into_value(self) -> Option<V::Value> {
match self {
Self::Append(value) => Some(value),
Self::Commit(value, _) => value,
}
}
pub const fn has_floor(&self) -> Option<Location<F>> {
match self {
Self::Commit(_, loc) => Some(*loc),
Self::Append(_) => None,
}
}
}
impl<F: Family, V: Codec> Write for Operation<F, V> {
fn write(&self, buf: &mut impl BufMut) {
V::write_operation(self, buf)
}
}
impl<F: Family, V: Codec> Committable for Operation<F, V> {
fn is_commit(&self) -> bool {
matches!(self, Self::Commit(_, _))
}
}
impl<F: Family, V: Codec> Read for Operation<F, V> {
type Cfg = <V as Codec>::ReadCfg;
fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
V::read_operation(buf, cfg)
}
}
impl<F: Family, V: ValueEncoding> Display for Operation<F, V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Append(value) => write!(f, "[append value:{}]", hex(&value.encode())),
Self::Commit(value, floor) => {
if let Some(value) = value {
write!(f, "[commit {} floor:{}]", hex(&value.encode()), **floor)
} else {
write!(f, "[commit floor:{}]", **floor)
}
}
}
}
}
#[cfg(feature = "arbitrary")]
impl<F: Family, V: ValueEncoding> arbitrary::Arbitrary<'_> for Operation<F, V>
where
V::Value: for<'a> arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let choice = u.int_in_range(0..=1)?;
match choice {
0 => Ok(Self::Append(V::Value::arbitrary(u)?)),
1 => {
let metadata = Option::<V::Value>::arbitrary(u)?;
let floor = Location::<F>::arbitrary(u)?;
Ok(Self::Commit(metadata, floor))
}
_ => unreachable!(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{merkle::mmr, qmdb::any::value::VariableEncoding};
use commonware_codec::Encode;
use commonware_formatting::hex;
use commonware_utils::sequence::U64;
#[test]
fn display_append() {
let op = Operation::<mmr::Family, VariableEncoding<U64>>::Append(U64::new(12345));
assert_eq!(
format!("{op}"),
format!("[append value:{}]", hex(&U64::new(12345).encode()))
);
}
#[test]
fn display_commit_some() {
let op = Operation::<mmr::Family, VariableEncoding<U64>>::Commit(
Some(U64::new(42)),
Location::new(7),
);
assert_eq!(
format!("{op}"),
format!("[commit {} floor:7]", hex(&U64::new(42).encode()))
);
}
#[test]
fn display_commit_none() {
let op = Operation::<mmr::Family, VariableEncoding<U64>>::Commit(None, Location::new(3));
assert_eq!(format!("{op}"), "[commit floor:3]");
}
#[cfg(feature = "arbitrary")]
mod conformance {
use super::Operation;
use crate::{
merkle::mmr,
qmdb::any::value::{FixedEncoding, VariableEncoding},
};
use commonware_codec::conformance::CodecConformance;
use commonware_utils::sequence::U64;
commonware_conformance::conformance_tests! {
CodecConformance<Operation<mmr::Family, VariableEncoding<U64>>>,
CodecConformance<Operation<mmr::Family, FixedEncoding<U64>>>
}
}
}