252 lines
7.5 KiB
Rust
252 lines
7.5 KiB
Rust
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;
|
|
use std::io::{Cursor, Write};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct InEntry {
|
|
pub entity: Option<Address>,
|
|
pub attribute: String,
|
|
pub value: EntryValue,
|
|
}
|
|
|
|
#[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<Self, Self::Error> {
|
|
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<Self, Self::Error> {
|
|
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<Self, Self::Error> {
|
|
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<Self, Self::Error> {
|
|
Ok(Entry {
|
|
entity: invariant.entity()?,
|
|
attribute: invariant.attribute.clone(),
|
|
value: invariant.value.clone(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl TryFrom<InEntry> for Entry {
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(in_entry: InEntry) -> Result<Self, Self::Error> {
|
|
match in_entry.entity {
|
|
Some(entity) => Ok(Entry {
|
|
entity,
|
|
attribute: in_entry.attribute,
|
|
value: in_entry.value,
|
|
}),
|
|
None => Ok(Entry::try_from(&InvariantEntry {
|
|
attribute: in_entry.attribute,
|
|
value: in_entry.value,
|
|
})?),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl InvariantEntry {
|
|
pub fn entity(&self) -> Result<Address> {
|
|
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<Hash> {
|
|
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<Hash> {
|
|
Entry::try_from(self)?.hash()
|
|
}
|
|
}
|
|
|
|
impl Addressable for Entry {}
|
|
impl Addressable for InvariantEntry {}
|
|
|
|
impl EntryValue {
|
|
pub fn to_string(&self) -> Result<String> {
|
|
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<Self, Self::Err> {
|
|
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::<f64>() {
|
|
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),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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::<EntryValue>()?;
|
|
assert_eq!(entry, decoded);
|
|
|
|
let entry = EntryValue::Number(1337.93);
|
|
let encoded = entry.to_string()?;
|
|
let decoded = encoded.parse::<EntryValue>()?;
|
|
assert_eq!(entry, decoded);
|
|
|
|
let entry = EntryValue::Address(Address::Url("https://upendproject.net".to_string()));
|
|
let encoded = entry.to_string()?;
|
|
let decoded = encoded.parse::<EntryValue>()?;
|
|
assert_eq!(entry, decoded);
|
|
|
|
Ok(())
|
|
}
|
|
}
|