use std::str::FromStr;
use crate::{errors, Edge, Identifier, Json};
use uuid::Uuid;
macro_rules! into_query {
($name:ident, $variant:ident) => {
#[allow(clippy::from_over_into)]
impl Into<Query> for $name {
fn into(self) -> Query {
Query::$variant(self)
}
}
};
}
macro_rules! nestable_query {
($name:ident, $variant:ident) => {
impl QueryExt for $name {}
impl CountQueryExt for $name {}
into_query!($name, $variant);
};
}
#[derive(Eq, PartialEq, Clone, Debug, Hash, Copy)]
pub enum EdgeDirection {
Outbound,
Inbound,
}
impl FromStr for EdgeDirection {
type Err = errors::ValidationError;
fn from_str(s: &str) -> Result<EdgeDirection, Self::Err> {
match s {
"outbound" => Ok(EdgeDirection::Outbound),
"inbound" => Ok(EdgeDirection::Inbound),
_ => Err(errors::ValidationError::InvalidValue),
}
}
}
impl From<EdgeDirection> for String {
fn from(d: EdgeDirection) -> Self {
match d {
EdgeDirection::Outbound => "outbound".to_string(),
EdgeDirection::Inbound => "inbound".to_string(),
}
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub enum Query {
AllVertex,
RangeVertex(RangeVertexQuery),
SpecificVertex(SpecificVertexQuery),
VertexWithPropertyPresence(VertexWithPropertyPresenceQuery),
VertexWithPropertyValue(VertexWithPropertyValueQuery),
AllEdge,
SpecificEdge(SpecificEdgeQuery),
EdgeWithPropertyPresence(EdgeWithPropertyPresenceQuery),
EdgeWithPropertyValue(EdgeWithPropertyValueQuery),
Pipe(PipeQuery),
PipeProperty(PipePropertyQuery),
PipeWithPropertyPresence(PipeWithPropertyPresenceQuery),
PipeWithPropertyValue(PipeWithPropertyValueQuery),
Include(IncludeQuery),
Count(CountQuery),
}
impl Query {
pub(crate) fn output_len(&self) -> usize {
match self {
Query::AllVertex
| Query::RangeVertex(_)
| Query::SpecificVertex(_)
| Query::VertexWithPropertyPresence(_)
| Query::VertexWithPropertyValue(_)
| Query::AllEdge
| Query::SpecificEdge(_)
| Query::EdgeWithPropertyPresence(_)
| Query::EdgeWithPropertyValue(_)
| Query::Count(_) => 1,
Query::Pipe(q) => q.inner.output_len(),
Query::PipeProperty(q) => q.inner.output_len(),
Query::PipeWithPropertyPresence(q) => q.inner.output_len(),
Query::PipeWithPropertyValue(q) => q.inner.output_len(),
Query::Include(q) => 1 + q.inner.output_len(),
}
}
pub(crate) fn output_type(&self) -> errors::ValidationResult<QueryOutputValue> {
match self {
Query::AllVertex
| Query::RangeVertex(_)
| Query::SpecificVertex(_)
| Query::VertexWithPropertyPresence(_)
| Query::VertexWithPropertyValue(_) => Ok(QueryOutputValue::Vertices(Vec::default())),
Query::AllEdge
| Query::SpecificEdge(_)
| Query::EdgeWithPropertyPresence(_)
| Query::EdgeWithPropertyValue(_) => Ok(QueryOutputValue::Edges(Vec::default())),
Query::Count(_) => Ok(QueryOutputValue::Count(0)),
Query::Pipe(q) => q.inner.output_type(),
Query::PipeProperty(q) => match q.inner.output_type()? {
QueryOutputValue::Vertices(_) => Ok(QueryOutputValue::VertexProperties(Vec::default())),
QueryOutputValue::Edges(_) => Ok(QueryOutputValue::EdgeProperties(Vec::default())),
_ => Err(errors::ValidationError::InnerQuery),
},
Query::PipeWithPropertyPresence(q) => q.inner.output_type(),
Query::PipeWithPropertyValue(q) => q.inner.output_type(),
Query::Include(q) => q.inner.output_type(),
}
}
}
pub trait QueryExt: Into<Query> {
fn outbound(self) -> errors::ValidationResult<PipeQuery> {
PipeQuery::new(Box::new(self.into()), EdgeDirection::Outbound)
}
fn inbound(self) -> errors::ValidationResult<PipeQuery> {
PipeQuery::new(Box::new(self.into()), EdgeDirection::Inbound)
}
fn with_property<T: Into<Identifier>>(self, name: T) -> errors::ValidationResult<PipeWithPropertyPresenceQuery> {
PipeWithPropertyPresenceQuery::new(Box::new(self.into()), name, true)
}
fn without_property<T: Into<Identifier>>(self, name: T) -> errors::ValidationResult<PipeWithPropertyPresenceQuery> {
PipeWithPropertyPresenceQuery::new(Box::new(self.into()), name, false)
}
fn with_property_equal_to<T: Into<Identifier>>(
self,
name: T,
value: Json,
) -> errors::ValidationResult<PipeWithPropertyValueQuery> {
PipeWithPropertyValueQuery::new(Box::new(self.into()), name, value, true)
}
fn with_property_not_equal_to<T: Into<Identifier>>(
self,
name: T,
value: Json,
) -> errors::ValidationResult<PipeWithPropertyValueQuery> {
PipeWithPropertyValueQuery::new(Box::new(self.into()), name, value, false)
}
fn properties(self) -> errors::ValidationResult<PipePropertyQuery> {
PipePropertyQuery::new(Box::new(self.into()))
}
fn include(self) -> IncludeQuery {
IncludeQuery::new(Box::new(self.into()))
}
}
pub trait CountQueryExt: Into<Query> {
fn count(self) -> errors::ValidationResult<CountQuery> {
CountQuery::new(Box::new(self.into()))
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct AllVertexQuery;
impl QueryExt for AllVertexQuery {}
impl CountQueryExt for AllVertexQuery {}
#[allow(clippy::from_over_into)]
impl Into<Query> for AllVertexQuery {
fn into(self) -> Query {
Query::AllVertex
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct RangeVertexQuery {
pub limit: u32,
pub t: Option<Identifier>,
pub start_id: Option<Uuid>,
}
nestable_query!(RangeVertexQuery, RangeVertex);
impl Default for RangeVertexQuery {
fn default() -> Self {
Self::new()
}
}
impl RangeVertexQuery {
pub fn new() -> Self {
Self {
limit: u32::MAX,
t: None,
start_id: None,
}
}
pub fn limit(self, limit: u32) -> Self {
Self {
limit,
t: self.t,
start_id: self.start_id,
}
}
pub fn t(self, t: Identifier) -> Self {
Self {
limit: self.limit,
t: Some(t),
start_id: self.start_id,
}
}
pub fn start_id(self, start_id: Uuid) -> Self {
Self {
limit: self.limit,
t: self.t,
start_id: Some(start_id),
}
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct SpecificVertexQuery {
pub ids: Vec<Uuid>,
}
nestable_query!(SpecificVertexQuery, SpecificVertex);
impl SpecificVertexQuery {
pub fn new(ids: Vec<Uuid>) -> Self {
Self { ids }
}
pub fn single(id: Uuid) -> Self {
Self { ids: vec![id] }
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct VertexWithPropertyPresenceQuery {
pub name: Identifier,
}
nestable_query!(VertexWithPropertyPresenceQuery, VertexWithPropertyPresence);
impl VertexWithPropertyPresenceQuery {
pub fn new<T: Into<Identifier>>(name: T) -> Self {
Self { name: name.into() }
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct VertexWithPropertyValueQuery {
pub name: Identifier,
pub value: Json,
}
nestable_query!(VertexWithPropertyValueQuery, VertexWithPropertyValue);
impl VertexWithPropertyValueQuery {
pub fn new<T: Into<Identifier>>(name: T, value: Json) -> Self {
Self {
name: name.into(),
value,
}
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct AllEdgeQuery;
impl QueryExt for AllEdgeQuery {}
impl CountQueryExt for AllEdgeQuery {}
#[allow(clippy::from_over_into)]
impl Into<Query> for AllEdgeQuery {
fn into(self) -> Query {
Query::AllEdge
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct SpecificEdgeQuery {
pub edges: Vec<Edge>,
}
nestable_query!(SpecificEdgeQuery, SpecificEdge);
impl SpecificEdgeQuery {
pub fn new(edges: Vec<Edge>) -> Self {
Self { edges }
}
pub fn single(edge: Edge) -> Self {
Self { edges: vec![edge] }
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct EdgeWithPropertyPresenceQuery {
pub name: Identifier,
}
nestable_query!(EdgeWithPropertyPresenceQuery, EdgeWithPropertyPresence);
impl EdgeWithPropertyPresenceQuery {
pub fn new<T: Into<Identifier>>(name: T) -> Self {
Self { name: name.into() }
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct EdgeWithPropertyValueQuery {
pub name: Identifier,
pub value: Json,
}
nestable_query!(EdgeWithPropertyValueQuery, EdgeWithPropertyValue);
impl EdgeWithPropertyValueQuery {
pub fn new<T: Into<Identifier>>(name: T, value: Json) -> Self {
Self {
name: name.into(),
value,
}
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct PipeQuery {
pub inner: Box<Query>,
pub direction: EdgeDirection,
pub limit: u32,
pub t: Option<Identifier>,
}
nestable_query!(PipeQuery, Pipe);
impl PipeQuery {
pub fn new(inner: Box<Query>, direction: EdgeDirection) -> errors::ValidationResult<Self> {
match inner.output_type()? {
QueryOutputValue::Vertices(_) | QueryOutputValue::Edges(_) => {}
_ => return Err(errors::ValidationError::InnerQuery),
}
Ok(Self {
inner,
direction,
limit: u32::MAX,
t: None,
})
}
pub fn limit(self, limit: u32) -> Self {
Self {
inner: self.inner,
direction: self.direction,
limit,
t: self.t,
}
}
pub fn t(self, t: Identifier) -> Self {
Self {
inner: self.inner,
direction: self.direction,
limit: self.limit,
t: Some(t),
}
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct PipePropertyQuery {
pub inner: Box<Query>,
pub name: Option<Identifier>,
}
into_query!(PipePropertyQuery, PipeProperty);
impl CountQueryExt for PipePropertyQuery {}
impl PipePropertyQuery {
pub fn new(inner: Box<Query>) -> errors::ValidationResult<Self> {
match inner.output_type()? {
QueryOutputValue::Vertices(_) | QueryOutputValue::Edges(_) => {}
_ => return Err(errors::ValidationError::InnerQuery),
}
Ok(Self { inner, name: None })
}
pub fn name(self, name: Identifier) -> Self {
Self {
inner: self.inner,
name: Some(name),
}
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct PipeWithPropertyPresenceQuery {
pub inner: Box<Query>,
pub name: Identifier,
pub exists: bool,
}
nestable_query!(PipeWithPropertyPresenceQuery, PipeWithPropertyPresence);
impl PipeWithPropertyPresenceQuery {
pub fn new<T: Into<Identifier>>(inner: Box<Query>, name: T, exists: bool) -> errors::ValidationResult<Self> {
match inner.output_type()? {
QueryOutputValue::Vertices(_) | QueryOutputValue::Edges(_) => {}
_ => return Err(errors::ValidationError::InnerQuery),
}
Ok(Self {
inner,
name: name.into(),
exists,
})
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct PipeWithPropertyValueQuery {
pub inner: Box<Query>,
pub name: Identifier,
pub value: Json,
pub equal: bool,
}
nestable_query!(PipeWithPropertyValueQuery, PipeWithPropertyValue);
impl PipeWithPropertyValueQuery {
pub fn new<T: Into<Identifier>>(
inner: Box<Query>,
name: T,
value: Json,
equal: bool,
) -> errors::ValidationResult<Self> {
match inner.output_type()? {
QueryOutputValue::Vertices(_) | QueryOutputValue::Edges(_) => {}
_ => return Err(errors::ValidationError::InnerQuery),
}
Ok(Self {
inner,
name: name.into(),
value,
equal,
})
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct IncludeQuery {
pub inner: Box<Query>,
}
nestable_query!(IncludeQuery, Include);
impl IncludeQuery {
pub fn new(inner: Box<Query>) -> Self {
Self { inner }
}
}
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct CountQuery {
pub inner: Box<Query>,
}
into_query!(CountQuery, Count);
impl CountQuery {
pub fn new(inner: Box<Query>) -> errors::ValidationResult<Self> {
match inner.output_type()? {
QueryOutputValue::Vertices(_)
| QueryOutputValue::Edges(_)
| QueryOutputValue::VertexProperties(_)
| QueryOutputValue::EdgeProperties(_) => {}
_ => return Err(errors::ValidationError::InnerQuery),
}
Ok(Self { inner })
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum QueryOutputValue {
Vertices(Vec<crate::Vertex>),
Edges(Vec<crate::Edge>),
Count(u64),
VertexProperties(Vec<crate::VertexProperties>),
EdgeProperties(Vec<crate::EdgeProperties>),
}
#[cfg(test)]
mod tests {
use crate::{
ijson, AllVertexQuery, CountQuery, CountQueryExt, EdgeDirection, Identifier, PipePropertyQuery, PipeQuery,
PipeWithPropertyPresenceQuery, PipeWithPropertyValueQuery, Query, ValidationError,
};
use std::str::FromStr;
fn expect_inner_query_err<T: core::fmt::Debug>(result: Result<T, ValidationError>) {
match result {
Err(ValidationError::InnerQuery) => (),
_ => assert!(false, "unexpected result: {:?}", result),
}
}
#[test]
fn should_convert_str_to_edge_direction() {
assert_eq!(EdgeDirection::from_str("outbound").unwrap(), EdgeDirection::Outbound);
assert_eq!(EdgeDirection::from_str("inbound").unwrap(), EdgeDirection::Inbound);
assert!(EdgeDirection::from_str("foo").is_err());
}
#[test]
fn should_convert_edge_direction_to_string() {
let s: String = EdgeDirection::Outbound.into();
assert_eq!(s, "outbound".to_string());
let s: String = EdgeDirection::Inbound.into();
assert_eq!(s, "inbound".to_string());
}
#[test]
fn should_fail_for_nested_count_queries() {
let q: Query = AllVertexQuery.count().unwrap().into();
expect_inner_query_err(CountQuery::new(Box::new(q.clone())));
expect_inner_query_err(PipeQuery::new(Box::new(q.clone()), EdgeDirection::Outbound));
expect_inner_query_err(PipePropertyQuery::new(Box::new(q.clone())));
expect_inner_query_err(PipeWithPropertyPresenceQuery::new(
Box::new(q.clone()),
Identifier::new("foo").unwrap(),
true,
));
expect_inner_query_err(PipeWithPropertyValueQuery::new(
Box::new(q.clone()),
Identifier::new("foo").unwrap(),
ijson!("bar"),
true,
));
}
}