2023-08-29 13:11:48 +02:00
|
|
|
use crate::addressing::Address;
|
2023-06-27 21:11:10 +02:00
|
|
|
use crate::error::UpEndError;
|
2023-06-29 14:29:38 +02:00
|
|
|
use crate::hash::{b58_decode, sha256hash, AsMultihash, AsMultihashError, UpMultihash};
|
2023-04-02 19:55:51 +02:00
|
|
|
use chrono::NaiveDateTime;
|
2021-07-26 21:00:05 +02:00
|
|
|
use serde::{Deserialize, Serialize};
|
2022-02-12 14:51:28 +01:00
|
|
|
use std::convert::TryFrom;
|
2021-07-26 21:00:05 +02:00
|
|
|
use std::io::{Cursor, Write};
|
2024-02-15 19:10:22 +01:00
|
|
|
use std::str::FromStr;
|
2023-05-19 17:30:09 +02:00
|
|
|
use url::Url;
|
2021-07-26 21:00:05 +02:00
|
|
|
|
2024-02-15 19:10:22 +01:00
|
|
|
#[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<Self, Self::Err> {
|
|
|
|
if value.is_empty() {
|
|
|
|
Err(UpEndError::EmptyAttribute)
|
|
|
|
} else {
|
|
|
|
Ok(Self(value.to_uppercase()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S> PartialEq<S> for Attribute
|
|
|
|
where
|
|
|
|
S: AsRef<str>,
|
|
|
|
{
|
|
|
|
fn eq(&self, other: &S) -> bool {
|
|
|
|
self.0.eq_ignore_ascii_case(other.as_ref())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-26 21:00:05 +02:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
pub struct Entry {
|
|
|
|
pub entity: Address,
|
2024-02-15 19:10:22 +01:00
|
|
|
pub attribute: Attribute,
|
2021-07-26 21:00:05 +02:00
|
|
|
pub value: EntryValue,
|
2023-04-02 19:55:51 +02:00
|
|
|
pub provenance: String,
|
|
|
|
pub timestamp: NaiveDateTime,
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
|
2022-01-21 17:59:53 +01:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct ImmutableEntry(pub Entry);
|
|
|
|
|
2021-07-26 21:00:05 +02:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
pub struct InvariantEntry {
|
2024-02-15 19:10:22 +01:00
|
|
|
pub attribute: Attribute,
|
2021-07-26 21:00:05 +02:00
|
|
|
pub value: EntryValue,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(tag = "t", content = "c")]
|
|
|
|
pub enum EntryValue {
|
2022-01-28 18:17:14 +01:00
|
|
|
String(String),
|
|
|
|
Number(f64),
|
2021-07-26 21:00:05 +02:00
|
|
|
Address(Address),
|
2022-02-19 14:59:13 +01:00
|
|
|
Null,
|
2021-07-26 21:00:05 +02:00
|
|
|
Invalid,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<&InvariantEntry> for Entry {
|
2023-06-27 21:11:10 +02:00
|
|
|
type Error = UpEndError;
|
2021-07-26 21:00:05 +02:00
|
|
|
|
|
|
|
fn try_from(invariant: &InvariantEntry) -> Result<Self, Self::Error> {
|
|
|
|
Ok(Entry {
|
|
|
|
entity: invariant.entity()?,
|
|
|
|
attribute: invariant.attribute.clone(),
|
|
|
|
value: invariant.value.clone(),
|
2023-04-02 19:55:51 +02:00
|
|
|
provenance: "INVARIANT".to_string(),
|
2024-02-15 19:10:22 +01:00
|
|
|
timestamp: NaiveDateTime::from_timestamp_opt(0, 0).unwrap(),
|
2021-07-26 21:00:05 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl InvariantEntry {
|
2023-06-27 21:11:10 +02:00
|
|
|
pub fn entity(&self) -> Result<Address, UpEndError> {
|
2021-07-26 21:00:05 +02:00
|
|
|
let mut entity = Cursor::new(vec![0u8; 0]);
|
2023-06-27 21:11:10 +02:00
|
|
|
entity
|
2024-02-15 19:10:22 +01:00
|
|
|
.write_all(self.attribute.0.as_bytes())
|
2023-06-27 21:11:10 +02:00
|
|
|
.map_err(UpEndError::from_any)?;
|
|
|
|
entity
|
|
|
|
.write_all(self.value.to_string()?.as_bytes())
|
|
|
|
.map_err(UpEndError::from_any)?;
|
2023-06-29 14:29:38 +02:00
|
|
|
Ok(Address::Hash(
|
|
|
|
sha256hash(entity.into_inner()).map_err(UpEndError::from_any)?,
|
|
|
|
))
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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-29 14:29:38 +02:00
|
|
|
impl AsMultihash for Entry {
|
|
|
|
fn as_multihash(&self) -> Result<UpMultihash, AsMultihashError> {
|
2021-07-26 21:00:05 +02:00
|
|
|
let mut result = Cursor::new(vec![0u8; 0]);
|
2023-06-25 15:29:52 +02:00
|
|
|
result.write_all(
|
|
|
|
self.entity
|
|
|
|
.encode()
|
2023-06-29 14:29:38 +02:00
|
|
|
.map_err(|e| AsMultihashError(e.to_string()))?
|
2023-06-25 15:29:52 +02:00
|
|
|
.as_slice(),
|
|
|
|
)?;
|
2024-02-15 19:10:22 +01:00
|
|
|
result.write_all(self.attribute.0.as_bytes())?;
|
2023-06-25 15:29:52 +02:00
|
|
|
result.write_all(
|
|
|
|
self.value
|
|
|
|
.to_string()
|
2023-06-29 14:29:38 +02:00
|
|
|
.map_err(|e| AsMultihashError(e.to_string()))?
|
2023-06-25 15:29:52 +02:00
|
|
|
.as_bytes(),
|
|
|
|
)?;
|
2023-06-29 15:17:06 +02:00
|
|
|
sha256hash(result.get_ref())
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-29 14:29:38 +02:00
|
|
|
impl AsMultihash for InvariantEntry {
|
|
|
|
fn as_multihash(&self) -> Result<UpMultihash, AsMultihashError> {
|
2023-06-25 15:29:52 +02:00
|
|
|
Entry::try_from(self)
|
2023-06-29 14:29:38 +02:00
|
|
|
.map_err(|e| AsMultihashError(e.to_string()))?
|
|
|
|
.as_multihash()
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl EntryValue {
|
2023-06-27 21:11:10 +02:00
|
|
|
pub fn to_string(&self) -> Result<String, UpEndError> {
|
2021-07-26 21:00:05 +02:00
|
|
|
let (type_char, content) = match self {
|
2022-01-28 18:17:14 +01:00
|
|
|
EntryValue::String(value) => ('S', value.to_owned()),
|
|
|
|
EntryValue::Number(n) => ('N', n.to_string()),
|
2021-07-26 21:00:05 +02:00
|
|
|
EntryValue::Address(address) => ('O', address.to_string()),
|
2022-02-19 14:59:13 +01:00
|
|
|
EntryValue::Null => ('X', "".to_string()),
|
2023-06-27 21:11:10 +02:00
|
|
|
EntryValue::Invalid => return Err(UpEndError::CannotSerializeInvalid),
|
2021-07-26 21:00:05 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(format!("{}{}", type_char, content))
|
|
|
|
}
|
2022-03-01 21:00:29 +01:00
|
|
|
|
|
|
|
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(_) => {
|
2023-05-19 17:30:09 +02:00
|
|
|
if let Ok(url) = Url::parse(string) {
|
|
|
|
EntryValue::Address(Address::Url(url))
|
2022-03-01 21:00:29 +01:00
|
|
|
} else {
|
|
|
|
EntryValue::String(string.to_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-26 21:00:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
2021-07-26 21:00:05 +02:00
|
|
|
} else {
|
|
|
|
let (type_char, content) = s.split_at(1);
|
|
|
|
match (type_char, content) {
|
2022-01-28 18:17:14 +01:00
|
|
|
("S", content) => Ok(EntryValue::String(String::from(content))),
|
|
|
|
("N", content) => {
|
|
|
|
if let Ok(n) = content.parse::<f64>() {
|
|
|
|
Ok(EntryValue::Number(n))
|
2021-07-26 21:00:05 +02:00
|
|
|
} 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)) {
|
2021-07-26 21:00:05 +02:00
|
|
|
Ok(EntryValue::Address(addr))
|
|
|
|
} else {
|
|
|
|
Ok(EntryValue::Invalid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => Ok(EntryValue::Invalid),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-01-28 18:17:14 +01:00
|
|
|
|
2023-05-19 17:30:09 +02:00
|
|
|
impl From<Url> for EntryValue {
|
|
|
|
fn from(value: Url) -> Self {
|
|
|
|
EntryValue::Address(Address::Url(value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-13 12:37:16 +01:00
|
|
|
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()),
|
2022-02-13 12:37:16 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-14 16:30:20 +02:00
|
|
|
pub enum EntryPart {
|
|
|
|
Entity(Address),
|
|
|
|
Attribute(Attribute),
|
|
|
|
Value(EntryValue),
|
|
|
|
Provenance(String),
|
|
|
|
Timestamp(NaiveDateTime),
|
|
|
|
}
|
|
|
|
|
2022-01-28 18:17:14 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
2023-06-27 21:11:10 +02:00
|
|
|
fn test_value_from_to_string() -> Result<(), UpEndError> {
|
2022-01-28 18:17:14 +01:00
|
|
|
let entry = EntryValue::String("hello".to_string());
|
|
|
|
let encoded = entry.to_string()?;
|
2023-06-27 21:11:10 +02:00
|
|
|
let decoded = encoded.parse::<EntryValue>().unwrap();
|
2022-01-28 18:17:14 +01:00
|
|
|
assert_eq!(entry, decoded);
|
|
|
|
|
|
|
|
let entry = EntryValue::Number(1337.93);
|
|
|
|
let encoded = entry.to_string()?;
|
2023-06-27 21:11:10 +02:00
|
|
|
let decoded = encoded.parse::<EntryValue>().unwrap();
|
2022-01-28 18:17:14 +01:00
|
|
|
assert_eq!(entry, decoded);
|
|
|
|
|
2023-05-19 17:30:09 +02:00
|
|
|
let entry = EntryValue::Address(Address::Url(Url::parse("https://upend.dev").unwrap()));
|
2022-01-28 18:17:14 +01:00
|
|
|
let encoded = entry.to_string()?;
|
2023-06-27 21:11:10 +02:00
|
|
|
let decoded = encoded.parse::<EntryValue>().unwrap();
|
2022-01-28 18:17:14 +01:00
|
|
|
assert_eq!(entry, decoded);
|
|
|
|
|
2022-02-13 12:28:15 +01:00
|
|
|
let entry = EntryValue::String("".to_string());
|
|
|
|
let encoded = entry.to_string()?;
|
2023-06-27 21:11:10 +02:00
|
|
|
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()?;
|
2023-06-27 21:11:10 +02:00
|
|
|
let decoded = encoded.parse::<EntryValue>().unwrap();
|
2022-02-19 14:59:13 +01:00
|
|
|
assert_eq!(entry, decoded);
|
|
|
|
|
2022-01-28 18:17:14 +01:00
|
|
|
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());
|
2023-05-19 17:30:09 +02:00
|
|
|
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());
|
|
|
|
}
|
2023-05-19 17:14:33 +02:00
|
|
|
|
|
|
|
#[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"),
|
2023-05-19 17:30:09 +02:00
|
|
|
EntryValue::Address(Address::Url(Url::parse("https://upend.dev").unwrap()))
|
2023-05-19 17:14:33 +02:00
|
|
|
);
|
|
|
|
}
|
2022-01-28 18:17:14 +01:00
|
|
|
}
|