use crate::addressing::Address; use crate::error::UpEndError; use crate::hash::{b58_decode, sha256hash, AsMultihash, AsMultihashError, UpMultihash}; use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::io::{Cursor, Write}; use std::str::FromStr; use url::Url; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] pub struct Attribute(String); impl Attribute { pub fn null() -> Self { Self("".to_string()) } } impl std::fmt::Display for Attribute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl FromStr for Attribute { type Err = UpEndError; fn from_str(value: &str) -> Result { if value.is_empty() { Err(UpEndError::EmptyAttribute) } else { Ok(Self(value.to_uppercase())) } } } impl PartialEq for Attribute where S: AsRef, { fn eq(&self, other: &S) -> bool { self.0.eq_ignore_ascii_case(other.as_ref()) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Entry { pub entity: Address, pub attribute: Attribute, pub value: EntryValue, pub provenance: String, pub user: Option, pub timestamp: NaiveDateTime, } #[derive(Debug, Clone)] pub struct ImmutableEntry(pub Entry); #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InvariantEntry { pub attribute: Attribute, pub value: EntryValue, } #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "t", content = "c")] pub enum EntryValue { String(String), Number(f64), Address(Address), Null, Invalid, } impl TryFrom<&InvariantEntry> for Entry { type Error = UpEndError; fn try_from(invariant: &InvariantEntry) -> Result { Ok(Entry { entity: invariant.entity()?, attribute: invariant.attribute.clone(), value: invariant.value.clone(), provenance: "INVARIANT".to_string(), user: None, timestamp: NaiveDateTime::from_timestamp_opt(0, 0).unwrap(), }) } } impl InvariantEntry { pub fn entity(&self) -> Result { let mut entity = Cursor::new(vec![0u8; 0]); entity .write_all(self.attribute.0.as_bytes()) .map_err(UpEndError::from_any)?; entity .write_all(self.value.to_string()?.as_bytes()) .map_err(UpEndError::from_any)?; Ok(Address::Hash( sha256hash(entity.into_inner()).map_err(UpEndError::from_any)?, )) } } impl std::fmt::Display for Entry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{} | {} | {}", self.entity, self.attribute, self.value) } } impl AsMultihash for Entry { fn as_multihash(&self) -> Result { let mut result = Cursor::new(vec![0u8; 0]); result.write_all( self.entity .encode() .map_err(|e| AsMultihashError(e.to_string()))? .as_slice(), )?; result.write_all(self.attribute.0.as_bytes())?; result.write_all( self.value .to_string() .map_err(|e| AsMultihashError(e.to_string()))? .as_bytes(), )?; sha256hash(result.get_ref()) } } impl AsMultihash for InvariantEntry { fn as_multihash(&self) -> Result { Entry::try_from(self) .map_err(|e| AsMultihashError(e.to_string()))? .as_multihash() } } impl EntryValue { pub fn to_string(&self) -> Result { let (type_char, content) = match self { EntryValue::String(value) => ('S', value.to_owned()), EntryValue::Number(n) => ('N', n.to_string()), EntryValue::Address(address) => ('O', address.to_string()), EntryValue::Null => ('X', "".to_string()), EntryValue::Invalid => return Err(UpEndError::CannotSerializeInvalid), }; Ok(format!("{}{}", type_char, content)) } pub fn guess_from>(string: S) -> Self { let string = string.as_ref(); match string.parse::() { Ok(num) => EntryValue::Number(num), Err(_) => { if let Ok(url) = Url::parse(string) { EntryValue::Address(Address::Url(url)) } else { EntryValue::String(string.to_string()) } } } } } impl std::str::FromStr for EntryValue { type Err = std::convert::Infallible; fn from_str(s: &str) -> Result { if s.len() < 2 { match s.chars().next() { Some('S') => Ok(EntryValue::String("".into())), Some('X') => Ok(EntryValue::Null), _ => Ok(EntryValue::Invalid), } } else { let (type_char, content) = s.split_at(1); match (type_char, content) { ("S", content) => Ok(EntryValue::String(String::from(content))), ("N", content) => { if let Ok(n) = content.parse::() { Ok(EntryValue::Number(n)) } else { Ok(EntryValue::Invalid) } } ("O", content) => { if let Ok(addr) = b58_decode(content).and_then(|v| Address::decode(&v)) { Ok(EntryValue::Address(addr)) } else { Ok(EntryValue::Invalid) } } _ => Ok(EntryValue::Invalid), } } } } impl From for EntryValue { fn from(value: Url) -> Self { EntryValue::Address(Address::Url(value)) } } impl std::fmt::Display for EntryValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let (entry_type, entry_value) = match self { EntryValue::Address(address) => ("ADDRESS", address.to_string()), EntryValue::String(string) => ("STRING", string.to_owned()), EntryValue::Number(n) => ("NUMBER", n.to_string()), EntryValue::Null => ("NULL", "NULL".to_string()), EntryValue::Invalid => ("INVALID", "INVALID".to_string()), }; write!(f, "{}: {}", entry_type, entry_value) } } impl From<&str> for EntryValue { fn from(str: &str) -> Self { Self::String(str.to_string()) } } impl From for EntryValue { fn from(str: String) -> Self { Self::String(str) } } impl From for EntryValue { fn from(num: f64) -> Self { Self::Number(num) } } impl From
for EntryValue { fn from(address: Address) -> Self { Self::Address(address) } } pub enum EntryPart { Entity(Address), Attribute(Attribute), Value(EntryValue), Provenance(String), Timestamp(NaiveDateTime), } #[cfg(test)] mod tests { use super::*; #[test] fn test_value_from_to_string() -> Result<(), UpEndError> { let entry = EntryValue::String("hello".to_string()); let encoded = entry.to_string()?; let decoded = encoded.parse::().unwrap(); assert_eq!(entry, decoded); let entry = EntryValue::Number(1337.93); let encoded = entry.to_string()?; let decoded = encoded.parse::().unwrap(); assert_eq!(entry, decoded); let entry = EntryValue::Address(Address::Url(Url::parse("https://upend.dev").unwrap())); let encoded = entry.to_string()?; let decoded = encoded.parse::().unwrap(); assert_eq!(entry, decoded); let entry = EntryValue::String("".to_string()); let encoded = entry.to_string()?; let decoded = encoded.parse::().unwrap(); assert_eq!(entry, decoded); let entry = EntryValue::Null; let encoded = entry.to_string()?; let decoded = encoded.parse::().unwrap(); assert_eq!(entry, decoded); Ok(()) } #[test] fn test_into() { assert_eq!(EntryValue::String(String::from("UPEND")), "UPEND".into()); assert_eq!(EntryValue::Number(1337.93), 1337.93.into()); let addr = Address::Url(Url::parse("https://upend.dev").unwrap()); assert_eq!(EntryValue::Address(addr.clone()), addr.into()); } #[test] fn test_guess_value() { assert_eq!( EntryValue::guess_from("UPEND"), EntryValue::String("UPEND".into()) ); assert_eq!( EntryValue::guess_from("1337.93"), EntryValue::Number(1337.93) ); assert_eq!( EntryValue::guess_from("https://upend.dev"), EntryValue::Address(Address::Url(Url::parse("https://upend.dev").unwrap())) ); } }