compact_argon2 0.2.0

Thin wrapper around the argon2 crate to improve ergonomics and provide a smaller serialization format
Documentation
pub use crate::{Hash, OUTPUT_SIZE};

impl serde::Serialize for Hash {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_bytes(&self.to_bytes())
    }
}

impl<'de> serde::Deserialize<'de> for Hash {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        use serde::de::{Unexpected, Visitor};

        struct BV;
        impl<'v> Visitor<'v> for BV {
            type Value = [u8; OUTPUT_SIZE];

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(formatter, "{OUTPUT_SIZE} bytes")
            }

            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                v.try_into()
                    .map_err(|_| serde::de::Error::invalid_length(v.len(), &self))
            }
        }

        let bytes = deserializer.deserialize_bytes(BV)?;
        Hash::try_from(&bytes).map_err(|_| {
            serde::de::Error::invalid_value(
                Unexpected::Unsigned(u32::from_be_bytes(bytes[12..16].try_into().unwrap()) as u64),
                &"a valid argon2id version",
            )
        })
    }
}

#[cfg(feature = "base64")]
/// Overrides the [`serde::Serialize`] and [`serde::Deserialize`] `impl`s
/// to encode the hash data as a hex string instead of a byte array.
///
/// ## Usage
///
/// ```rs
/// #[derive(Serialize, Deserialize)]
/// struct User {
///     pub name: String,
///     #[serde(with = "compact_argon2::hex")]
///     pub password: Hash,
/// }
/// ```
pub mod base64 {
    use serde::de::{Unexpected, Visitor};
    use serde::{Deserializer, Serializer};

    use super::*;
    use crate::base64::BASE64;
    use crate::{BASE64_OUTPUT_SIZE, ParseError};

    pub fn serialize<S>(value: Hash, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut buf = [0u8; BASE64_OUTPUT_SIZE];
        serializer.serialize_str(value.write_base64(&mut buf))
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Hash, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct B64Visitor;

        impl<'v> Visitor<'v> for B64Visitor {
            type Value = [u8; OUTPUT_SIZE];

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(formatter, "a base64 string of length {BASE64_OUTPUT_SIZE}")
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                if v.len() != BASE64_OUTPUT_SIZE {
                    return Err(serde::de::Error::invalid_length(v.len(), &self));
                }

                let mut buf = [0u8; OUTPUT_SIZE];
                ::base64::Engine::decode_slice(&BASE64, v, &mut buf)
                    .map_err(|_| serde::de::Error::invalid_value(Unexpected::Str(v), &self))?;
                Ok(buf)
            }
        }

        let bytes = deserializer.deserialize_str(B64Visitor)?;
        Hash::try_from(&bytes).map_err(|err| match err {
            ParseError::InvalidVersion(v) => serde::de::Error::invalid_value(
                Unexpected::Unsigned(v as u64),
                &"a valid argon2 version",
            ),
            ParseError::MemoryTooLow { .. } | ParseError::InvalidParameters => {
                serde::de::Error::invalid_value(
                    Unexpected::Other("parameters"),
                    &"valid argon2 parameters",
                )
            },
            ParseError::SliceLength => unreachable!(),
        })
    }
}