use std::str::FromStr;
#[cfg(not(feature = "hex"))]
type InnerDateTime<const HASH_LEN: usize> = String;
#[cfg(feature = "hex")]
type InnerDateTime<const HASH_LEN: usize> = [u8; HASH_LEN];
const HASH_LEN_MD5: usize = 16;
const HASH_LEN_SHA1: usize = 20;
const HASH_LEN_SHA256: usize = 32;
const HASH_LEN_SHA512: usize = 64;
#[derive(Clone, Debug, PartialEq)]
pub struct Digest<const HASH_LEN: usize>(InnerDateTime<HASH_LEN>);
pub type DigestMd5 = Digest<HASH_LEN_MD5>;
pub type DigestSha1 = Digest<HASH_LEN_SHA1>;
pub type DigestSha256 = Digest<HASH_LEN_SHA256>;
pub type DigestSha512 = Digest<HASH_LEN_SHA512>;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum DigestParseError {
Empty,
BadLength,
InvalidEncoding,
}
crate::errors::error_enum!(DigestParseError);
#[cfg(not(feature = "hex"))]
mod no_hex {
use super::*;
impl<const HASH_LEN: usize> std::fmt::Display for Digest<HASH_LEN> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{}", self.0)
}
}
impl<const HASH_LEN: usize> FromStr for Digest<HASH_LEN> {
type Err = DigestParseError;
fn from_str(checksum: &str) -> Result<Self, DigestParseError> {
if checksum.is_empty() {
return Err(DigestParseError::Empty);
}
if checksum.len() != (HASH_LEN * 2) {
return Err(DigestParseError::BadLength);
}
Ok(Self(checksum.to_owned()))
}
}
}
#[cfg(feature = "hex")]
mod hex {
use super::*;
use ::hex;
impl<const HASH_LEN: usize> std::fmt::Display for Digest<HASH_LEN> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{}", hex::encode(self.0))
}
}
impl<const HASH_LEN: usize> FromStr for Digest<HASH_LEN> {
type Err = DigestParseError;
fn from_str(checksum: &str) -> Result<Self, DigestParseError> {
if checksum.is_empty() {
return Err(DigestParseError::Empty);
}
let checksum = hex::decode(checksum).map_err(|_| DigestParseError::InvalidEncoding)?;
Ok(Self(
checksum
.try_into()
.map_err(|_| DigestParseError::BadLength)?,
))
}
}
impl<const HASH_LEN: usize> Digest<HASH_LEN> {
#[cfg_attr(docsrs, doc(cfg(feature = "hex")))]
pub fn digest(&self) -> [u8; HASH_LEN] {
self.0
}
}
}
#[cfg(feature = "serde")]
mod serde {
use super::Digest;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
impl<const HASH_LEN: usize> Serialize for Digest<HASH_LEN> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
String::serialize(&self.to_string(), serializer)
}
}
impl<'de, const HASH_LEN: usize> Deserialize<'de> for Digest<HASH_LEN> {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
s.parse::<Digest<HASH_LEN>>()
.map_err(|e| D::Error::custom(format!("{e:?}")))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! check_parses {
($name:ident, $type:ty: $checksum:expr ) => {
#[test]
fn $name() {
assert!($checksum.parse::<$type>().is_ok());
}
};
}
macro_rules! check_fails {
($name:ident, $type:ty: $checksum:expr ) => {
#[test]
fn $name() {
assert!($checksum.parse::<$type>().is_err());
}
};
}
check_fails!(empty_md5, DigestMd5: "");
check_fails!(empty_sh1, DigestSha1: "");
check_fails!(empty_sha256, DigestSha256: "");
check_fails!(empty_sha512, DigestSha512: "");
check_parses!(good_md5, DigestMd5: "d41d8cd98f00b204e9800998ecf8427e");
check_parses!(good_sha1, DigestSha1: "da39a3ee5e6b4b0d3255bfef95601890afd80709");
check_parses!(good_sha256, DigestSha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
check_parses!(good_sha512, DigestSha512: "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e");
check_fails!(bad_md5, DigestMd5: "d41d8cd98f00b204e9800998ecf8427");
check_fails!(bad_sha1, DigestSha1: "da39a3ee5e6b4b0d3255bfef95601890afd8070");
check_fails!(bad_sha256, DigestSha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85");
check_fails!(bad_sha512, DigestSha512: "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3");
#[cfg(feature = "hex")]
mod hex {
use super::*;
check_fails!(malformed_md5, DigestMd5: "d41d8cd98f00bHACK9800998ecf8427e");
check_fails!(malformed_sha1, DigestSha1: "da39a3ee5e6b4b0d3HACKfef95601890afd80709");
check_fails!(malformed_sha256, DigestSha256: "e3b0c44298fc1c149afbf4HACK6fb92427ae41e4649b934ca495991b7852b855");
check_fails!(malformed_sha512, DigestSha512: "cf83e1357eefb8bHACK02850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e");
}
}