use std::{fmt, str::FromStr};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use sha2::{Digest, Sha256};
use crate::nori::error::invalid;
use crate::nori::{DictionaryError, DictionaryResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ResourceHash([u8; 32]);
impl ResourceHash {
pub(crate) const fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub(crate) const fn into_bytes(self) -> [u8; 32] {
self.0
}
pub fn of(bytes: &[u8]) -> Self {
Self(Sha256::digest(bytes).into())
}
}
impl fmt::Display for ResourceHash {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.0 {
write!(formatter, "{byte:02x}")?;
}
Ok(())
}
}
impl FromStr for ResourceHash {
type Err = DictionaryError;
fn from_str(text: &str) -> DictionaryResult<Self> {
if text.len() != 64 || !text.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return Err(invalid("resource hash", "expected 64 hexadecimal digits"));
}
let mut bytes = [0; 32];
for (index, byte) in bytes.iter_mut().enumerate() {
*byte = u8::from_str_radix(&text[index * 2..index * 2 + 2], 16)
.map_err(|_| invalid("resource hash", "invalid hexadecimal byte"))?;
}
Ok(Self(bytes))
}
}
impl Serialize for ResourceHash {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for ResourceHash {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
String::deserialize(deserializer)?
.parse()
.map_err(serde::de::Error::custom)
}
}