use crate::addressing::{Address, Addressable}; use crate::database::inner::models; use crate::util::hash::{b58_decode, hash, Hash, Hashable}; use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use std::io::{Cursor, Write}; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum InEntry { Invariant(InvariantEntry), Address { entity: InAddress }, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum InAddress { Address(String), Components { t: String, c: String }, } impl TryInto
for InAddress { type Error = anyhow::Error; fn try_into(self) -> Result { Ok(match self { InAddress::Address(address) => address.parse()?, InAddress::Components { t, c } => { // I absolutely cannot handle serde magic right now // TODO: make this automatically derive from `Address` definition match t.as_str() { "Attribute" => Address::Attribute(c), "Url" => Address::Url(c), _ => c.parse()?, } } }) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Entry { pub entity: Address, pub attribute: String, pub value: EntryValue, } #[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), 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()?, }) } 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), }) } else { Err(anyhow!( "Inconsistent database: Both values of entry are NULL!" )) } } } impl TryFrom<&Entry> for models::Entry { type Error = anyhow::Error; fn try_from(e: &Entry) -> Result { 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.clone()), _ => None, }, entity: e.entity.encode()?, attribute: e.attribute.clone(), value_str: None, value_num: None, immutable: false, }; 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(), }) } } 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::Invalid => return Err(anyhow!("Cannot serialize invalid Entity value.")), }; Ok(format!("{}{}", type_char, content)) } } 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::Invalid => ("INVALID", "INVALID".to_string()), }; write!(f, "{}: {}", entry_type, entry_value) } } impl std::str::FromStr for EntryValue { type Err = std::convert::Infallible; fn from_str(s: &str) -> Result { if s.len() < 2 { 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(str: String) -> Self { Self::String(str) } } #[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("https://upendproject.net".to_string())); let encoded = entry.to_string()?; let decoded = encoded.parse::()?; assert_eq!(entry, decoded); Ok(()) } #[test] fn test_in_address() -> Result<()> { let address = Address::Url("https://upendproject.net".into()); let in_address = InAddress::Address(address.to_string()); assert_eq!(address, in_address.try_into()?); let in_address = InAddress::Components { t: "Url".into(), c: "https://upendproject.net".into(), }; assert_eq!(address, in_address.try_into()?); Ok(()) } }