use crate::addressing::{Address, Addressable}; use crate::entry::EntryValue; use crate::error::UpEndError; use nonempty::NonEmpty; use std::borrow::Borrow; use std::convert::TryFrom; 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)] pub enum QueryComponent where T: TryFrom, { Exact(T), In(Vec), Contains(String), Variable(Option), } #[derive(Debug, Clone, PartialEq)] pub struct PatternQuery { pub entity: QueryComponent
, pub attribute: QueryComponent, pub value: QueryComponent, } impl TryFrom for Address { type Error = UpEndError; fn try_from(value: lexpr::Value) -> Result { match value { lexpr::Value::Symbol(str) => { if let Some(address_str) = str.strip_prefix('@') { address_str .parse() .map_err(|e: UpEndError| UpEndError::QueryParseError(e.to_string())) } else { Err(UpEndError::QueryParseError( "Incorrect address format (use @address).".into(), )) } } _ => Err(UpEndError::QueryParseError( "Incorrect type for address (use @address).".into(), )), } } } impl TryFrom for EntryValue { type Error = UpEndError; fn try_from(value: lexpr::Value) -> Result { match value { lexpr::Value::Number(num) => Ok(EntryValue::Number(num.as_f64().ok_or_else(|| { UpEndError::QueryParseError(format!("Error processing number ({num:?}).")) })?)), lexpr::Value::Char(chr) => Ok(EntryValue::Address( chr.to_string() .address() .map_err(|e| { UpEndError::QueryParseError(format!( "Error hashing character `{}`: {:}", chr, e )) })? .into(), )), lexpr::Value::String(str) => Ok(EntryValue::Address( str.to_string() .address() .map_err(|e| { UpEndError::QueryParseError(format!( "Error hashing string `{}`: {:}", str, e )) })? .into(), )), lexpr::Value::Symbol(_) => Ok(EntryValue::Address(Address::try_from(value.clone())?)), _ => Err(UpEndError::QueryParseError( "Value can only be a string, number or address.".into(), )), } } } impl TryFrom for Attribute { type Error = UpEndError; fn try_from(value: lexpr::Value) -> Result { match value { lexpr::Value::String(str) => Ok(Attribute(str.to_string())), _ => Err(UpEndError::QueryParseError( "Can only convert to attribute from string.".into(), )), } } } #[derive(Debug, Clone, PartialEq)] pub enum QueryPart { Matches(PatternQuery), Type(String), } #[derive(Debug, Clone, PartialEq)] pub enum QueryQualifier { And, Or, Not, Join, } #[derive(Debug, Clone, PartialEq)] pub struct MultiQuery { pub qualifier: QueryQualifier, pub queries: NonEmpty>, } #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, PartialEq)] pub enum Query { SingleQuery(QueryPart), MultiQuery(MultiQuery), } impl TryFrom<&lexpr::Value> for Query { type Error = UpEndError; fn try_from(expression: &lexpr::Value) -> Result { fn parse_component>( value: &lexpr::Value, ) -> Result, UpEndError> where UpEndError: From<>::Error>, { match value { lexpr::Value::Cons(cons) => { if let lexpr::Value::Symbol(symbol) = cons.car() { match symbol.borrow() { "in" => { let (cons_vec, _) = cons.clone().into_vec(); if let Some(split) = cons_vec.split_first() { let args = split.1; let values: Result, _> = args .iter() .map(|value| T::try_from(value.clone())) .collect(); Ok(QueryComponent::In(values?)) } else { Err(UpEndError::QueryParseError( "Malformed expression: Inner value cannot be empty.".into(), )) } } "contains" => { let (mut cons_vec, _) = cons.clone().into_vec(); match cons_vec.len() { 2 => { let value = cons_vec.remove(1); if let lexpr::Value::String(str) = value { Ok(QueryComponent::Contains(str.into_string())) } else { Err(UpEndError::QueryParseError("Malformed expression: 'Contains' argument must be a string.".into())) } } _ => Err(UpEndError::QueryParseError( "Malformed expression: 'Contains' requires a single argument.".into() )), } } _ => Err(UpEndError::QueryParseError(format!( "Malformed expression: Unknown symbol {}", symbol ))), } } else { Err(UpEndError::QueryParseError(format!( "Malformed expression: Inner value '{:?}' is not a symbol.", value ))) } } lexpr::Value::Symbol(symbol) if symbol.starts_with('?') => { let var_name = symbol.strip_prefix('?').unwrap(); Ok(QueryComponent::Variable(if var_name.is_empty() { None } else { Some(var_name.into()) })) } _ => Ok(QueryComponent::Exact(T::try_from(value.clone())?)), } } if let lexpr::Value::Cons(value) = expression { if let lexpr::Value::Symbol(symbol) = value.car() { match symbol.borrow() { "matches" => { let (cons_vec, _) = value.clone().into_vec(); if let [_, entity, attribute, value] = &cons_vec[..] { let entity = parse_component::
(entity)?; let attribute = parse_component::(attribute)?; let value = parse_component::(value)?; Ok(Query::SingleQuery(QueryPart::Matches(PatternQuery { entity, attribute, value, }))) } else { Err(UpEndError::QueryParseError( "Malformed expression: Wrong number of arguments to 'matches'." .into(), )) } } "type" => { let (cons_vec, _) = value.clone().into_vec(); if let [_, type_name] = &cons_vec[..] { if let lexpr::Value::String(type_name_str) = type_name { Ok(Query::SingleQuery(QueryPart::Type( type_name_str.to_string(), ))) } else { Err(UpEndError::QueryParseError( "Malformed expression: Type must be specified as a string." .into(), )) } } else { Err(UpEndError::QueryParseError( "Malformed expression: Wrong number of arguments to 'type'.".into(), )) } } "and" | "or" | "join" => { let (cons_vec, _) = value.clone().into_vec(); let sub_expressions = &cons_vec[1..]; let values = sub_expressions .iter() .map(|value| Ok(Box::new(Query::try_from(value)?))) .collect::>, UpEndError>>()?; if let Some(queries) = NonEmpty::from_vec(values) { Ok(Query::MultiQuery(MultiQuery { qualifier: match symbol.borrow() { "and" => QueryQualifier::And, "join" => QueryQualifier::Join, _ => QueryQualifier::Or, }, queries, })) } else { Err(UpEndError::QueryParseError( "Malformed expression: sub-query list cannot be empty.".into(), )) } } "not" => { let (cons_vec, _) = value.clone().into_vec(); let sub_expressions = &cons_vec[1..]; let values = sub_expressions .iter() .map(|value| Ok(Box::new(Query::try_from(value)?))) .collect::>, UpEndError>>()?; if values.len() == 1 { Ok(Query::MultiQuery(MultiQuery { qualifier: QueryQualifier::Not, queries: NonEmpty::from_vec(values).unwrap(), })) } else { Err(UpEndError::QueryParseError( "Malformed expression: NOT takes exactly one parameter.".into(), )) } } _ => Err(UpEndError::QueryParseError(format!( "Malformed expression: Unknown symbol '{}'.", symbol ))), } } else { Err(UpEndError::QueryParseError(format!( "Malformed expression: Value '{:?}' is not a symbol.", value ))) } } else { Err(UpEndError::QueryParseError( "Malformed expression: Not a list.".into(), )) } } } impl FromStr for Query { type Err = UpEndError; fn from_str(s: &str) -> Result { let sexp = lexpr::from_str_custom(s, lexpr::parse::Options::new()).map_err(|e| { UpEndError::QueryParseError(format!("failed to parse s-expression: {e}")) })?; Query::try_from(&sexp) } } #[cfg(test)] mod test { use crate::{addressing::Addressable, error::UpEndError}; use super::*; use url::Url; #[test] fn test_matches() -> Result<(), UpEndError> { let query = "(matches ? ? ?)".parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::Variable(None) })) ); let address = Address::Url(Url::parse("https://upend.dev").unwrap()); let query = format!("(matches @{address} ? ?)").parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Exact(address), attribute: QueryComponent::Variable(None), value: QueryComponent::Variable(None) })) ); let query = r#"(matches ? "FOO" ?)"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Exact("FOO".into()), value: QueryComponent::Variable(None) })) ); let value = EntryValue::Number(1337.93); let query = "(matches ? ? 1337.93)".parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::Exact(value) })) ); Ok(()) } #[test] fn test_joins() -> Result<(), UpEndError> { let query = "(matches ?a ?b ?)".parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(Some("a".into())), attribute: QueryComponent::Variable(Some("b".into())), value: QueryComponent::Variable(None) })) ); Ok(()) } #[test] fn test_in_parse() -> Result<(), UpEndError> { let query = r#"(matches ? (in "FOO" "BAR") ?)"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::In(vec!("FOO".into(), "BAR".into())), value: QueryComponent::Variable(None) })) ); let values: Vec = vec![ "FOO".address().unwrap().into(), "BAR".address().unwrap().into(), ]; let query = r#"(matches ? ? (in "FOO" "BAR"))"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::In(values) })) ); let values = vec![EntryValue::Number(1337.93), EntryValue::Number(1968.12)]; let query = r#"(matches ? ? (in 1337.93 1968.12))"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::In(values) })) ); // Invalid queries let values: Vec = vec!["FOO".address().unwrap().into(), EntryValue::Number(1337.93)]; let query = r#"(matches ? ? (in "FOO" 1337.93))"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::In(values) })) ); let values = vec![EntryValue::Number(1337.93), "FOO".address().unwrap().into()]; let query = r#"(matches ? ? (in 1337.93 "FOO"))"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::In(values) })) ); Ok(()) } #[test] fn test_contains() -> Result<(), UpEndError> { let query = r#"(matches (contains "foo") ? ?)"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Contains("foo".to_string()), attribute: QueryComponent::Variable(None), value: QueryComponent::Variable(None) })) ); let query = r#"(matches ? (contains "foo") ?)"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Contains("foo".to_string()), value: QueryComponent::Variable(None), })) ); let query = r#"(matches ? ? (contains "foo"))"#.parse::()?; assert_eq!( query, Query::SingleQuery(QueryPart::Matches(PatternQuery { entity: QueryComponent::Variable(None), attribute: QueryComponent::Variable(None), value: QueryComponent::Contains("foo".to_string()) })) ); Ok(()) } }