refactor: Attributes are their proper type instead of strings
ci/woodpecker/push/woodpecker Pipeline was successful Details

Also adds checking for non-emptiness and upper-casing
feat/tables
Tomáš Mládek 2024-02-15 19:10:22 +01:00
parent c5c157a856
commit afa5bd088d
16 changed files with 210 additions and 164 deletions

View File

@ -1,3 +1,4 @@
use crate::entry::Attribute;
use crate::error::{AddressComponentsDecodeError, UpEndError}; use crate::error::{AddressComponentsDecodeError, UpEndError};
use crate::hash::{ use crate::hash::{
b58_decode, b58_encode, AsMultihash, AsMultihashError, LargeMultihash, UpMultihash, IDENTITY, b58_decode, b58_encode, AsMultihash, AsMultihashError, LargeMultihash, UpMultihash, IDENTITY,
@ -18,7 +19,7 @@ use wasm_bindgen::prelude::*;
pub enum Address { pub enum Address {
Hash(UpMultihash), Hash(UpMultihash),
Uuid(Uuid), Uuid(Uuid),
Attribute(String), Attribute(Attribute),
Url(Url), Url(Url),
} }
@ -62,7 +63,7 @@ impl Address {
), ),
Self::Attribute(attribute) => ( Self::Attribute(attribute) => (
UP_ATTRIBUTE, UP_ATTRIBUTE,
LargeMultihash::wrap(IDENTITY, attribute.as_bytes()) LargeMultihash::wrap(IDENTITY, attribute.to_string().as_bytes())
.map_err(UpEndError::from_any)?, .map_err(UpEndError::from_any)?,
), ),
Self::Url(url) => ( Self::Url(url) => (
@ -103,7 +104,10 @@ impl Address {
Uuid::from_slice(digest.as_slice()).map_err(UpEndError::from_any)?, Uuid::from_slice(digest.as_slice()).map_err(UpEndError::from_any)?,
)), )),
UP_ATTRIBUTE => Ok(Address::Attribute( UP_ATTRIBUTE => Ok(Address::Attribute(
String::from_utf8(digest).map_err(UpEndError::from_any)?, String::from_utf8(digest)
.map_err(UpEndError::from_any)?
.as_str()
.parse()?,
)), )),
UP_URL => Ok(Address::Url( UP_URL => Ok(Address::Url(
Url::parse(&String::from_utf8(digest).map_err(UpEndError::from_any)?) Url::parse(&String::from_utf8(digest).map_err(UpEndError::from_any)?)
@ -120,7 +124,7 @@ impl Address {
let (entity_type, entity_content) = match self { let (entity_type, entity_content) = match self {
Address::Hash(uphash) => ("Hash", Some(b58_encode(uphash.to_bytes()))), Address::Hash(uphash) => ("Hash", Some(b58_encode(uphash.to_bytes()))),
Address::Uuid(uuid) => ("Uuid", Some(uuid.to_string())), Address::Uuid(uuid) => ("Uuid", Some(uuid.to_string())),
Address::Attribute(attribute) => ("Attribute", Some(attribute.clone())), Address::Attribute(attribute) => ("Attribute", Some(attribute.to_string())),
Address::Url(url) => ("Url", Some(url.to_string())), Address::Url(url) => ("Url", Some(url.to_string())),
}; };
@ -133,11 +137,12 @@ impl Address {
pub fn from_components(components: AddressComponents) -> Result<Self, UpEndError> { pub fn from_components(components: AddressComponents) -> Result<Self, UpEndError> {
// TODO: make this automatically derive from `Address` definition // TODO: make this automatically derive from `Address` definition
let address = match components { let address = match components {
AddressComponents { t, c } if t == "Attribute" => { AddressComponents { t, c } if t == "Attribute" => Address::Attribute(
Address::Attribute(c.ok_or(UpEndError::AddressComponentsDecodeError( c.ok_or(UpEndError::AddressComponentsDecodeError(
AddressComponentsDecodeError::MissingValue, AddressComponentsDecodeError::MissingValue,
))?) ))?
} .parse()?,
),
AddressComponents { t, c } if t == "Url" => Address::Url(if let Some(string) = c { AddressComponents { t, c } if t == "Url" => Address::Url(if let Some(string) = c {
Url::parse(&string).map_err(|e| { Url::parse(&string).map_err(|e| {
UpEndError::AddressComponentsDecodeError( UpEndError::AddressComponentsDecodeError(
@ -276,7 +281,7 @@ mod tests {
#[test] #[test]
fn test_attribute_codec() -> Result<(), UpEndError> { fn test_attribute_codec() -> Result<(), UpEndError> {
let addr = Address::Attribute(String::from("ATTRIBUTE")); let addr = Address::Attribute("ATTRIBUTE".parse().unwrap());
let encoded = addr.encode()?; let encoded = addr.encode()?;
let decoded = Address::decode(&encoded)?; let decoded = Address::decode(&encoded)?;
assert_eq!(addr, decoded); assert_eq!(addr, decoded);

View File

@ -1,4 +1,5 @@
use crate::addressing::Address; use crate::addressing::Address;
use crate::entry::Attribute;
use crate::entry::InvariantEntry; use crate::entry::InvariantEntry;
use crate::hash::{LargeMultihash, UpMultihash}; use crate::hash::{LargeMultihash, UpMultihash};
@ -19,13 +20,13 @@ pub const ATTR_KEY: &str = "KEY";
lazy_static! { lazy_static! {
pub static ref HIER_ROOT_INVARIANT: InvariantEntry = InvariantEntry { pub static ref HIER_ROOT_INVARIANT: InvariantEntry = InvariantEntry {
attribute: String::from(ATTR_KEY), attribute: ATTR_KEY.parse().unwrap(),
value: "HIER_ROOT".into(), value: "HIER_ROOT".into(),
}; };
pub static ref HIER_ROOT_ADDR: Address = HIER_ROOT_INVARIANT.entity().unwrap(); pub static ref HIER_ROOT_ADDR: Address = HIER_ROOT_INVARIANT.entity().unwrap();
pub static ref TYPE_HASH_ADDRESS: Address = pub static ref TYPE_HASH_ADDRESS: Address =
Address::Hash(UpMultihash::from(LargeMultihash::default())); Address::Hash(UpMultihash::from(LargeMultihash::default()));
pub static ref TYPE_UUID_ADDRESS: Address = Address::Uuid(uuid::Uuid::nil()); pub static ref TYPE_UUID_ADDRESS: Address = Address::Uuid(uuid::Uuid::nil());
pub static ref TYPE_ATTRIBUTE_ADDRESS: Address = Address::Attribute("".to_string()); pub static ref TYPE_ATTRIBUTE_ADDRESS: Address = Address::Attribute(Attribute::null());
pub static ref TYPE_URL_ADDRESS: Address = Address::Url(url::Url::parse("up:").unwrap()); pub static ref TYPE_URL_ADDRESS: Address = Address::Url(url::Url::parse("up:").unwrap());
} }

View File

@ -1,17 +1,52 @@
use crate::addressing::Address; use crate::addressing::Address;
use crate::error::UpEndError; use crate::error::UpEndError;
use crate::hash::{b58_decode, sha256hash, AsMultihash, AsMultihashError, UpMultihash}; use crate::hash::{b58_decode, sha256hash, AsMultihash, AsMultihashError, UpMultihash};
use crate::lang::Attribute;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::io::{Cursor, Write}; use std::io::{Cursor, Write};
use std::str::FromStr;
use url::Url; use url::Url;
#[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())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Entry { pub struct Entry {
pub entity: Address, pub entity: Address,
pub attribute: String, pub attribute: Attribute,
pub value: EntryValue, pub value: EntryValue,
pub provenance: String, pub provenance: String,
pub timestamp: NaiveDateTime, pub timestamp: NaiveDateTime,
@ -22,7 +57,7 @@ pub struct ImmutableEntry(pub Entry);
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InvariantEntry { pub struct InvariantEntry {
pub attribute: String, pub attribute: Attribute,
pub value: EntryValue, pub value: EntryValue,
} }
@ -36,18 +71,6 @@ pub enum EntryValue {
Invalid, Invalid,
} }
impl Default for Entry {
fn default() -> Self {
Self {
entity: Address::Uuid(uuid::Uuid::nil()),
attribute: Default::default(),
value: EntryValue::Null,
provenance: "SYSTEM".into(),
timestamp: NaiveDateTime::from_timestamp_opt(0, 0).unwrap(),
}
}
}
impl TryFrom<&InvariantEntry> for Entry { impl TryFrom<&InvariantEntry> for Entry {
type Error = UpEndError; type Error = UpEndError;
@ -57,7 +80,7 @@ impl TryFrom<&InvariantEntry> for Entry {
attribute: invariant.attribute.clone(), attribute: invariant.attribute.clone(),
value: invariant.value.clone(), value: invariant.value.clone(),
provenance: "INVARIANT".to_string(), provenance: "INVARIANT".to_string(),
..Default::default() timestamp: NaiveDateTime::from_timestamp_opt(0, 0).unwrap(),
}) })
} }
} }
@ -66,7 +89,7 @@ impl InvariantEntry {
pub fn entity(&self) -> Result<Address, UpEndError> { pub fn entity(&self) -> Result<Address, UpEndError> {
let mut entity = Cursor::new(vec![0u8; 0]); let mut entity = Cursor::new(vec![0u8; 0]);
entity entity
.write_all(self.attribute.as_bytes()) .write_all(self.attribute.0.as_bytes())
.map_err(UpEndError::from_any)?; .map_err(UpEndError::from_any)?;
entity entity
.write_all(self.value.to_string()?.as_bytes()) .write_all(self.value.to_string()?.as_bytes())
@ -92,7 +115,7 @@ impl AsMultihash for Entry {
.map_err(|e| AsMultihashError(e.to_string()))? .map_err(|e| AsMultihashError(e.to_string()))?
.as_slice(), .as_slice(),
)?; )?;
result.write_all(self.attribute.as_bytes())?; result.write_all(self.attribute.0.as_bytes())?;
result.write_all( result.write_all(
self.value self.value
.to_string() .to_string()

View File

@ -3,6 +3,7 @@ pub enum UpEndError {
HashDecodeError(String), HashDecodeError(String),
AddressParseError(String), AddressParseError(String),
AddressComponentsDecodeError(AddressComponentsDecodeError), AddressComponentsDecodeError(AddressComponentsDecodeError),
EmptyAttribute,
CannotSerializeInvalid, CannotSerializeInvalid,
QueryParseError(String), QueryParseError(String),
Other(String), Other(String),
@ -35,6 +36,7 @@ impl std::fmt::Display for UpEndError {
String::from("Invalid EntryValues cannot be serialized."), String::from("Invalid EntryValues cannot be serialized."),
UpEndError::QueryParseError(err) => format!("Error parsing query: {err}"), UpEndError::QueryParseError(err) => format!("Error parsing query: {err}"),
UpEndError::Other(err) => format!("Unknown error: {err}"), UpEndError::Other(err) => format!("Unknown error: {err}"),
UpEndError::EmptyAttribute => String::from("Attribute cannot be empty."),
} }
) )
} }

View File

@ -1,4 +1,5 @@
use crate::addressing::Address; use crate::addressing::Address;
use crate::entry::Attribute;
use crate::entry::EntryValue; use crate::entry::EntryValue;
use crate::error::UpEndError; use crate::error::UpEndError;
use nonempty::NonEmpty; use nonempty::NonEmpty;
@ -6,15 +7,6 @@ use std::borrow::Borrow;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::str::FromStr; use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribute(pub String);
impl From<&str> for Attribute {
fn from(str: &str) -> Self {
Self(str.to_string())
}
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum QueryComponent<T> pub enum QueryComponent<T>
where where
@ -79,7 +71,7 @@ impl TryFrom<lexpr::Value> for Attribute {
fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> { fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> {
match value { match value {
lexpr::Value::String(str) => Ok(Attribute(str.to_string())), lexpr::Value::String(str) => str.parse(),
_ => Err(UpEndError::QueryParseError( _ => Err(UpEndError::QueryParseError(
"Can only convert to attribute from string.".into(), "Can only convert to attribute from string.".into(),
)), )),
@ -331,7 +323,7 @@ mod test {
query, query,
Query::SingleQuery(QueryPart::Matches(PatternQuery { Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None), entity: QueryComponent::Variable(None),
attribute: QueryComponent::Exact("FOO".into()), attribute: QueryComponent::Exact("FOO".parse().unwrap()),
value: QueryComponent::Variable(None) value: QueryComponent::Variable(None)
})) }))
); );
@ -372,7 +364,7 @@ mod test {
query, query,
Query::SingleQuery(QueryPart::Matches(PatternQuery { Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None), entity: QueryComponent::Variable(None),
attribute: QueryComponent::In(vec!("FOO".into(), "BAR".into())), attribute: QueryComponent::In(vec!("FOO".parse().unwrap(), "BAR".parse().unwrap())),
value: QueryComponent::Variable(None) value: QueryComponent::Variable(None)
})) }))
); );

View File

@ -18,14 +18,15 @@ use upend_db::{
lazy_static! { lazy_static! {
pub static ref ID3_TYPE_INVARIANT: InvariantEntry = InvariantEntry { pub static ref ID3_TYPE_INVARIANT: InvariantEntry = InvariantEntry {
attribute: String::from(ATTR_KEY), attribute: ATTR_KEY.parse().unwrap(),
value: "TYPE_ID3".into(), value: "TYPE_ID3".into(),
}; };
pub static ref ID3_TYPE_LABEL: Entry = Entry { pub static ref ID3_TYPE_LABEL: Entry = Entry {
entity: ID3_TYPE_INVARIANT.entity().unwrap(), entity: ID3_TYPE_INVARIANT.entity().unwrap(),
attribute: ATTR_LABEL.into(), attribute: ATTR_LABEL.parse().unwrap(),
value: "ID3".into(), value: "ID3".into(),
..Default::default() provenance: "INVARIANT".to_string(),
timestamp: chrono::Utc::now().naive_utc(),
}; };
} }
@ -59,13 +60,14 @@ impl Extractor for ID3Extractor {
let tags = id3::Tag::read_from_path(file_path)?; let tags = id3::Tag::read_from_path(file_path)?;
let mut result: Vec<Entry> = tags let mut result: Vec<Entry> = vec![];
.frames()
.flat_map(|frame| match frame.content() { for frame in tags.frames() {
id3::Content::Text(text) => vec![ if let id3::Content::Text(text) = frame.content() {
result.extend(vec![
Entry { Entry {
entity: address.clone(), entity: address.clone(),
attribute: format!("ID3_{}", frame.id()), attribute: format!("ID3_{}", frame.id()).parse()?,
value: match frame.id() { value: match frame.id() {
"TYER" | "TBPM" => EntryValue::guess_from(text), "TYER" | "TBPM" => EntryValue::guess_from(text),
_ => text.clone().into(), _ => text.clone().into(),
@ -74,16 +76,15 @@ impl Extractor for ID3Extractor {
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
}, },
Entry { Entry {
entity: Address::Attribute(format!("ID3_{}", frame.id())), entity: (format!("ID3_{}", frame.id())).parse()?,
attribute: ATTR_LABEL.into(), attribute: ATTR_LABEL.parse().unwrap(),
value: format!("ID3: {}", frame.name()).into(), value: format!("ID3: {}", frame.name()).into(),
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
}, },
], ]);
_ => vec![], }
}) }
.collect();
let mut has_pictures = false; let mut has_pictures = false;
for (idx, picture) in tags.pictures().enumerate() { for (idx, picture) in tags.pictures().enumerate() {
@ -99,7 +100,7 @@ impl Extractor for ID3Extractor {
)?; )?;
result.push(Entry { result.push(Entry {
entity: address.clone(), entity: address.clone(),
attribute: "ID3_PICTURE".to_string(), attribute: "ID3_PICTURE".parse()?,
value: EntryValue::Address(Address::Hash(hash)), value: EntryValue::Address(Address::Hash(hash)),
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -108,8 +109,8 @@ impl Extractor for ID3Extractor {
} }
if has_pictures { if has_pictures {
result.push(Entry { result.push(Entry {
entity: Address::Attribute("ID3_PICTURE".to_string()), entity: "ID3_PICTURE".parse()?,
attribute: ATTR_LABEL.into(), attribute: ATTR_LABEL.parse().unwrap(),
value: "ID3 Embedded Image".into(), value: "ID3 Embedded Image".into(),
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -123,9 +124,10 @@ impl Extractor for ID3Extractor {
.filter(|e| e.attribute != ATTR_LABEL) .filter(|e| e.attribute != ATTR_LABEL)
.map(|e| Entry { .map(|e| Entry {
entity: Address::Attribute(e.attribute.clone()), entity: Address::Attribute(e.attribute.clone()),
attribute: ATTR_OF.into(), attribute: ATTR_OF.parse().unwrap(),
value: EntryValue::Address(ID3_TYPE_INVARIANT.entity().unwrap()), value: EntryValue::Address(ID3_TYPE_INVARIANT.entity().unwrap()),
..Default::default() provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(),
}) })
.collect::<Vec<Entry>>(), .collect::<Vec<Entry>>(),
); );
@ -134,9 +136,10 @@ impl Extractor for ID3Extractor {
ID3_TYPE_LABEL.clone(), ID3_TYPE_LABEL.clone(),
Entry { Entry {
entity: address.clone(), entity: address.clone(),
attribute: ATTR_IN.into(), attribute: ATTR_IN.parse().unwrap(),
value: EntryValue::Address(ID3_TYPE_INVARIANT.entity().unwrap()), value: EntryValue::Address(ID3_TYPE_INVARIANT.entity().unwrap()),
..Default::default() provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(),
}, },
]); ]);
} }

View File

@ -3,6 +3,7 @@ use std::sync::Arc;
use super::Extractor; use super::Extractor;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use upend_base::entry::Attribute;
use upend_base::{ use upend_base::{
addressing::Address, addressing::Address,
constants::{ATTR_IN, ATTR_KEY, ATTR_LABEL, ATTR_OF}, constants::{ATTR_IN, ATTR_KEY, ATTR_LABEL, ATTR_OF},
@ -21,14 +22,15 @@ pub struct ExifExtractor;
lazy_static! { lazy_static! {
pub static ref EXIF_TYPE_INVARIANT: InvariantEntry = InvariantEntry { pub static ref EXIF_TYPE_INVARIANT: InvariantEntry = InvariantEntry {
attribute: String::from(ATTR_KEY), attribute: ATTR_KEY.parse().unwrap(),
value: "TYPE_EXIF".into(), value: "TYPE_EXIF".into(),
}; };
pub static ref EXIF_TYPE_LABEL: Entry = Entry { pub static ref EXIF_TYPE_LABEL: Entry = Entry {
entity: EXIF_TYPE_INVARIANT.entity().unwrap(), entity: EXIF_TYPE_INVARIANT.entity().unwrap(),
attribute: ATTR_LABEL.into(), attribute: ATTR_LABEL.parse().unwrap(),
value: "EXIF".into(), value: "EXIF".into(),
..Default::default() provenance: "INVARIANT".to_string(),
timestamp: chrono::Utc::now().naive_utc(),
}; };
} }
@ -63,14 +65,16 @@ impl Extractor for ExifExtractor {
let exifreader = exif::Reader::new(); let exifreader = exif::Reader::new();
let exif = exifreader.read_from_container(&mut bufreader)?; let exif = exifreader.read_from_container(&mut bufreader)?;
let mut result: Vec<Entry> = exif let mut result: Vec<Entry> = vec![];
for field in exif
.fields() .fields()
.filter(|field| !matches!(field.value, exif::Value::Undefined(..))) .filter(|field| !matches!(field.value, exif::Value::Undefined(..)))
.flat_map(|field| { {
if let Some(tag_description) = field.tag.description() { if let Some(tag_description) = field.tag.description() {
let attribute = format!("EXIF_{}", field.tag.1); let attribute: Attribute = format!("EXIF_{}", field.tag.1).parse()?;
vec![ result.extend(vec![
Entry { Entry {
entity: address.clone(), entity: address.clone(),
attribute: attribute.clone(), attribute: attribute.clone(),
@ -78,27 +82,23 @@ impl Extractor for ExifExtractor {
exif::Tag::ExifVersion => { exif::Tag::ExifVersion => {
EntryValue::String(format!("{}", field.display_value())) EntryValue::String(format!("{}", field.display_value()))
} }
_ => EntryValue::guess_from(format!( _ => {
"{}", EntryValue::guess_from(format!("{}", field.display_value()))
field.display_value() }
)),
}, },
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
}, },
Entry { Entry {
entity: Address::Attribute(attribute), entity: Address::Attribute(attribute),
attribute: ATTR_LABEL.into(), attribute: ATTR_LABEL.parse().unwrap(),
value: format!("EXIF: {}", tag_description).into(), value: format!("EXIF: {}", tag_description).into(),
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
}, },
] ]);
} else { }
vec![]
} }
})
.collect();
if !result.is_empty() { if !result.is_empty() {
result.extend( result.extend(
@ -107,9 +107,10 @@ impl Extractor for ExifExtractor {
.filter(|e| e.attribute != ATTR_LABEL) .filter(|e| e.attribute != ATTR_LABEL)
.map(|e| Entry { .map(|e| Entry {
entity: Address::Attribute(e.attribute.clone()), entity: Address::Attribute(e.attribute.clone()),
attribute: ATTR_OF.into(), attribute: ATTR_OF.parse().unwrap(),
value: EntryValue::Address(EXIF_TYPE_INVARIANT.entity().unwrap()), value: EntryValue::Address(EXIF_TYPE_INVARIANT.entity().unwrap()),
..Default::default() provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(),
}) })
.collect::<Vec<Entry>>(), .collect::<Vec<Entry>>(),
); );
@ -120,9 +121,10 @@ impl Extractor for ExifExtractor {
EXIF_TYPE_LABEL.clone(), EXIF_TYPE_LABEL.clone(),
Entry { Entry {
entity: address.clone(), entity: address.clone(),
attribute: ATTR_IN.into(), attribute: ATTR_IN.parse().unwrap(),
value: EntryValue::Address(EXIF_TYPE_INVARIANT.entity().unwrap()), value: EntryValue::Address(EXIF_TYPE_INVARIANT.entity().unwrap()),
..Default::default() provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(),
}, },
]); ]);
} }

View File

@ -18,20 +18,22 @@ const DURATION_KEY: &str = "MEDIA_DURATION";
lazy_static! { lazy_static! {
pub static ref MEDIA_TYPE_INVARIANT: InvariantEntry = InvariantEntry { pub static ref MEDIA_TYPE_INVARIANT: InvariantEntry = InvariantEntry {
attribute: String::from(ATTR_KEY), attribute: ATTR_KEY.parse().unwrap(),
value: "TYPE_MEDIA".into(), value: "TYPE_MEDIA".into(),
}; };
pub static ref MEDIA_TYPE_LABEL: Entry = Entry { pub static ref MEDIA_TYPE_LABEL: Entry = Entry {
entity: MEDIA_TYPE_INVARIANT.entity().unwrap(), entity: MEDIA_TYPE_INVARIANT.entity().unwrap(),
attribute: ATTR_LABEL.into(), attribute: ATTR_LABEL.parse().unwrap(),
value: "Multimedia".into(), value: "Multimedia".into(),
..Default::default() provenance: "INVARIANT".to_string(),
timestamp: chrono::Utc::now().naive_utc(),
}; };
pub static ref DURATION_OF_MEDIA: Entry = Entry { pub static ref DURATION_OF_MEDIA: Entry = Entry {
entity: Address::Attribute(DURATION_KEY.to_string()), entity: DURATION_KEY.parse().unwrap(),
attribute: ATTR_OF.into(), attribute: ATTR_OF.parse().unwrap(),
value: EntryValue::Address(MEDIA_TYPE_INVARIANT.entity().unwrap()), value: EntryValue::Address(MEDIA_TYPE_INVARIANT.entity().unwrap()),
..Default::default() provenance: "INVARIANT".to_string(),
timestamp: chrono::Utc::now().naive_utc(),
}; };
} }
@ -90,7 +92,7 @@ impl Extractor for MediaExtractor {
let result = vec![ let result = vec![
Entry { Entry {
entity: address.clone(), entity: address.clone(),
attribute: DURATION_KEY.to_string(), attribute: DURATION_KEY.parse().unwrap(),
value: EntryValue::Number(duration), value: EntryValue::Number(duration),
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -102,9 +104,10 @@ impl Extractor for MediaExtractor {
DURATION_OF_MEDIA.clone(), DURATION_OF_MEDIA.clone(),
Entry { Entry {
entity: address.clone(), entity: address.clone(),
attribute: ATTR_IN.into(), attribute: ATTR_IN.parse().unwrap(),
value: EntryValue::Address(MEDIA_TYPE_INVARIANT.entity().unwrap()), value: EntryValue::Address(MEDIA_TYPE_INVARIANT.entity().unwrap()),
..Default::default() provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(),
}, },
]; ];

View File

@ -40,21 +40,21 @@ impl Extractor for WebExtractor {
let mut entries = vec![ let mut entries = vec![
html.title.as_ref().map(|html_title| Entry { html.title.as_ref().map(|html_title| Entry {
entity: address.clone(), entity: address.clone(),
attribute: "HTML_TITLE".to_string(), attribute: "HTML_TITLE".parse().unwrap(),
value: html_title.clone().into(), value: html_title.clone().into(),
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
}), }),
html.title.map(|html_title| Entry { html.title.map(|html_title| Entry {
entity: address.clone(), entity: address.clone(),
attribute: ATTR_LABEL.to_string(), attribute: ATTR_LABEL.parse().unwrap(),
value: html_title.into(), value: html_title.into(),
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
}), }),
html.description.map(|html_desc| Entry { html.description.map(|html_desc| Entry {
entity: address.clone(), entity: address.clone(),
attribute: "HTML_DESCRIPTION".to_string(), attribute: "HTML_DESCRIPTION".parse().unwrap(),
value: html_desc.into(), value: html_desc.into(),
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -65,7 +65,7 @@ impl Extractor for WebExtractor {
if attribute == "OG_TITLE" { if attribute == "OG_TITLE" {
entries.push(Some(Entry { entries.push(Some(Entry {
entity: address.clone(), entity: address.clone(),
attribute: ATTR_LABEL.to_string(), attribute: ATTR_LABEL.parse()?,
value: value.clone().into(), value: value.clone().into(),
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -74,7 +74,7 @@ impl Extractor for WebExtractor {
entries.push(Some(Entry { entries.push(Some(Entry {
entity: address.clone(), entity: address.clone(),
attribute, attribute: attribute.parse()?,
value: value.into(), value: value.into(),
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -83,7 +83,7 @@ impl Extractor for WebExtractor {
for image in html.opengraph.images { for image in html.opengraph.images {
entries.push(Some(Entry { entries.push(Some(Entry {
entity: address.clone(), entity: address.clone(),
attribute: "OG_IMAGE".to_string(), attribute: "OG_IMAGE".parse()?,
value: image.url.into(), value: image.url.into(),
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -99,10 +99,10 @@ impl Extractor for WebExtractor {
vec![ vec![
Entry { Entry {
entity: Address::Attribute(e.attribute.clone()), entity: Address::Attribute(e.attribute.clone()),
attribute: ATTR_OF.to_string(), attribute: ATTR_OF.parse().unwrap(),
value: EntryValue::Address(TYPE_URL_ADDRESS.clone()), value: EntryValue::Address(TYPE_URL_ADDRESS.clone()),
provenance: "SYSTEM EXTRACTOR".to_string(), provenance: "SYSTEM EXTRACTOR".to_string(),
..Default::default() timestamp: chrono::Utc::now().naive_utc(),
}, },
e, e,
] ]

View File

@ -47,6 +47,7 @@ use url::Url;
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
use is_executable::IsExecutable; use is_executable::IsExecutable;
use upend_base::error::UpEndError;
#[derive(Clone)] #[derive(Clone)]
pub struct State { pub struct State {
@ -417,7 +418,7 @@ pub async fn put_object(
if let Some(entity) = in_entry.entity { if let Some(entity) = in_entry.entity {
Ok(Entry { Ok(Entry {
entity: entity.try_into()?, entity: entity.try_into()?,
attribute: in_entry.attribute, attribute: in_entry.attribute.parse()?,
value: in_entry.value, value: in_entry.value,
provenance: (match &provenance { provenance: (match &provenance {
Some(s) => format!("API {}", s), Some(s) => format!("API {}", s),
@ -429,7 +430,7 @@ pub async fn put_object(
}) })
} else { } else {
Ok(Entry::try_from(&InvariantEntry { Ok(Entry::try_from(&InvariantEntry {
attribute: in_entry.attribute, attribute: in_entry.attribute.parse()?,
value: in_entry.value, value: in_entry.value,
})?) })?)
} }
@ -481,7 +482,7 @@ pub async fn put_object(
if connection.retrieve_object(&address)?.is_empty() { if connection.retrieve_object(&address)?.is_empty() {
connection.insert_entry(Entry { connection.insert_entry(Entry {
entity: address.clone(), entity: address.clone(),
attribute: ATTR_ADDED.to_string(), attribute: ATTR_ADDED.parse().unwrap(),
value: EntryValue::Number( value: EntryValue::Number(
SystemTime::now() SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
@ -622,7 +623,7 @@ pub async fn put_object_attribute(
let new_attr_entry = Entry { let new_attr_entry = Entry {
entity: address, entity: address,
attribute, attribute: attribute.parse()?,
value: value.into_inner(), value: value.into_inner(),
provenance: (match &query.provenance { provenance: (match &query.provenance {
Some(s) => format!("API {}", s), Some(s) => format!("API {}", s),
@ -688,7 +689,14 @@ pub async fn get_address(
web::Query(query): web::Query<HashMap<String, String>>, web::Query(query): web::Query<HashMap<String, String>>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let (address, immutable) = if let Some(attribute) = query.get("attribute") { let (address, immutable) = if let Some(attribute) = query.get("attribute") {
(Address::Attribute(attribute.into()), true) (
Address::Attribute(
attribute
.parse()
.map_err(|e: UpEndError| ErrorBadRequest(e.to_string()))?,
),
true,
)
} else if let Some(url) = query.get("url") { } else if let Some(url) = query.get("url") {
( (
Address::Url(Url::parse(url).map_err(ErrorBadRequest)?), Address::Url(Url::parse(url).map_err(ErrorBadRequest)?),
@ -1145,7 +1153,7 @@ mod tests {
assert_eq!(result["entity"]["t"], "Hash"); assert_eq!(result["entity"]["t"], "Hash");
assert_eq!(result["entity"]["c"], digest_str); assert_eq!(result["entity"]["c"], digest_str);
let address = Address::Attribute("TEST".to_string()); let address = Address::Attribute("TEST".parse().unwrap());
let req = actix_web::test::TestRequest::get() let req = actix_web::test::TestRequest::get()
.uri(&format!("/api/obj/{}", address)) .uri(&format!("/api/obj/{}", address))
.to_request(); .to_request();

View File

@ -18,7 +18,8 @@ use diesel::{BoxableExpression, QueryDsl};
use diesel::{ExpressionMethods, TextExpressionMethods}; use diesel::{ExpressionMethods, TextExpressionMethods};
use upend_base::addressing::Address; use upend_base::addressing::Address;
use upend_base::entry::{EntryPart, EntryValue}; use upend_base::entry::{EntryPart, EntryValue};
use upend_base::lang::{Attribute, Query, QueryComponent, QueryPart, QueryQualifier}; use upend_base::error::UpEndError;
use upend_base::lang::{Query, QueryComponent, QueryPart, QueryQualifier};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct QueryExecutionError(String); pub struct QueryExecutionError(String);
@ -31,6 +32,12 @@ impl std::fmt::Display for QueryExecutionError {
impl std::error::Error for QueryExecutionError {} impl std::error::Error for QueryExecutionError {}
impl From<UpEndError> for QueryExecutionError {
fn from(e: UpEndError) -> Self {
QueryExecutionError(e.to_string())
}
}
pub fn execute( pub fn execute(
connection: &PooledConnection<ConnectionManager<SqliteConnection>>, connection: &PooledConnection<ConnectionManager<SqliteConnection>>,
query: Query, query: Query,
@ -126,7 +133,7 @@ pub fn execute(
} }
EntryPart::Attribute(a) => { EntryPart::Attribute(a) => {
Some(EntryValue::Address(Address::Attribute( Some(EntryValue::Address(Address::Attribute(
a.0.clone(), a.clone(),
))) )))
} }
EntryPart::Value(v) => Some(v.clone()), EntryPart::Value(v) => Some(v.clone()),
@ -172,9 +179,9 @@ pub fn execute(
subquery_results subquery_results
.iter() .iter()
.map(|e| { .map(|e| {
EntryPart::Attribute(Attribute(e.attribute.clone())) e.attribute.parse().map(|a| EntryPart::Attribute(a))
}) })
.collect(), .collect::<Result<Vec<EntryPart>, _>>()?,
); );
} }
@ -266,10 +273,10 @@ fn to_sqlite_predicates(query: Query) -> Result<SqlResult, QueryExecutionError>
match &eq.attribute { match &eq.attribute {
QueryComponent::Exact(q_attribute) => { QueryComponent::Exact(q_attribute) => {
subqueries.push(Box::new(data::attribute.eq(q_attribute.0.clone()))) subqueries.push(Box::new(data::attribute.eq(q_attribute.to_string())))
} }
QueryComponent::In(q_attributes) => subqueries.push(Box::new( QueryComponent::In(q_attributes) => subqueries.push(Box::new(
data::attribute.eq_any(q_attributes.iter().map(|a| &a.0).cloned()), data::attribute.eq_any(q_attributes.iter().map(|a| a.to_string())),
)), )),
QueryComponent::Contains(q_attribute) => subqueries QueryComponent::Contains(q_attribute) => subqueries
.push(Box::new(data::attribute.like(format!("%{}%", q_attribute)))), .push(Box::new(data::attribute.like(format!("%{}%", q_attribute)))),

View File

@ -1,25 +1,25 @@
use crate::inner::models; use crate::inner::models;
use anyhow::{anyhow, Result};
use std::convert::TryFrom; use std::convert::TryFrom;
use upend_base::addressing::{Address, Addressable}; use upend_base::addressing::{Address, Addressable};
use upend_base::entry::{Entry, EntryValue, ImmutableEntry}; use upend_base::entry::{Entry, EntryValue, ImmutableEntry};
use upend_base::error::UpEndError;
impl TryFrom<&models::Entry> for Entry { impl TryFrom<&models::Entry> for Entry {
type Error = anyhow::Error; type Error = UpEndError;
fn try_from(e: &models::Entry) -> Result<Self, Self::Error> { fn try_from(e: &models::Entry) -> Result<Self, Self::Error> {
if let Some(value_str) = &e.value_str { if let Some(value_str) = &e.value_str {
Ok(Entry { Ok(Entry {
entity: Address::decode(&e.entity)?, entity: Address::decode(&e.entity)?,
attribute: e.attribute.clone(), attribute: e.attribute.parse()?,
value: value_str.parse()?, value: value_str.parse().unwrap(),
provenance: e.provenance.clone(), provenance: e.provenance.clone(),
timestamp: e.timestamp, timestamp: e.timestamp,
}) })
} else if let Some(value_num) = e.value_num { } else if let Some(value_num) = e.value_num {
Ok(Entry { Ok(Entry {
entity: Address::decode(&e.entity)?, entity: Address::decode(&e.entity)?,
attribute: e.attribute.clone(), attribute: e.attribute.parse()?,
value: EntryValue::Number(value_num), value: EntryValue::Number(value_num),
provenance: e.provenance.clone(), provenance: e.provenance.clone(),
timestamp: e.timestamp, timestamp: e.timestamp,
@ -27,7 +27,7 @@ impl TryFrom<&models::Entry> for Entry {
} else { } else {
Ok(Entry { Ok(Entry {
entity: Address::decode(&e.entity)?, entity: Address::decode(&e.entity)?,
attribute: e.attribute.clone(), attribute: e.attribute.parse()?,
value: EntryValue::Number(f64::NAN), value: EntryValue::Number(f64::NAN),
provenance: e.provenance.clone(), provenance: e.provenance.clone(),
timestamp: e.timestamp, timestamp: e.timestamp,
@ -40,18 +40,15 @@ impl TryFrom<&Entry> for models::Entry {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(e: &Entry) -> Result<Self, Self::Error> { fn try_from(e: &Entry) -> Result<Self, Self::Error> {
if e.attribute.is_empty() {
return Err(anyhow!("Attribute cannot be empty."));
}
let base_entry = models::Entry { let base_entry = models::Entry {
identity: e.address()?.encode()?, identity: e.address()?.encode()?,
entity_searchable: match &e.entity { entity_searchable: match &e.entity {
Address::Attribute(attr) => Some(attr.clone()), Address::Attribute(attr) => Some(attr.to_string()),
Address::Url(url) => Some(url.to_string()), Address::Url(url) => Some(url.to_string()),
_ => None, _ => None,
}, },
entity: e.entity.encode()?, entity: e.entity.encode()?,
attribute: e.attribute.clone(), attribute: e.attribute.to_string(),
value_str: None, value_str: None,
value_num: None, value_num: None,
immutable: false, immutable: false,

View File

@ -74,7 +74,7 @@ pub fn list_roots(connection: &UpEndConnection) -> Result<Vec<Address>> {
Ok(connection Ok(connection
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery { .query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None), entity: QueryComponent::Variable(None),
attribute: QueryComponent::Exact(ATTR_IN.into()), attribute: QueryComponent::Exact(ATTR_IN.parse().unwrap()),
value: QueryComponent::Exact((*HIER_ROOT_ADDR).clone().into()), value: QueryComponent::Exact((*HIER_ROOT_ADDR).clone().into()),
})))? })))?
.into_iter() .into_iter()
@ -105,7 +105,7 @@ pub fn fetch_or_create_dir(
let matching_directories = connection let matching_directories = connection
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery { .query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None), entity: QueryComponent::Variable(None),
attribute: QueryComponent::Exact(ATTR_LABEL.into()), attribute: QueryComponent::Exact(ATTR_LABEL.parse().unwrap()),
value: QueryComponent::Exact(directory.to_string().into()), value: QueryComponent::Exact(directory.to_string().into()),
})))? })))?
.into_iter() .into_iter()
@ -115,7 +115,7 @@ pub fn fetch_or_create_dir(
Some(parent) => connection Some(parent) => connection
.query(Query::SingleQuery(QueryPart::Matches(PatternQuery { .query(Query::SingleQuery(QueryPart::Matches(PatternQuery {
entity: QueryComponent::Variable(None), entity: QueryComponent::Variable(None),
attribute: QueryComponent::Exact(ATTR_IN.into()), attribute: QueryComponent::Exact(ATTR_IN.parse().unwrap()),
value: QueryComponent::Exact(parent.into()), value: QueryComponent::Exact(parent.into()),
})))? })))?
.into_iter() .into_iter()
@ -135,7 +135,7 @@ pub fn fetch_or_create_dir(
let directory_entry = Entry { let directory_entry = Entry {
entity: new_directory_address.clone(), entity: new_directory_address.clone(),
attribute: String::from(ATTR_LABEL), attribute: ATTR_LABEL.parse().unwrap(),
value: directory.to_string().into(), value: directory.to_string().into(),
provenance: "SYSTEM FS".to_string(), provenance: "SYSTEM FS".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -145,7 +145,7 @@ pub fn fetch_or_create_dir(
connection.insert_entry(if let Some(parent) = parent { connection.insert_entry(if let Some(parent) = parent {
Entry { Entry {
entity: new_directory_address.clone(), entity: new_directory_address.clone(),
attribute: String::from(ATTR_IN), attribute: ATTR_IN.parse().unwrap(),
value: parent.into(), value: parent.into(),
provenance: "SYSTEM FS".to_string(), provenance: "SYSTEM FS".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -153,7 +153,7 @@ pub fn fetch_or_create_dir(
} else { } else {
Entry { Entry {
entity: new_directory_address.clone(), entity: new_directory_address.clone(),
attribute: String::from(ATTR_IN), attribute: ATTR_IN.parse().unwrap(),
value: HIER_ROOT_ADDR.clone().into(), value: HIER_ROOT_ADDR.clone().into(),
provenance: "SYSTEM FS".to_string(), provenance: "SYSTEM FS".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),

View File

@ -40,7 +40,7 @@ use std::sync::{Arc, Mutex, RwLock};
use std::time::Duration; use std::time::Duration;
use tracing::{debug, error, trace, warn}; use tracing::{debug, error, trace, warn};
use upend_base::addressing::{Address, Addressable}; use upend_base::addressing::{Address, Addressable};
use upend_base::entry::{Entry, EntryValue, ImmutableEntry}; use upend_base::entry::{Attribute, Entry, EntryValue, ImmutableEntry};
use upend_base::error::UpEndError; use upend_base::error::UpEndError;
use upend_base::hash::UpMultihash; use upend_base::hash::UpMultihash;
use upend_base::lang::Query; use upend_base::lang::Query;
@ -308,7 +308,7 @@ impl UpEndConnection {
let entries = primary let entries = primary
.iter() .iter()
.map(Entry::try_from) .map(Entry::try_from)
.collect::<Result<Vec<Entry>>>()?; .collect::<Result<Vec<Entry>, UpEndError>>()?;
let secondary = data let secondary = data
.filter( .filter(
@ -326,7 +326,7 @@ impl UpEndConnection {
let secondary_entries = secondary let secondary_entries = secondary
.iter() .iter()
.map(Entry::try_from) .map(Entry::try_from)
.collect::<Result<Vec<Entry>>>()?; .collect::<Result<Vec<Entry>, UpEndError>>()?;
Ok([entries, secondary_entries].concat()) Ok([entries, secondary_entries].concat())
} }
@ -414,7 +414,7 @@ impl UpEndConnection {
} }
// #[deprecated] // #[deprecated]
pub fn get_all_attributes(&self) -> Result<Vec<String>> { pub fn get_all_attributes(&self) -> Result<Vec<Attribute>> {
use crate::inner::schema::data::dsl::*; use crate::inner::schema::data::dsl::*;
let _lock = self.lock.read().unwrap(); let _lock = self.lock.read().unwrap();
@ -426,7 +426,10 @@ impl UpEndConnection {
.order_by(attribute) .order_by(attribute)
.load::<String>(&conn)?; .load::<String>(&conn)?;
Ok(result) Ok(result
.into_iter()
.map(|a| a.parse())
.collect::<Result<Vec<Attribute>, UpEndError>>()?)
} }
pub fn get_stats(&self) -> Result<serde_json::Value> { pub fn get_stats(&self) -> Result<serde_json::Value> {
@ -478,10 +481,10 @@ impl UpEndConnection {
) )
.load(&conn)?; .load(&conn)?;
result Ok(result
.iter() .iter()
.map(Entry::try_from) .map(Entry::try_from)
.collect::<Result<Vec<Entry>>>() .collect::<Result<Vec<Entry>, UpEndError>>()?)
} }
} }

View File

@ -3,7 +3,7 @@ macro_rules! upend_insert_val {
($db_connection:expr, $entity:expr, $attribute:expr, $value:expr) => {{ ($db_connection:expr, $entity:expr, $attribute:expr, $value:expr) => {{
$db_connection.insert_entry(Entry { $db_connection.insert_entry(Entry {
entity: $entity.clone(), entity: $entity.clone(),
attribute: String::from($attribute), attribute: $attribute.parse().unwrap(),
value: upend_base::entry::EntryValue::String(String::from($value)), value: upend_base::entry::EntryValue::String(String::from($value)),
provenance: "SYSTEM INIT".to_string(), provenance: "SYSTEM INIT".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -16,7 +16,7 @@ macro_rules! upend_insert_addr {
($db_connection:expr, $entity:expr, $attribute:expr, $addr:expr) => {{ ($db_connection:expr, $entity:expr, $attribute:expr, $addr:expr) => {{
$db_connection.insert_entry(Entry { $db_connection.insert_entry(Entry {
entity: $entity.clone(), entity: $entity.clone(),
attribute: String::from($attribute), attribute: $attribute.parse().unwrap(),
value: upend_base::entry::EntryValue::Address($addr.clone()), value: upend_base::entry::EntryValue::Address($addr.clone()),
provenance: "SYSTEM INIT".to_string(), provenance: "SYSTEM INIT".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),

View File

@ -108,13 +108,13 @@ impl FsStore {
trace!("Initializing DB types."); trace!("Initializing DB types.");
upend_insert_addr!( upend_insert_addr!(
upconnection, upconnection,
Address::Attribute(FILE_SIZE_KEY.to_string()), Address::Attribute(FILE_SIZE_KEY.parse().unwrap()),
ATTR_OF, ATTR_OF,
TYPE_HASH_ADDRESS TYPE_HASH_ADDRESS
)?; )?;
upend_insert_addr!( upend_insert_addr!(
upconnection, upconnection,
Address::Attribute(FILE_MIME_KEY.to_string()), Address::Attribute(FILE_MIME_KEY.parse().unwrap()),
ATTR_OF, ATTR_OF,
TYPE_HASH_ADDRESS TYPE_HASH_ADDRESS
)?; )?;
@ -438,7 +438,7 @@ impl FsStore {
// Metadata // Metadata
let size_entry = Entry { let size_entry = Entry {
entity: blob_address.clone(), entity: blob_address.clone(),
attribute: FILE_SIZE_KEY.to_string(), attribute: FILE_SIZE_KEY.parse().unwrap(),
value: (size as f64).into(), value: (size as f64).into(),
provenance: "SYSTEM INIT".to_string(), provenance: "SYSTEM INIT".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -447,7 +447,7 @@ impl FsStore {
let mime_type = tree_magic_mini::from_filepath(path).map(|s| s.to_string()); let mime_type = tree_magic_mini::from_filepath(path).map(|s| s.to_string());
let mime_entry = mime_type.map(|mime_type| Entry { let mime_entry = mime_type.map(|mime_type| Entry {
entity: blob_address.clone(), entity: blob_address.clone(),
attribute: FILE_MIME_KEY.to_string(), attribute: FILE_MIME_KEY.parse().unwrap(),
value: mime_type.into(), value: mime_type.into(),
provenance: "SYSTEM INIT".to_string(), provenance: "SYSTEM INIT".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -455,7 +455,7 @@ impl FsStore {
let added_entry = Entry { let added_entry = Entry {
entity: blob_address.clone(), entity: blob_address.clone(),
attribute: ATTR_ADDED.to_string(), attribute: ATTR_ADDED.parse().unwrap(),
value: (SystemTime::now() value: (SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap() .unwrap()
@ -480,7 +480,7 @@ impl FsStore {
let label_entry = Entry { let label_entry = Entry {
entity: blob_address.clone(), entity: blob_address.clone(),
attribute: ATTR_LABEL.to_string(), attribute: ATTR_LABEL.parse().unwrap(),
value: name value: name
.unwrap_or_else(|| filename.as_os_str().to_string_lossy().to_string()) .unwrap_or_else(|| filename.as_os_str().to_string_lossy().to_string())
.into(), .into(),
@ -498,7 +498,7 @@ impl FsStore {
let dir_has_entry = Entry { let dir_has_entry = Entry {
entity: blob_address.clone(), entity: blob_address.clone(),
attribute: ATTR_IN.to_string(), attribute: ATTR_IN.parse().unwrap(),
value: parent_dir.clone().into(), value: parent_dir.clone().into(),
provenance: "SYSTEM INIT".to_string(), provenance: "SYSTEM INIT".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),
@ -507,7 +507,7 @@ impl FsStore {
let alias_entry = Entry { let alias_entry = Entry {
entity: dir_has_entry_addr, entity: dir_has_entry_addr,
attribute: ATTR_BY.to_string(), attribute: ATTR_BY.parse().unwrap(),
value: label_entry_addr.into(), value: label_entry_addr.into(),
provenance: "SYSTEM INIT".to_string(), provenance: "SYSTEM INIT".to_string(),
timestamp: chrono::Utc::now().naive_utc(), timestamp: chrono::Utc::now().naive_utc(),