use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Hash([u8; 32]);
impl Hash {
#[inline]
pub const fn new(bytes: [u8; 32]) -> Self {
Self(bytes)
}
#[inline]
pub const fn zero() -> Self {
Self([0u8; 32])
}
#[inline]
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
#[inline]
pub fn as_bytes_mut(&mut self) -> &mut [u8; 32] {
&mut self.0
}
#[inline]
pub fn as_slice(&self) -> &[u8] {
&self.0
}
#[inline]
pub fn into_inner(self) -> [u8; 32] {
self.0
}
#[inline]
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
pub fn to_hex(&self) -> String {
hex::encode(self.0)
}
pub fn from_hex(s: &str) -> Result<Self, HashParseError> {
let s = s
.strip_prefix("0x")
.or_else(|| s.strip_prefix("0X"))
.unwrap_or(s);
let bytes = hex::decode(s).map_err(|e| HashParseError::InvalidHex(e.to_string()))?;
if bytes.len() != 32 {
return Err(HashParseError::WrongLength {
expected: 32,
got: bytes.len(),
});
}
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
Ok(Self(arr))
}
}
impl AsRef<[u8]> for Hash {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl AsRef<[u8; 32]> for Hash {
#[inline]
fn as_ref(&self) -> &[u8; 32] {
&self.0
}
}
impl From<[u8; 32]> for Hash {
#[inline]
fn from(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
impl From<&[u8; 32]> for Hash {
#[inline]
fn from(bytes: &[u8; 32]) -> Self {
Self(*bytes)
}
}
impl TryFrom<&[u8]> for Hash {
type Error = HashParseError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() != 32 {
return Err(HashParseError::WrongLength {
expected: 32,
got: bytes.len(),
});
}
let mut arr = [0u8; 32];
arr.copy_from_slice(bytes);
Ok(Self(arr))
}
}
impl FromStr for Hash {
type Err = HashParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_hex(s)
}
}
impl fmt::Display for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "0x{}", self.to_hex())
} else {
write!(f, "0x{}…", &self.to_hex()[..8])
}
}
}
impl fmt::Debug for Hash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Hash(0x{})", self.to_hex())
}
}
impl Default for Hash {
#[inline]
fn default() -> Self {
Self::zero()
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[allow(missing_docs)]
pub enum HashParseError {
#[error("invalid hex: {0}")]
InvalidHex(String),
#[error("expected 32 bytes, got {got}")]
WrongLength { expected: usize, got: usize },
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_new() {
let h = Hash::new([1u8; 32]);
assert_eq!(h.as_bytes(), &[1u8; 32]);
}
#[test]
fn test_hash_zero() {
let h = Hash::zero();
assert_eq!(h.as_bytes(), &[0u8; 32]);
}
#[test]
fn test_hash_hex_roundtrip() {
let h = Hash::new([0xAB; 32]);
let hex = h.to_hex();
let parsed = Hash::from_hex(&hex).unwrap();
assert_eq!(h, parsed);
}
#[test]
fn test_hash_from_hex_with_prefix() {
let h = Hash::from_hex("0xabcdef").unwrap_err();
assert!(matches!(h, HashParseError::WrongLength { .. }));
}
#[test]
fn test_hash_display() {
let h = Hash::new([0xAB; 32]);
let display = format!("{}", h);
assert!(display.starts_with("0x"));
assert!(display.contains("…"));
}
#[test]
fn test_hash_display_altern() {
let h = Hash::new([0xAB; 32]);
let display = format!("{:#}", h);
assert_eq!(display.len(), 66); }
#[test]
fn test_hash_debug() {
let h = Hash::new([0xAB; 32]);
let debug = format!("{:?}", h);
assert!(debug.starts_with("Hash(0x"));
}
#[test]
fn test_hash_from_str() {
let h: Hash = "abababababababababababababababababababababababababababababababab"
.parse()
.unwrap();
assert_eq!(
h.to_hex(),
"abababababababababababababababababababababababababababababababab"
);
}
}