use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::errors::{ManifestError, ManifestResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Md5Hash([u8; 16]);
impl Md5Hash {
pub fn from_hex(hex: &str) -> ManifestResult<Self> {
if hex.len() != 32 {
return Err(ManifestError::InvalidHash {
hash: hex.to_string(),
});
}
if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(ManifestError::InvalidHash {
hash: hex.to_string(),
});
}
let mut bytes = [0u8; 16];
for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
let hex_pair = std::str::from_utf8(chunk).unwrap(); bytes[i] = u8::from_str_radix(hex_pair, 16).unwrap(); }
Ok(Md5Hash(bytes))
}
pub fn to_hex(&self) -> String {
use std::fmt::Write;
self.0.iter().fold(String::with_capacity(32), |mut acc, b| {
write!(&mut acc, "{:02x}", b).unwrap();
acc
})
}
pub fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
pub fn from_bytes(bytes: [u8; 16]) -> Self {
Md5Hash(bytes)
}
}
impl fmt::Display for Md5Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_hex())
}
}
impl FromStr for Md5Hash {
type Err = ManifestError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_hex(s)
}
}
impl Serialize for Md5Hash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_hex())
}
}
impl<'de> Deserialize<'de> for Md5Hash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let hex_string = String::deserialize(deserializer)?;
Self::from_hex(&hex_string).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_hex_strings() {
let test_cases = [
"50c9d1c465f3cbff652be1509c2e2a4e",
"9734faa872681f96b144f60d29d52011",
"abcdef1234567890abcdef1234567890",
"00000000000000000000000000000000",
"ffffffffffffffffffffffffffffffff",
];
for hex in &test_cases {
let hash = Md5Hash::from_hex(hex).unwrap();
assert_eq!(hash.to_hex(), hex.to_lowercase());
}
}
#[test]
fn test_case_insensitive() {
let lower = "50c9d1c465f3cbff652be1509c2e2a4e";
let upper = "50C9D1C465F3CBFF652BE1509C2E2A4E";
let mixed = "50c9D1C465f3CBFF652be1509c2E2A4e";
let hash_lower = Md5Hash::from_hex(lower).unwrap();
let hash_upper = Md5Hash::from_hex(upper).unwrap();
let hash_mixed = Md5Hash::from_hex(mixed).unwrap();
assert_eq!(hash_lower, hash_upper);
assert_eq!(hash_upper, hash_mixed);
assert_eq!(hash_lower.to_hex(), lower); }
#[test]
fn test_invalid_hex_strings() {
let invalid_cases = [
"", "50c9d1c465f3cbff652be1509c2e2a4", "50c9d1c465f3cbff652be1509c2e2a4e5", "50c9d1c465f3cbff652be1509c2e2a4g", "50c9d1c465f3cbff652be1509c2e2a4X", "50c9d1c4 65f3cbff652be1509c2e2a4e", "50c9d1c4-65f3cbff652be1509c2e2a4e", ];
for hex in &invalid_cases {
assert!(Md5Hash::from_hex(hex).is_err(), "Should reject: {}", hex);
}
}
#[test]
fn test_from_str_trait() {
let hex = "50c9d1c465f3cbff652be1509c2e2a4e";
let hash: Md5Hash = hex.parse().unwrap();
assert_eq!(hash.to_hex(), hex);
}
#[test]
fn test_display_trait() {
let hash = Md5Hash::from_hex("50c9d1c465f3cbff652be1509c2e2a4e").unwrap();
assert_eq!(format!("{}", hash), "50c9d1c465f3cbff652be1509c2e2a4e");
assert_eq!(
format!("{:?}", hash),
"Md5Hash([80, 201, 209, 196, 101, 243, 203, 255, 101, 43, 225, 80, 156, 46, 42, 78])"
);
}
#[test]
fn test_equality_and_hashing() {
let hash1 = Md5Hash::from_hex("50c9d1c465f3cbff652be1509c2e2a4e").unwrap();
let hash2 = Md5Hash::from_hex("50c9d1c465f3cbff652be1509c2e2a4e").unwrap();
let hash3 = Md5Hash::from_hex("9734faa872681f96b144f60d29d52011").unwrap();
assert_eq!(hash1, hash2);
assert_ne!(hash1, hash3);
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
hash1.hash(&mut hasher1);
hash2.hash(&mut hasher2);
assert_eq!(hasher1.finish(), hasher2.finish());
}
#[test]
fn test_serialization() {
let hash = Md5Hash::from_hex("50c9d1c465f3cbff652be1509c2e2a4e").unwrap();
let json = serde_json::to_string(&hash).unwrap();
assert_eq!(json, "\"50c9d1c465f3cbff652be1509c2e2a4e\"");
let deserialized: Md5Hash = serde_json::from_str(&json).unwrap();
assert_eq!(hash, deserialized);
}
#[test]
fn test_as_bytes() {
let hash = Md5Hash::from_hex("50c9d1c465f3cbff652be1509c2e2a4e").unwrap();
let bytes = hash.as_bytes();
assert_eq!(bytes.len(), 16);
assert_eq!(bytes[0], 0x50);
assert_eq!(bytes[1], 0xc9);
assert_eq!(bytes[15], 0x4e);
}
#[test]
fn test_from_bytes() {
let bytes = [
0x50, 0xc9, 0xd1, 0xc4, 0x65, 0xf3, 0xcb, 0xff, 0x65, 0x2b, 0xe1, 0x50, 0x9c, 0x2e,
0x2a, 0x4e,
];
let hash = Md5Hash::from_bytes(bytes);
assert_eq!(hash.to_hex(), "50c9d1c465f3cbff652be1509c2e2a4e");
assert_eq!(hash.as_bytes(), &bytes);
}
#[test]
fn test_memory_efficiency() {
use std::mem;
assert_eq!(mem::size_of::<Md5Hash>(), 16);
let string_hash = "50c9d1c465f3cbff652be1509c2e2a4e".to_string();
let md5_hash = Md5Hash::from_hex("50c9d1c465f3cbff652be1509c2e2a4e").unwrap();
assert!(mem::size_of_val(&string_hash) > mem::size_of_val(&md5_hash));
assert_eq!(mem::size_of_val(&md5_hash), 16);
}
}