upend/base/src/entry.rs

281 lines
8.4 KiB
Rust
Raw Normal View History

use crate::addressing::{Address, Addressable};
use crate::error::UpEndError;
2023-06-25 15:29:52 +02:00
use crate::hash::{b58_decode, sha256hash, AsDigest, AsDigestError, Digest};
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
2022-02-12 14:51:28 +01:00
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),
2022-02-19 14:59:13 +01:00
Null,
Invalid,
}
impl TryFrom<&InvariantEntry> for Entry {
type Error = UpEndError;
fn try_from(invariant: &InvariantEntry) -> Result<Self, Self::Error> {
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<Address, UpEndError> {
let mut entity = Cursor::new(vec![0u8; 0]);
entity
.write_all(self.attribute.as_bytes())
.map_err(UpEndError::from_any)?;
entity
.write_all(self.value.to_string()?.as_bytes())
.map_err(UpEndError::from_any)?;
2023-06-25 15:29:52 +02:00
Ok(Address::Hash(sha256hash(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)
}
}
2023-06-25 15:29:52 +02:00
// 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 AsDigest for Entry {
fn as_digest(&self) -> Result<Digest, AsDigestError> {
let mut result = Cursor::new(vec![0u8; 0]);
2023-06-25 15:29:52 +02:00
result.write_all(
self.entity
.encode()
.map_err(|e| AsDigestError(e.to_string()))?
.as_slice(),
)?;
result.write_all(self.attribute.as_bytes())?;
2023-06-25 15:29:52 +02:00
result.write_all(
self.value
.to_string()
.map_err(|e| AsDigestError(e.to_string()))?
.as_bytes(),
)?;
Ok(sha256hash(result.get_ref()))
}
}
2023-06-25 15:29:52 +02:00
impl AsDigest for InvariantEntry {
fn as_digest(&self) -> Result<Digest, AsDigestError> {
Entry::try_from(self)
.map_err(|e| AsDigestError(e.to_string()))?
.as_digest()
}
}
impl Addressable for Entry {}
impl Addressable for InvariantEntry {}
impl EntryValue {
pub fn to_string(&self) -> Result<String, UpEndError> {
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()),
2022-02-19 14:59:13 +01:00
EntryValue::Null => ('X', "".to_string()),
EntryValue::Invalid => return Err(UpEndError::CannotSerializeInvalid),
};
Ok(format!("{}{}", type_char, content))
}
pub fn guess_from<S: AsRef<str>>(string: S) -> Self {
let string = string.as_ref();
match string.parse::<f64>() {
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<Self, Self::Err> {
if s.len() < 2 {
2022-02-19 14:59:13 +01:00
match s.chars().next() {
Some('S') => Ok(EntryValue::String("".into())),
Some('X') => Ok(EntryValue::Null),
_ => Ok(EntryValue::Invalid),
2022-02-13 12:28:15 +01:00
}
} 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) => {
2022-01-26 16:55:23 +01:00
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<Url> 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()),
2022-02-19 14:59:13 +01:00
EntryValue::Null => ("NULL", "NULL".to_string()),
EntryValue::Invalid => ("INVALID", "INVALID".to_string()),
};
write!(f, "{}: {}", entry_type, entry_value)
}
}
2022-02-13 12:55:49 +01:00
impl From<&str> for EntryValue {
fn from(str: &str) -> Self {
Self::String(str.to_string())
}
}
impl From<String> for EntryValue {
fn from(str: String) -> Self {
Self::String(str)
}
}
impl From<f64> for EntryValue {
fn from(num: f64) -> Self {
Self::Number(num)
}
}
impl From<Address> for EntryValue {
fn from(address: Address) -> Self {
Self::Address(address)
2022-02-10 11:38:45 +01:00
}
}
#[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::<EntryValue>().unwrap();
assert_eq!(entry, decoded);
let entry = EntryValue::Number(1337.93);
let encoded = entry.to_string()?;
let decoded = encoded.parse::<EntryValue>().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::<EntryValue>().unwrap();
assert_eq!(entry, decoded);
2022-02-13 12:28:15 +01:00
let entry = EntryValue::String("".to_string());
let encoded = entry.to_string()?;
let decoded = encoded.parse::<EntryValue>().unwrap();
2022-02-13 12:28:15 +01:00
assert_eq!(entry, decoded);
2022-02-19 14:59:13 +01:00
let entry = EntryValue::Null;
let encoded = entry.to_string()?;
let decoded = encoded.parse::<EntryValue>().unwrap();
2022-02-19 14:59:13 +01:00
assert_eq!(entry, decoded);
Ok(())
}
2022-02-13 12:55:49 +01:00
#[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());
2022-02-13 12:55:49 +01:00
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()))
);
}
}