use crate::addressing::{Address, Addressable}; use crate::database::inner::models; use crate::util::hash::{b58_decode, hash, Hash, Hashable}; use anyhow::{anyhow, Result}; use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::io::{Cursor, Write}; use url::Url; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Entry { pub entity: Address, pub attribute: String, pub value: EntryValue, pub provenance: String, pub timestamp: NaiveDateTime, } #[derive(Debug, Clone)] pub struct ImmutableEntry(pub Entry); #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InvariantEntry { pub attribute: String, pub value: EntryValue, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "t", content = "c")] pub enum EntryValue { String(String), Number(f64), Address(Address), Null, Invalid, } impl TryFrom<&models::Entry> for Entry { type Error = anyhow::Error; fn try_from(e: &models::Entry) -> Result { if let Some(value_str) = &e.value_str { Ok(Entry { entity: Address::decode(&e.entity)?, attribute: e.attribute.clone(), value: value_str.parse()?, provenance: e.provenance.clone(), timestamp: e.timestamp, }) } else if let Some(value_num) = e.value_num { Ok(Entry { entity: Address::decode(&e.entity)?, attribute: e.attribute.clone(), value: EntryValue::Number(value_num), provenance: e.provenance.clone(), timestamp: e.timestamp, }) } else { Ok(Entry { entity: Address::decode(&e.entity)?, attribute: e.attribute.clone(), value: EntryValue::Number(f64::NAN), provenance: e.provenance.clone(), timestamp: e.timestamp, }) } } } impl TryFrom<&Entry> for models::Entry { type Error = anyhow::Error; fn try_from(e: &Entry) -> Result { if e.attribute.is_empty() { return Err(anyhow!("Attribute cannot be empty.")); } let base_entry = models::Entry { identity: e.address()?.encode()?, entity_searchable: match &e.entity { Address::Attribute(attr) => Some(attr.clone()), Address::Url(url) => Some(url.to_string()), _ => None, }, entity: e.entity.encode()?, attribute: e.attribute.clone(), value_str: None, value_num: None, immutable: false, provenance: e.provenance.clone(), timestamp: e.timestamp, }; match e.value { EntryValue::Number(n) => Ok(models::Entry { value_str: None, value_num: Some(n), ..base_entry }), _ => Ok(models::Entry { value_str: Some(e.value.to_string()?), value_num: None, ..base_entry }), } } } impl TryFrom<&ImmutableEntry> for models::Entry { type Error = anyhow::Error; fn try_from(e: &ImmutableEntry) -> Result { Ok(models::Entry { immutable: true, ..models::Entry::try_from(&e.0)? }) } } impl TryFrom<&InvariantEntry> for Entry { type Error = anyhow::Error; fn try_from(invariant: &InvariantEntry) -> Result { Ok(Entry { entity: invariant.entity()?, attribute: invariant.attribute.clone(), value: invariant.value.clone(), provenance: "INVARIANT".to_string(), 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.as_bytes())?; entity.write_all(self.value.to_string()?.as_bytes())?; Ok(Address::Hash(hash(entity.into_inner()))) } } 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 Hashable for Entry { fn hash(self: &Entry) -> Result { let mut result = Cursor::new(vec![0u8; 0]); result.write_all(self.entity.encode()?.as_slice())?; result.write_all(self.attribute.as_bytes())?; result.write_all(self.value.to_string()?.as_bytes())?; Ok(hash(result.get_ref())) } } impl Hashable for InvariantEntry { fn hash(&self) -> Result { Entry::try_from(self)?.hash() } } impl Addressable for Entry {} impl Addressable for InvariantEntry {} 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(anyhow!("Cannot serialize invalid value.")), }; 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) } } #[cfg(test)] mod tests { use super::*; use anyhow::Result; #[test] fn test_value_from_to_string() -> Result<()> { let entry = EntryValue::String("hello".to_string()); let encoded = entry.to_string()?; let decoded = encoded.parse::()?; assert_eq!(entry, decoded); let entry = EntryValue::Number(1337.93); let encoded = entry.to_string()?; let decoded = encoded.parse::()?; 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::()?; assert_eq!(entry, decoded); let entry = EntryValue::String("".to_string()); let encoded = entry.to_string()?; let decoded = encoded.parse::()?; assert_eq!(entry, decoded); let entry = EntryValue::Null; let encoded = entry.to_string()?; let decoded = encoded.parse::()?; 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())) ); } }