willow-data-model 0.6.0

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

#[cfg(feature = "dev")]
use arbitrary::Arbitrary;

use compact_u64::{cu64_decode_canonic_standalone, cu64_decode_standalone};
use ufotofu::codec_prelude::*;

use crate::{groupings::Coordinatelike, prelude::*};

/// The metadata associated with each Willow [Payload](https://willowprotocol.org/specs/data-model/index.html#Payload) string.
///
/// [Entries](https://willowprotocol.org/specs/data-model/index.html#Entry) are the central concept in Willow. In order to make any bytestring of data accessible to Willow, you need to create an Entry describing its metadata. Specifically, an Entry consists of
///
/// - a [namespace_id](https://willowprotocol.org/specs/data-model/index.html#entry_namespace_id) (roughly, this addresses a universe of Willow data, fully independent from all data (i.e., Entries) of different namespace ids) of type `N`,
/// - a [subspace_id](https://willowprotocol.org/specs/data-model/index.html#entry_subspace_id) (roughly, a fully indendent part of a namespace, typically subspaces correspond to individual users) of type `S`,
/// - a [path](https://willowprotocol.org/specs/data-model/index.html#entry_path) (roughly, a file-system-like way of arranging payloads hierarchically within a subspace) of type [`Path`],
/// - a [timestamp](https://willowprotocol.org/specs/data-model/index.html#entry_timestamp) (newer Entries can overwrite certain older Entries),
/// - a [payload_length](https://willowprotocol.org/specs/data-model/index.html#entry_payload_length) (the length of the payload string), and
/// - a [payload_digest](https://willowprotocol.org/specs/data-model/index.html#entry_payload_digest) (a secure hash of the payload string being inserted into Willow).
///
/// To access these six fields, use the methods of the [`Entrylike`] trait (which [`Entry`] implements). The [`EntrylikeExt`] trait provides additional helper methods, for example, methods to check which Entries can delete which other Entries.
///
/// To create Entries, use the [`Entry::builder`] or [`Entry::prefilled_builder`] functions.
///
/// # Example
///
/// ```
/// use willow_data_model::prelude::*;
///
/// let entry = Entry::builder()
///     .namespace_id("family")
///     .subspace_id("alfie")
///     .path(Path::<4, 4, 4>::new())
///     .timestamp(12345)
///     .payload_digest("some_hash")
///     .payload_length(17)
///     .build().unwrap();
///
/// assert_eq!(*entry.wdm_subspace_id(), "alfie");
///
/// let newer = Entry::prefilled_builder(&entry).timestamp(99999).build().unwrap();
/// assert!(newer.wdm_prunes(&entry));
/// ```
///
/// [Spec definition](https://willowprotocol.org/specs/data-model/index.html#Entry).
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
#[cfg_attr(feature = "dev", derive(Arbitrary))]
pub struct Entry<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> {
    pub(crate) namespace_id: N,
    pub(crate) subspace_id: S,
    pub(crate) path: Path<MCL, MCC, MPL>,
    pub(crate) timestamp: Timestamp,
    pub(crate) payload_length: u64,
    pub(crate) payload_digest: PD,
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD>
    Entry<MCL, MCC, MPL, N, S, PD>
{
    /// Creates a builder for [`Entry`].
    ///
    /// # Examples
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// // Supplying incomplete data errors.
    /// assert!(
    ///     Entry::builder()
    ///     .path(Path::<4, 4, 4>::new())
    ///     .namespace_id("family")
    ///     .subspace_id("alfie")
    ///     .payload_digest("some_hash")
    ///     // timestamp and payload_length are missing!
    ///     .build().is_err()
    /// );
    ///
    /// // Supplying all necessary data yields an entry.
    /// let entry = Entry::builder()
    ///     .namespace_id("family")
    ///     .subspace_id("alfie")
    ///     .path(Path::<4, 4, 4>::new())
    ///     .timestamp(12345)
    ///     .payload_digest("some_hash")
    ///     .payload_length(17)
    ///     .build().unwrap();
    ///
    /// assert_eq!(*entry.wdm_subspace_id(), "alfie");
    /// ```
    pub fn builder() -> EntryBuilder<MCL, MCC, MPL, N, S, PD> {
        EntryBuilder::create_empty()
    }

    /// Creates a builder which is prefilled with the data from some other entry.
    ///
    /// Use this function to create modified copies of entries.
    ///
    /// # Examples
    ///
    /// ```
    /// use willow_data_model::prelude::*;
    ///
    /// // Supplying all necessary data yields an entry.
    /// let first_entry = Entry::builder()
    ///     .namespace_id("family")
    ///     .subspace_id("alfie")
    ///     .path(Path::<4, 4, 4>::new())
    ///     .timestamp(12345)
    ///     .payload_digest("some_hash")
    ///     .payload_length(17)
    ///     .build().unwrap();
    ///
    /// assert_eq!(*first_entry.wdm_payload_digest(), "some_hash");
    ///
    /// let second_entry = Entry::prefilled_builder(&first_entry)
    ///     .timestamp(67890)
    ///     .payload_digest("another_hash")
    ///     .payload_length(4)
    ///     .build().unwrap();
    ///
    /// assert_eq!(*second_entry.wdm_payload_digest(), "another_hash");
    /// ```
    pub fn prefilled_builder<E>(source: &E) -> EntryBuilder<MCL, MCC, MPL, N, S, PD>
    where
        E: Entrylike<MCL, MCC, MPL, N, S, PD> + ?Sized,
        N: Clone,
        S: Clone,
        PD: Clone,
    {
        let mut builder = Self::builder();

        builder
            .namespace_id(source.wdm_namespace_id().clone())
            .subspace_id(source.wdm_subspace_id().clone())
            .path(source.wdm_path().clone())
            .timestamp(source.wdm_timestamp())
            .payload_digest(source.wdm_payload_digest().clone())
            .payload_length(source.wdm_payload_length());

        builder
    }

    /// Creates an [`Entry`] with [equal data](EntrylikeExt::wdm_entry_eq) to that of the given [`Entrylike`].
    ///
    /// ```
    /// # #[cfg(feature = "dev")] {
    /// use willow_data_model::prelude::*;
    /// use willow_data_model::test_parameters::*;
    ///
    /// let entry1 = Entry::builder()
    ///     .namespace_id(Family)
    ///     .subspace_id(Alfie)
    ///     .path(Path::<4, 4, 4>::new())
    ///     .timestamp(12345)
    ///     .payload_digest(Spades)
    ///     .payload_length(2)
    ///     .build().unwrap();
    ///
    /// let entry2 = Entry::from_entrylike(&entry1);
    /// assert_eq!(entry1, entry2);
    /// # }
    /// ```
    pub fn from_entrylike<E>(entrylike_to_clone: &E) -> Self
    where
        E: Entrylike<MCL, MCC, MPL, N, S, PD> + ?Sized,
        N: Clone,
        S: Clone,
        PD: Clone,
    {
        Self::prefilled_builder(entrylike_to_clone).build().unwrap()
    }

    /// Turns `self` into an [`AuthorisedEntry`] by creating an authorisation token for it.
    ///
    /// ```
    /// # #[cfg(feature = "dev")] {
    /// use willow_data_model::prelude::*;
    /// use willow_data_model::test_parameters::*;
    ///
    /// let entry = Entry::builder()
    ///     .namespace_id(Family)
    ///     .subspace_id(Alfie)
    ///     .path(Path::<4, 4, 4>::new())
    ///     .timestamp(12345)
    ///     .payload_digest(Spades)
    ///     .payload_length(2)
    ///     .build().unwrap();
    ///
    /// let authed = entry.into_authorised_entry::<TestSubspaceSignature>(&AlfieSecret).unwrap();
    /// assert_eq!(authed.authorisation_token(), &AlfieSignature);
    /// # }
    /// ```
    pub fn into_authorised_entry<AT>(
        self,
        ingredients: &AT::Ingredients,
    ) -> Result<AuthorisedEntry<MCL, MCC, MPL, N, S, PD, AT>, AT::CreationError>
    where
        AT: AuthorisationToken<MCL, MCC, MPL, N, S, PD> + Debug,
        N: Clone + Debug,
        S: Clone + Debug,
        PD: Clone + Debug,
    {
        let authorisation_token = AuthorisationToken::new_for_entry(&self, ingredients)?;

        Ok(PossiblyAuthorisedEntry {
                entry: self,
                authorisation_token,
            }
            .into_authorised_entry().expect("AuthorisationToken::new_for_entry must produce an authorisation token that authorises the entry"))
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Keylike<MCL, MCC, MPL, S>
    for Entry<MCL, MCC, MPL, N, S, PD>
{
    fn wdm_subspace_id(&self) -> &S {
        &self.subspace_id
    }

    fn wdm_path(&self) -> &Path<MCL, MCC, MPL> {
        &self.path
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD>
    Coordinatelike<MCL, MCC, MPL, S> for Entry<MCL, MCC, MPL, N, S, PD>
{
    fn wdm_timestamp(&self) -> Timestamp {
        self.timestamp
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Namespaced<N>
    for Entry<MCL, MCC, MPL, N, S, PD>
{
    fn wdm_namespace_id(&self) -> &N {
        &self.namespace_id
    }
}

impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD>
    Entrylike<MCL, MCC, MPL, N, S, PD> for Entry<MCL, MCC, MPL, N, S, PD>
{
    fn wdm_payload_length(&self) -> u64 {
        self.payload_length
    }

    fn wdm_payload_digest(&self) -> &PD {
        &self.payload_digest
    }
}

/// Implements [encode_entry](https://willowprotocol.org/specs/encodings/index.html#encode_entry).
impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Encodable
    for Entry<MCL, MCC, MPL, N, S, PD>
where
    N: Encodable,
    S: Encodable,
    PD: Encodable,
{
    async fn encode<C>(&self, consumer: &mut C) -> Result<(), C::Error>
    where
        C: BulkConsumer<Item = u8> + ?Sized,
    {
        self.wdm_encode_entry(consumer).await
    }
}

/// Implements [encode_entry](https://willowprotocol.org/specs/encodings/index.html#encode_entry).
impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> EncodableKnownLength
    for Entry<MCL, MCC, MPL, N, S, PD>
where
    N: EncodableKnownLength,
    S: EncodableKnownLength,
    PD: EncodableKnownLength,
{
    fn len_of_encoding(&self) -> usize {
        self.wdm_length_of_entry_encoding()
    }
}

/// Implements [EncodeEntry](https://willowprotocol.org/specs/encodings/index.html#EncodeEntry).
impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> Decodable
    for Entry<MCL, MCC, MPL, N, S, PD>
where
    N: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>>,
    S: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>>,
    PD: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>>,
{
    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,
    {
        Ok(Self {
            namespace_id: producer
                .produce_decoded_canonic()
                .await
                .map_err(|err| err.map_other(Into::into))?,
            subspace_id: producer
                .produce_decoded_canonic()
                .await
                .map_err(|err| err.map_other(Into::into))?,
            path: producer.produce_decoded().await?,
            timestamp: cu64_decode_standalone(producer)
                .await
                .map_err(|err| err.map_other(|_| unreachable!()))?
                .into(),
            payload_length: cu64_decode_standalone(producer)
                .await
                .map_err(|err| err.map_other(|_| unreachable!()))?,
            payload_digest: producer
                .produce_decoded_canonic()
                .await
                .map_err(|err| err.map_other(Into::into))?,
        })
    }
}

/// Implements [encode_entry](https://willowprotocol.org/specs/encodings/index.html#encode_entry).
impl<const MCL: usize, const MCC: usize, const MPL: usize, N, S, PD> DecodableCanonic
    for Entry<MCL, MCC, MPL, N, S, PD>
where
    N: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>>,
    S: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>>,
    PD: DecodableCanonic<ErrorReason: Into<Blame>, ErrorCanonic: Into<Blame>>,
{
    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,
    {
        Ok(Self {
            namespace_id: producer
                .produce_decoded_canonic()
                .await
                .map_err(|err| err.map_other(Into::into))?,
            subspace_id: producer
                .produce_decoded_canonic()
                .await
                .map_err(|err| err.map_other(Into::into))?,
            path: producer.produce_decoded_canonic().await?,
            timestamp: cu64_decode_canonic_standalone(producer)
                .await
                .map_err(|err| err.map_other(|_| unreachable!()))?
                .into(),
            payload_length: cu64_decode_canonic_standalone(producer)
                .await
                .map_err(|err| err.map_other(|_| unreachable!()))?,
            payload_digest: producer
                .produce_decoded_canonic()
                .await
                .map_err(|err| err.map_other(Into::into))?,
        })
    }
}