use core::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Bytes32(pub [u8; 32]);
impl Bytes32 {
pub fn from_hex(hex_str: &str) -> Result<Bytes32, InvalidBytes32> {
if hex_str.len() != 64 {
return Err(InvalidBytes32);
}
let mut out = [0u8; 32];
hex::decode_to_slice(hex_str, &mut out).map_err(|_| InvalidBytes32)?;
Ok(Bytes32(out))
}
pub fn to_hex(&self) -> String {
hex::encode(self.0)
}
}
impl From<[u8; 32]> for Bytes32 {
fn from(raw: [u8; 32]) -> Self {
Bytes32(raw)
}
}
impl fmt::Debug for Bytes32 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Bytes32({})", self.to_hex())
}
}
impl fmt::Display for Bytes32 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_hex())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidBytes32;
impl fmt::Display for InvalidBytes32 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("value must be exactly 64 hex digits (32 bytes)")
}
}
impl std::error::Error for InvalidBytes32 {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrips_lowercase_hex() {
let h = "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899";
assert_eq!(Bytes32::from_hex(h).unwrap().to_hex(), h);
}
#[test]
fn normalizes_uppercase_to_lowercase() {
let upper = "AABB".to_string() + &"00".repeat(30);
let lower = "aabb".to_string() + &"00".repeat(30);
assert_eq!(Bytes32::from_hex(&upper).unwrap().to_hex(), lower);
}
#[test]
fn rejects_wrong_length() {
assert_eq!(Bytes32::from_hex("1111"), Err(InvalidBytes32));
assert_eq!(Bytes32::from_hex(&"11".repeat(33)), Err(InvalidBytes32));
}
#[test]
fn rejects_non_hex() {
assert_eq!(Bytes32::from_hex(&"zz".repeat(32)), Err(InvalidBytes32));
}
#[test]
fn from_array_and_display() {
let b = Bytes32::from([0x11u8; 32]);
assert_eq!(b.to_string(), "11".repeat(32));
assert!(format!("{b:?}").contains(&"11".repeat(32)));
}
}