willow-data-model 0.6.0

The core datatypes of Willow, an eventually consistent data store with improved distributed deletion.
Documentation
use anyhash::Hasher;
use ufotofu::prelude::*;

use crate::prelude::*;

/// A builder for [`Entry`].
///
/// See [`Entry::builder`] and [`Entry::prefilled_builder`].
pub struct EntryBuilder<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> {
    namespace_id: Option<N>,
    subspace_id: Option<S>,
    path: Option<Path<MCL, MCC, MPL>>,
    timestamp: Option<Timestamp>,
    payload_length: Option<u64>,
    payload_digest: Option<PD>,
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD>
    EntryBuilder<MCL, MCC, MPL, N, S, PD>
{
    pub(crate) fn create_empty() -> Self {
        Self {
            namespace_id: None,
            subspace_id: None,
            path: None,
            timestamp: None,
            payload_length: None,
            payload_digest: None,
        }
    }

    /// Sets the [namespace_id](https://willowprotocol.org/specs/data-model/index.html#entry_namespace_id) of the entry being built.
    pub fn namespace_id(&mut self, value: N) -> &mut Self {
        let new = self;
        new.namespace_id = Some(value);
        new
    }

    /// Sets the [subspace_id](https://willowprotocol.org/specs/data-model/index.html#entry_subspace_id) of the entry being built.
    pub fn subspace_id(&mut self, value: S) -> &mut Self {
        let new = self;
        new.subspace_id = Some(value);
        new
    }

    /// Sets the [path](https://willowprotocol.org/specs/data-model/index.html#entry_path) of the entry being built.
    pub fn path(&mut self, value: Path<MCL, MCC, MPL>) -> &mut Self {
        let new = self;
        new.path = Some(value);
        new
    }

    /// Sets the [timestamp](https://willowprotocol.org/specs/data-model/index.html#entry_timestamp) of the entry being built.
    pub fn timestamp<T: Into<Timestamp>>(&mut self, value: T) -> &mut Self {
        let new = self;
        new.timestamp = Some(value.into());
        new
    }

    /// Sets the [payload_length](https://willowprotocol.org/specs/data-model/index.html#entry_payload_length) of the entry being built.
    pub fn payload_length(&mut self, value: u64) -> &mut Self {
        let new = self;
        new.payload_length = Some(value);
        new
    }

    /// Sets the [payload_digest](https://willowprotocol.org/specs/data-model/index.html#entry_payload_digest) of the entry being built.
    pub fn payload_digest(&mut self, value: PD) -> &mut Self {
        let new = self;
        new.payload_digest = Some(value);
        new
    }

    /// Sets the [payload_length](https://willowprotocol.org/specs/data-model/index.html#entry_payload_length) and [payload_digest](https://willowprotocol.org/specs/data-model/index.html#entry_payload_digest) of the entry being built to those of the given [Payload](https://willowprotocol.org/specs/data-model/index.html#Payload).
    ///
    /// The type parameter `H` is the type of the [`Hasher`] which hashes the payload into a payload digest (of type `Digest: Into<PD>`). Its [`Default`] impl provides the initial state of the hasher.
    pub fn payload<Payload: AsRef<[u8]>, H, Digest>(&mut self, payload: Payload) -> &mut Self
    where
        H: Default + Hasher<Digest>,
        Digest: Into<PD>,
    {
        let new = self;

        let mut hasher = H::default();
        hasher.write(payload.as_ref());

        new.payload_digest = Some(hasher.finish().into());
        new.payload_length = Some(payload.as_ref().len() as u64);

        new
    }

    /// Sets the [payload_length](https://willowprotocol.org/specs/data-model/index.html#entry_payload_length) and [payload_digest](https://willowprotocol.org/specs/data-model/index.html#entry_payload_digest) of the entry being built to those of the payload given by the producer.
    ///
    /// The type parameter `H` is the type of the [`Hasher`] which hashes the payload into a payload digest (of type `Digest: Into<PD>`). Its [`Default`] impl provides the initial state of the hasher.
    pub async fn payload_async<P, H, Digest>(
        &mut self,
        payload_producer: &mut P,
    ) -> Result<&mut Self, P::Error>
    where
        H: Default + Hasher<Digest>,
        Digest: Into<PD>,
        P: BulkProducer<Item = u8, Final = ()>,
    {
        let new = self;

        let mut hasher = H::default();
        let mut payload_len = 0;

        loop {
            match payload_producer
                .expose_items_sync(|partial_payload| {
                    hasher.write(partial_payload);
                    payload_len += partial_payload.len();
                    (partial_payload.len(), ())
                })
                .await?
            {
                Either::Left(_) => {}
                Either::Right(_) => {
                    new.payload_digest = Some(hasher.finish().into());
                    new.payload_length = Some(payload_len as u64);
                    return Ok(new);
                }
            }
        }
    }

    /// Sets the [timestamp](https://willowprotocol.org/specs/data-model/index.html#entry_timestamp) of the entry being built to the current time.
    #[cfg(feature = "std")]
    pub fn now(&mut self) -> Result<&mut Self, HifitimeError> {
        let new = self;

        new.timestamp = Some(Timestamp::now()?);

        Ok(new)
    }

    /// Builds the [`Entry`].
    ///
    /// Calling this method multiple times will cause a panic.
    pub fn build(&mut self) -> Result<Entry<MCL, MCC, MPL, N, S, PD>, EntryBuilderError> {
        Ok(Entry {
            namespace_id: self
                .namespace_id
                .take()
                .ok_or(EntryBuilderError::MissingNamespaceId)?,
            subspace_id: self
                .subspace_id
                .take()
                .ok_or(EntryBuilderError::MissingSubespaceId)?,
            path: self.path.take().ok_or(EntryBuilderError::MissingPath)?,
            timestamp: self
                .timestamp
                .take()
                .ok_or(EntryBuilderError::MissingTimestamp)?,
            payload_length: self
                .payload_length
                .take()
                .ok_or(EntryBuilderError::MissingPayloadLength)?,
            payload_digest: self
                .payload_digest
                .take()
                .ok_or(EntryBuilderError::MissingPayloadDigest)?,
        })
    }
}

/// Everything that can go wrong when trying to build an [`Entry`].
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EntryBuilderError {
    /// The builder was not configured with a namespace id.
    MissingNamespaceId,
    /// The builder was not configured with a subspace id.
    MissingSubespaceId,
    /// The builder was not configured with a path.
    MissingPath,
    /// The builder was not configured with a timestamp.
    MissingTimestamp,
    /// The builder was not configured with a payload length.
    MissingPayloadLength,
    /// The builder was not configured with a payload digest.
    MissingPayloadDigest,
}