orthodoxy 0.2.3

A collection of my utility libraries
Documentation
use base64ct::{Base64, Encoding};
use sha3::Digest;
use std::fmt::Display;
use tracing::{error, warn};

use facet::Facet;

#[derive(thiserror::Error, Debug)]
pub enum Errors {
    #[error(transparent)]
    StdIo(#[from] std::io::Error),
    #[error("Attempted to load")]
    TooFewBytes { needed: usize, provided: usize },

    #[error("{0}")]
    Custom(String),
}

impl From<String> for Errors {
    fn from(value: String) -> Self {
        Self::Custom(value)
    }
}

impl From<&str> for Errors {
    fn from(value: &str) -> Self {
        Self::from(value.to_string())
    }
}

pub type Res<T> = Result<T, Errors>;
const BLAKE3_LEN: usize = 32;
const SHA3_256_LEN: usize = 32;
const SHA3_512_LEN: usize = 64;

#[derive(Facet, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Copy, Debug)]
#[repr(C)]
pub enum Hish {
    Blake3([u8; BLAKE3_LEN]),
    Sha256([u8; SHA3_256_LEN]),
    Sha512([u8; SHA3_512_LEN]),
}

impl Hish {
    fn sha256_empty_arr() -> [u8; SHA3_256_LEN] {
        [0u8; SHA3_256_LEN]
    }

    fn sha512_empty_arr() -> [u8; SHA3_512_LEN] {
        [0u8; SHA3_512_LEN]
    }

    fn blake3_empty_arr() -> [u8; BLAKE3_LEN] {
        [0u8; BLAKE3_LEN]
    }

    fn sha256_empty() -> Self {
        Self::Sha256([0u8; SHA3_256_LEN])
    }

    fn sha512_empty() -> Self {
        Self::Sha512([0u8; SHA3_512_LEN])
    }

    fn blake3_empty() -> Self {
        Self::Blake3([0u8; BLAKE3_LEN])
    }

    const fn len(&self) -> usize {
        match self {
            Hish::Blake3(_) => BLAKE3_LEN,
            Hish::Sha256(_) => SHA3_256_LEN,
            Hish::Sha512(_) => SHA3_512_LEN,
        }
    }

    const fn prefix(&self) -> &'static str {
        match self {
            Hish::Blake3(_) => "blake3",
            Hish::Sha256(_) => "sha256",
            Hish::Sha512(_) => "sha512",
        }
    }
    fn bytes_ref(&self) -> &[u8] {
        match self {
            Hish::Blake3(b) => b,
            Hish::Sha256(b) => b,
            Hish::Sha512(b) => b,
        }
    }
}

impl Hish {
    fn length_err(&self, passed_len: usize) -> String {
        let hash_len = self.len();
        let prefix = self.prefix();
        if hash_len < passed_len {
            format!("Passed too many bytes: Expected <{hash_len}>, got <{passed_len}>")
        } else if hash_len > passed_len {
            format!("Passed too few bytes: Expected <{hash_len}>, got <{passed_len}>")
        } else {
            error!(
                "Produced an error that the wrong amounts of bytes were passed, but it was the correct amount!"
            );
            format!("Passed the correct amount of bytes: Expected <{hash_len}>, got <{passed_len}>")
        }
    }

    fn replace_unchecked(&mut self, bytes: &[u8]) {
        match self {
            Hish::Blake3(inner) => inner.copy_from_slice(&bytes[0..BLAKE3_LEN]),
            Hish::Sha256(inner) => inner.copy_from_slice(&bytes[0..SHA3_256_LEN]),
            Hish::Sha512(inner) => inner.copy_from_slice(&bytes[0..SHA3_512_LEN]),
        }
    }
}

impl Display for Hish {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let p = self.prefix();
        let b = Base64::encode_string(self.bytes_ref());
        write!(f, "{p}/{b}")
    }
}

impl From<sha3::Sha3_256> for Hish {
    fn from(value: sha3::Sha3_256) -> Self {
        Self::Sha256(value.finalize().into())
    }
}

impl From<sha3::Sha3_512> for Hish {
    fn from(value: sha3::Sha3_512) -> Self {
        Self::Sha512(value.finalize().into())
    }
}

impl From<blake3::Hash> for Hish {
    fn from(value: blake3::Hash) -> Self {
        Self::Blake3(value.into())
    }
}

impl TryFrom<(&str, Vec<u8>)> for Hish {
    type Error = Errors;

    fn try_from(value: (&str, Vec<u8>)) -> Result<Self, Self::Error> {
        let len = value.1.len();

        let (mut h, hish_len) = match value.0 {
            "blake3" | "b3" => (Self::blake3_empty(), BLAKE3_LEN),
            "sha3_256" | "sha256" => (Self::sha256_empty(), SHA3_256_LEN),
            "sha3_512" | "sha512" => (Self::sha512_empty(), SHA3_256_LEN),

            _ => return Err(format!("'{}' is not a name of a hash algorithm", value.0).into()),
        };

        let m = if len == hish_len {
            h.replace_unchecked(value.1.as_ref());
            return Ok(h);
        } else {
            h.length_err(len)
        };
        warn!(message = m);
        Err(m.into())
    }
}