willow-data-model 0.7.0

The core datatypes of Willow, an eventually consistent data store with improved distributed deletion.
Documentation
use arbitrary::Arbitrary;

use anyhash::{Hasher, HasherWrite};

use ufotofu::codec_prelude::*;

/// A payload digest for testing.
///
/// To hash a bytestring, add all its bytes modulo 256, and take the result modulo 4,  mapping to the different values as follows:
///
/// - 0 -> Clubs
/// - 1 -> Diamonds
/// - 2 -> Hearts
/// - 3 -> Spades
///
/// In case you did not notice, **this is not cryptographically secure**.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Arbitrary)]
#[allow(missing_docs)]
pub enum TestDigest {
    Clubs,
    Diamonds,
    Hearts,
    Spades,
}

pub use TestDigest::*;

impl Encodable for TestDigest {
    async fn encode<C>(&self, consumer: &mut C) -> Result<(), C::Error>
    where
        C: BulkConsumer<Item = u8> + ?Sized,
    {
        match self {
            Clubs => consumer.consume_item(0).await,
            Diamonds => consumer.consume_item(1).await,
            Hearts => consumer.consume_item(2).await,
            Spades => consumer.consume_item(3).await,
        }
    }
}

impl EncodableKnownLength for TestDigest {
    fn len_of_encoding(&self) -> usize {
        1
    }
}

impl Decodable for TestDigest {
    type ErrorReason = Blame;

    async fn decode<P>(
        producer: &mut P,
    ) -> Result<Self, DecodeError<P::Final, P::Error, Self::ErrorReason>>
    where
        P: BulkProducer<Item = u8> + ?Sized,
        Self: Sized,
    {
        let byte = producer.produce_item().await?;

        match byte {
            0 => Ok(Clubs),
            1 => Ok(Diamonds),
            2 => Ok(Hearts),
            3 => Ok(Spades),
            _ => Err(DecodeError::Other(Blame::TheirFault)),
        }
    }
}

impl DecodableCanonic for TestDigest {
    type ErrorCanonic = Blame;

    async fn decode_canonic<P>(
        producer: &mut P,
    ) -> Result<Self, DecodeError<P::Final, P::Error, Self::ErrorCanonic>>
    where
        P: BulkProducer<Item = u8> + ?Sized,
        Self: Sized,
    {
        Self::decode(producer).await
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Arbitrary, Default)]
/// The state for hashing a bytestring into a [`TestDigest`].
pub struct TestDigestHasher(u8);

impl HasherWrite for TestDigestHasher {
    fn write(&mut self, bytes: &[u8]) {
        for byte in bytes {
            self.0 = self.0.wrapping_add(*byte)
        }
    }
}

impl Hasher<TestDigest> for TestDigestHasher {
    fn finish(&self) -> TestDigest {
        match self.0 % 4 {
            0 => TestDigest::Clubs,
            1 => TestDigest::Diamonds,
            2 => TestDigest::Hearts,
            3 => TestDigest::Spades,
            _ => unreachable!(),
        }
    }
}