use std::fmt; use crate::{addressing::Address, error::UpEndError}; use multihash::Hasher; use serde::{ de::{self, Visitor}, ser, Deserialize, Deserializer, Serialize, Serializer, }; #[derive(Debug, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "diesel", derive(diesel::FromSqlRow))] pub struct Digest(pub Vec); impl AsRef<[u8]> for Digest { fn as_ref(&self) -> &[u8] { self.0.as_ref() } } impl Serialize for Digest { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, { serializer.serialize_str( b58_encode( Address::Hash(self.clone()) .encode() .map_err(ser::Error::custom)?, ) .as_str(), ) } } struct DigestVisitor; impl<'de> Visitor<'de> for DigestVisitor { type Value = Digest; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a valid UpEnd address (hash/UUID) as a multi-hashed string") } fn visit_str(self, str: &str) -> Result where E: de::Error, { b58_decode(str) .map(|v| Digest(v)) .map_err(|e| de::Error::custom(format!("Error deserializing digest: {}", e))) } } impl<'de> Deserialize<'de> for Digest { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_str(DigestVisitor) } } #[cfg(feature = "diesel")] impl diesel::types::FromSql for Digest { fn from_sql( bytes: Option<&::RawValue>, ) -> diesel::deserialize::Result { Ok(Digest(Vec::from(diesel::not_none!(bytes).read_blob()))) } } pub fn sha256hash>(input: T) -> Digest { let mut hasher = multihash::Sha2_256::default(); hasher.update(input.as_ref()); Digest(Vec::from(hasher.finalize())) } pub fn b58_encode>(vec: T) -> String { multibase::encode(multibase::Base::Base58Btc, vec.as_ref()) } pub fn b58_decode>(input: T) -> Result, UpEndError> { let input = input.as_ref(); let (_base, data) = multibase::decode(input).map_err(|err| UpEndError::HashDecodeError(err.to_string()))?; Ok(data) } #[derive(Debug, Clone)] pub struct AsDigestError(pub String); impl std::fmt::Display for AsDigestError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl std::error::Error for AsDigestError {} impl From for AsDigestError { fn from(err: std::io::Error) -> Self { AsDigestError(err.to_string()) } } pub trait AsDigest { fn as_digest(&self) -> Result; } #[cfg(test)] mod tests { use crate::hash::{b58_decode, b58_encode}; #[test] fn test_encode_decode() { let content = "Hello, World!".as_bytes(); let encoded = b58_encode(content); let decoded = b58_decode(encoded); assert!(decoded.is_ok()); assert_eq!(content, decoded.unwrap()); } }