ridl 0.6.1

Hard-to-misuse, modern crypto primitives and conventions for litl.
Documentation
use std::{borrow::Cow, marker::PhantomData};

use litl::{impl_debug_as_litl, impl_single_tagged_data_serde, SingleTaggedData, TaggedDataError};
use serde::{de::DeserializeOwned, Serialize};
use serde_derive::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum StrongHash {
    HashV1(blake3::Hash),
}

impl SingleTaggedData for StrongHash {
    const TAG: &'static str = "hashV1";

    fn as_bytes(&self) -> Cow<[u8]> {
        match self {
            StrongHash::HashV1(hash) => Cow::from(hash.as_bytes().as_ref()),
        }
    }

    fn from_bytes(data: &[u8]) -> Result<Self, TaggedDataError>
    where
        Self: Sized,
    {
        let hash_bytes: [u8; 32] = data
            .try_into()
            .map_err(|err| TaggedDataError::data_error(Into::<StrongHashError>::into(err)))?;
        Ok(StrongHash::HashV1(blake3::Hash::from(hash_bytes)))
    }
}

#[derive(Debug, Error)]
pub enum StrongHashError {
    #[error("Invalid StrongHash length")]
    InvalidLength(#[from] std::array::TryFromSliceError),
}

impl_single_tagged_data_serde!(StrongHash);
impl_debug_as_litl!(StrongHash);

impl Ord for StrongHash {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.as_bytes().cmp(other.as_bytes())
    }
}

impl PartialOrd for StrongHash {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl StrongHash {
    pub fn as_bytes(&self) -> &[u8] {
        match self {
            StrongHash::HashV1(hash) => hash.as_bytes(),
        }
    }
}

#[derive(Clone, Default, Debug)]
pub struct StrongHasher(blake3::Hasher);

impl StrongHasher {
    pub fn update(&mut self, data: &[u8]) -> &mut Self {
        self.0.update(data);
        self
    }

    pub fn finalize(&mut self) -> StrongHash {
        StrongHash::HashV1(self.0.finalize())
    }
}

#[derive(Serialize, Deserialize)]
#[serde(transparent)]
pub struct HashOf<T> {
    hash: StrongHash,
    #[serde(default)]
    _marker: PhantomData<T>,
}

impl<T: Serialize> HashOf<T> {
    pub fn hash(value: &T) -> Self {
        HashOf {
            hash: StrongHasher::default()
                .update(&litl::to_vec_canonical(value).unwrap())
                .finalize(),
            _marker: PhantomData,
        }
    }

    pub fn as_value_hash(self) -> HashOf<litl::Val> {
        HashOf {
            hash: self.hash,
            _marker: PhantomData,
        }
    }
}

impl HashOf<litl::Val> {
    pub fn as_type_hash<D: DeserializeOwned>(self) -> HashOf<D> {
        HashOf {
            hash: self.hash,
            _marker: PhantomData,
        }
    }
}

impl<T> PartialEq for HashOf<T> {
    fn eq(&self, other: &Self) -> bool {
        self.hash.eq(&other.hash)
    }
}

impl<T> Eq for HashOf<T> {}

impl<T> Ord for HashOf<T> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.hash.cmp(&other.hash)
    }
}

impl<T> PartialOrd for HashOf<T> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl<T> std::hash::Hash for HashOf<T> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.hash.hash(state)
    }
}

impl<T> std::fmt::Debug for HashOf<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.hash.fmt(f)
    }
}

impl<T> std::fmt::Display for HashOf<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.hash.fmt(f)
    }
}

impl<T> std::str::FromStr for HashOf<T> {
    type Err = <StrongHash as std::str::FromStr>::Err;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(HashOf {
            hash: s.parse()?,
            _marker: PhantomData,
        })
    }
}

impl<T> Clone for HashOf<T> {
    fn clone(&self) -> Self {
        Self {
            hash: self.hash,
            _marker: PhantomData,
        }
    }
}

impl<T> Copy for HashOf<T> {}

// TODO: add test for canoncical serialization