use crate::error::{AddressComponentsDecodeError, UpEndError}; use crate::hash::{b58_decode, b58_encode, AsDigest, AsDigestError, Digest}; use serde::de::Visitor; use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; use std::hash::Hash; use std::str::FromStr; use url::Url; use uuid::Uuid; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; #[derive(Clone, Eq, PartialEq, Hash)] pub enum Address { Hash(Digest), Uuid(Uuid), Attribute(String), Url(Url), } #[cfg_attr(feature = "wasm", wasm_bindgen(getter_with_clone))] #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct AddressComponents { pub t: String, pub c: Option, } // multihash SHA2-256 const SHA2_256: u64 = 0x12; // multihash identity const IDENTITY: u64 = 0x00; pub type LargeMultihash = multihash::MultihashGeneric<256>; impl Address { pub fn encode(&self) -> Result, UpEndError> { let hash = match self { Self::Hash(hash) => { LargeMultihash::wrap(SHA2_256, &hash.0).map_err(UpEndError::from_any)? } Self::Uuid(uuid) => { LargeMultihash::wrap(IDENTITY, &[vec![b'U'], uuid.as_bytes().to_vec()].concat()) .map_err(UpEndError::from_any)? } Self::Attribute(attribute) => { LargeMultihash::wrap(IDENTITY, &[&[b'A'], attribute.as_bytes()].concat()) .map_err(UpEndError::from_any)? } Self::Url(url) => { LargeMultihash::wrap(IDENTITY, &[&[b'X'], url.to_string().as_bytes()].concat()) .map_err(UpEndError::from_any)? } }; Ok(hash.to_bytes()) } pub fn decode(buffer: &[u8]) -> Result { let multihash = LargeMultihash::from_bytes(buffer).map_err(|err| { UpEndError::AddressParseError(format!("Error decoding address: {}", err)) })?; match multihash.code() { SHA2_256 => Ok(Self::Hash(Digest(multihash.digest().to_vec()))), IDENTITY => { let digest = multihash.digest().to_owned(); let digest_content: Vec = digest.clone().into_iter().skip(1).collect(); match digest[0] { b'U' => Ok(Self::Uuid( uuid::Uuid::from_slice(digest_content.as_slice()) .map_err(UpEndError::from_any)?, )), b'A' => Ok(Self::Attribute( String::from_utf8(digest_content).map_err(UpEndError::from_any)?, )), b'X' => Ok(Self::Url( Url::parse( &String::from_utf8(digest_content).map_err(UpEndError::from_any)?, ) .map_err(|e| UpEndError::AddressParseError(e.to_string()))?, )), _ => Err(UpEndError::AddressParseError( "Error decoding address: Unknown identity marker.".to_string(), )), } } _ => Err(UpEndError::AddressParseError( "Error decoding address: Unknown hash function type.".to_string(), )), } } pub fn as_components<'a>(&'a self) -> AddressComponents { // TODO: make this automatically derive from `Address` definition let (entity_type, entity_content) = match self { Address::Hash(digest) => ("Hash", Some(b58_encode(digest))), Address::Uuid(uuid) => ("Uuid", Some(uuid.to_string())), Address::Attribute(attribute) => ("Attribute", Some(attribute.clone())), Address::Url(url) => ("Url", Some(url.to_string())), }; AddressComponents { t: entity_type.to_string(), c: entity_content, } } pub fn from_components(components: AddressComponents) -> Result { // TODO: make this automatically derive from `Address` definition let address = match components { AddressComponents { t, c } if t == "Attribute" => Address::Attribute( c.map(|x| x.to_string()) .ok_or(UpEndError::AddressComponentsDecodeError( AddressComponentsDecodeError::MissingValue, ))?, ), AddressComponents { t, c } if t == "Url" => Address::Url(if let Some(string) = c { Url::parse(&string).map_err(|e| { UpEndError::AddressComponentsDecodeError( AddressComponentsDecodeError::UrlDecodeError(e.to_string()), ) })? } else { Err(UpEndError::AddressComponentsDecodeError( AddressComponentsDecodeError::MissingValue, ))? }), AddressComponents { t, c } if t == "Uuid" => match c { Some(c) => c.parse()?, None => Address::Uuid(Uuid::new_v4()), }, AddressComponents { t, .. } => Err(UpEndError::AddressComponentsDecodeError( AddressComponentsDecodeError::UnknownType(t), ))?, }; Ok(address) } } impl Serialize for Address { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, { serializer.serialize_str(b58_encode(self.encode().map_err(ser::Error::custom)?).as_str()) } } struct AddressVisitor; impl<'de> Visitor<'de> for AddressVisitor { type Value = Address; 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, { let bytes = b58_decode(str) .map_err(|e| de::Error::custom(format!("Error deserializing address: {}", e)))?; Address::decode(bytes.as_ref()) .map_err(|e| de::Error::custom(format!("Error deserializing address: {}", e))) } } impl<'de> Deserialize<'de> for Address { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_str(AddressVisitor) } } impl FromStr for Address { type Err = UpEndError; fn from_str(s: &str) -> Result { Address::decode( b58_decode(s) .map_err(|e| { UpEndError::HashDecodeError(format!("Error deserializing address: {}", e)) })? .as_ref(), ) } } impl std::fmt::Display for Address { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", b58_encode(self.encode().map_err(|_| std::fmt::Error)?) ) } } impl std::fmt::Debug for Address { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "Address<{}>: {}", match self { Address::Hash(_) => "Hash", Address::Uuid(_) => "UUID", Address::Attribute(_) => "Attribute", Address::Url(_) => "URL", }, self ) } } pub trait Addressable: AsDigest { fn address(&self) -> Result { Ok(Address::Hash(self.as_digest()?)) } } #[cfg(test)] mod tests { use url::Url; use uuid::Uuid; use crate::addressing::Address; use crate::hash::Digest; use super::UpEndError; #[test] fn test_hash_codec() -> Result<(), UpEndError> { let addr = Address::Hash(Digest(vec![1, 2, 3, 4, 5])); let encoded = addr.encode()?; let decoded = Address::decode(&encoded)?; assert_eq!(addr, decoded); Ok(()) } #[test] fn test_uuid_codec() -> Result<(), UpEndError> { let addr = Address::Uuid(Uuid::new_v4()); let encoded = addr.encode()?; let decoded = Address::decode(&encoded)?; assert_eq!(addr, decoded); Ok(()) } #[test] fn test_attribute_codec() -> Result<(), UpEndError> { let addr = Address::Attribute(String::from("ATTRIBUTE")); let encoded = addr.encode()?; let decoded = Address::decode(&encoded)?; assert_eq!(addr, decoded); Ok(()) } #[test] fn test_url_codec() -> Result<(), UpEndError> { let addr = Address::Url(Url::parse("https://upend.dev/an/url/that/is/particularly/long/because/multihash/used/to/have/a/small/limit").unwrap()); let encoded = addr.encode()?; let decoded = Address::decode(&encoded)?; assert_eq!(addr, decoded); Ok(()) } }