1use std::str::FromStr;
2
3use crate::{errors, Edge, Identifier, Json};
4
5use uuid::Uuid;
6
7macro_rules! into_query {
8 ($name:ident, $variant:ident) => {
9 #[allow(clippy::from_over_into)]
11 impl Into<Query> for $name {
12 fn into(self) -> Query {
13 Query::$variant(self)
14 }
15 }
16 };
17}
18
19macro_rules! nestable_query {
20 ($name:ident, $variant:ident) => {
21 impl QueryExt for $name {}
22 impl CountQueryExt for $name {}
23 into_query!($name, $variant);
24 };
25}
26
27#[derive(Eq, PartialEq, Clone, Debug, Hash, Copy)]
36pub enum EdgeDirection {
37 Outbound,
39 Inbound,
41}
42
43impl FromStr for EdgeDirection {
44 type Err = errors::ValidationError;
45
46 fn from_str(s: &str) -> Result<EdgeDirection, Self::Err> {
47 match s {
48 "outbound" => Ok(EdgeDirection::Outbound),
49 "inbound" => Ok(EdgeDirection::Inbound),
50 _ => Err(errors::ValidationError::InvalidValue),
51 }
52 }
53}
54
55impl From<EdgeDirection> for String {
56 fn from(d: EdgeDirection) -> Self {
57 match d {
58 EdgeDirection::Outbound => "outbound".to_string(),
59 EdgeDirection::Inbound => "inbound".to_string(),
60 }
61 }
62}
63
64#[derive(Eq, PartialEq, Clone, Debug)]
66pub enum Query {
67 AllVertex,
69 RangeVertex(RangeVertexQuery),
71 SpecificVertex(SpecificVertexQuery),
73 VertexWithPropertyPresence(VertexWithPropertyPresenceQuery),
75 VertexWithPropertyValue(VertexWithPropertyValueQuery),
77
78 AllEdge,
80 SpecificEdge(SpecificEdgeQuery),
82 EdgeWithPropertyPresence(EdgeWithPropertyPresenceQuery),
84 EdgeWithPropertyValue(EdgeWithPropertyValueQuery),
86
87 Pipe(PipeQuery),
90 PipeProperty(PipePropertyQuery),
92 PipeWithPropertyPresence(PipeWithPropertyPresenceQuery),
94 PipeWithPropertyValue(PipeWithPropertyValueQuery),
96
97 Include(IncludeQuery),
99 Count(CountQuery),
101}
102
103impl Query {
104 pub(crate) fn output_len(&self) -> usize {
108 match self {
109 Query::AllVertex
110 | Query::RangeVertex(_)
111 | Query::SpecificVertex(_)
112 | Query::VertexWithPropertyPresence(_)
113 | Query::VertexWithPropertyValue(_)
114 | Query::AllEdge
115 | Query::SpecificEdge(_)
116 | Query::EdgeWithPropertyPresence(_)
117 | Query::EdgeWithPropertyValue(_)
118 | Query::Count(_) => 1,
119 Query::Pipe(q) => q.inner.output_len(),
120 Query::PipeProperty(q) => q.inner.output_len(),
121 Query::PipeWithPropertyPresence(q) => q.inner.output_len(),
122 Query::PipeWithPropertyValue(q) => q.inner.output_len(),
123 Query::Include(q) => 1 + q.inner.output_len(),
124 }
125 }
126
127 pub(crate) fn output_type(&self) -> errors::ValidationResult<QueryOutputValue> {
131 match self {
132 Query::AllVertex
133 | Query::RangeVertex(_)
134 | Query::SpecificVertex(_)
135 | Query::VertexWithPropertyPresence(_)
136 | Query::VertexWithPropertyValue(_) => Ok(QueryOutputValue::Vertices(Vec::default())),
137 Query::AllEdge
138 | Query::SpecificEdge(_)
139 | Query::EdgeWithPropertyPresence(_)
140 | Query::EdgeWithPropertyValue(_) => Ok(QueryOutputValue::Edges(Vec::default())),
141 Query::Count(_) => Ok(QueryOutputValue::Count(0)),
142 Query::Pipe(q) => q.inner.output_type(),
143 Query::PipeProperty(q) => match q.inner.output_type()? {
144 QueryOutputValue::Vertices(_) => Ok(QueryOutputValue::VertexProperties(Vec::default())),
145 QueryOutputValue::Edges(_) => Ok(QueryOutputValue::EdgeProperties(Vec::default())),
146 _ => Err(errors::ValidationError::InnerQuery),
147 },
148 Query::PipeWithPropertyPresence(q) => q.inner.output_type(),
149 Query::PipeWithPropertyValue(q) => q.inner.output_type(),
150 Query::Include(q) => q.inner.output_type(),
151 }
152 }
153}
154
155pub trait QueryExt: Into<Query> {
157 fn outbound(self) -> errors::ValidationResult<PipeQuery> {
159 PipeQuery::new(Box::new(self.into()), EdgeDirection::Outbound)
160 }
161
162 fn inbound(self) -> errors::ValidationResult<PipeQuery> {
164 PipeQuery::new(Box::new(self.into()), EdgeDirection::Inbound)
165 }
166
167 fn with_property<T: Into<Identifier>>(self, name: T) -> errors::ValidationResult<PipeWithPropertyPresenceQuery> {
172 PipeWithPropertyPresenceQuery::new(Box::new(self.into()), name, true)
173 }
174
175 fn without_property<T: Into<Identifier>>(self, name: T) -> errors::ValidationResult<PipeWithPropertyPresenceQuery> {
180 PipeWithPropertyPresenceQuery::new(Box::new(self.into()), name, false)
181 }
182
183 fn with_property_equal_to<T: Into<Identifier>>(
189 self,
190 name: T,
191 value: Json,
192 ) -> errors::ValidationResult<PipeWithPropertyValueQuery> {
193 PipeWithPropertyValueQuery::new(Box::new(self.into()), name, value, true)
194 }
195
196 fn with_property_not_equal_to<T: Into<Identifier>>(
202 self,
203 name: T,
204 value: Json,
205 ) -> errors::ValidationResult<PipeWithPropertyValueQuery> {
206 PipeWithPropertyValueQuery::new(Box::new(self.into()), name, value, false)
207 }
208
209 fn properties(self) -> errors::ValidationResult<PipePropertyQuery> {
211 PipePropertyQuery::new(Box::new(self.into()))
212 }
213
214 fn include(self) -> IncludeQuery {
216 IncludeQuery::new(Box::new(self.into()))
217 }
218}
219
220pub trait CountQueryExt: Into<Query> {
221 fn count(self) -> errors::ValidationResult<CountQuery> {
223 CountQuery::new(Box::new(self.into()))
224 }
225}
226
227#[derive(Eq, PartialEq, Clone, Debug)]
229pub struct AllVertexQuery;
230
231impl QueryExt for AllVertexQuery {}
232impl CountQueryExt for AllVertexQuery {}
233
234#[allow(clippy::from_over_into)]
236impl Into<Query> for AllVertexQuery {
237 fn into(self) -> Query {
238 Query::AllVertex
239 }
240}
241
242#[derive(Eq, PartialEq, Clone, Debug)]
244pub struct RangeVertexQuery {
245 pub limit: u32,
247
248 pub t: Option<Identifier>,
250
251 pub start_id: Option<Uuid>,
253}
254
255nestable_query!(RangeVertexQuery, RangeVertex);
256
257impl Default for RangeVertexQuery {
258 fn default() -> Self {
259 Self::new()
260 }
261}
262
263impl RangeVertexQuery {
264 pub fn new() -> Self {
266 Self {
267 limit: u32::MAX,
268 t: None,
269 start_id: None,
270 }
271 }
272
273 pub fn limit(self, limit: u32) -> Self {
278 Self {
279 limit,
280 t: self.t,
281 start_id: self.start_id,
282 }
283 }
284
285 pub fn t(self, t: Identifier) -> Self {
290 Self {
291 limit: self.limit,
292 t: Some(t),
293 start_id: self.start_id,
294 }
295 }
296
297 pub fn start_id(self, start_id: Uuid) -> Self {
302 Self {
303 limit: self.limit,
304 t: self.t,
305 start_id: Some(start_id),
306 }
307 }
308}
309
310#[derive(Eq, PartialEq, Clone, Debug)]
312pub struct SpecificVertexQuery {
313 pub ids: Vec<Uuid>,
315}
316
317nestable_query!(SpecificVertexQuery, SpecificVertex);
318
319impl SpecificVertexQuery {
320 pub fn new(ids: Vec<Uuid>) -> Self {
326 Self { ids }
327 }
328
329 pub fn single(id: Uuid) -> Self {
334 Self { ids: vec![id] }
335 }
336}
337
338#[derive(Eq, PartialEq, Clone, Debug)]
340pub struct VertexWithPropertyPresenceQuery {
341 pub name: Identifier,
343}
344
345nestable_query!(VertexWithPropertyPresenceQuery, VertexWithPropertyPresence);
346
347impl VertexWithPropertyPresenceQuery {
348 pub fn new<T: Into<Identifier>>(name: T) -> Self {
353 Self { name: name.into() }
354 }
355}
356
357#[derive(Eq, PartialEq, Clone, Debug)]
359pub struct VertexWithPropertyValueQuery {
360 pub name: Identifier,
362 pub value: Json,
364}
365
366nestable_query!(VertexWithPropertyValueQuery, VertexWithPropertyValue);
367
368impl VertexWithPropertyValueQuery {
369 pub fn new<T: Into<Identifier>>(name: T, value: Json) -> Self {
375 Self {
376 name: name.into(),
377 value,
378 }
379 }
380}
381
382#[derive(Eq, PartialEq, Clone, Debug)]
384pub struct AllEdgeQuery;
385
386impl QueryExt for AllEdgeQuery {}
387impl CountQueryExt for AllEdgeQuery {}
388
389#[allow(clippy::from_over_into)]
391impl Into<Query> for AllEdgeQuery {
392 fn into(self) -> Query {
393 Query::AllEdge
394 }
395}
396
397#[derive(Eq, PartialEq, Clone, Debug)]
399pub struct SpecificEdgeQuery {
400 pub edges: Vec<Edge>,
402}
403
404nestable_query!(SpecificEdgeQuery, SpecificEdge);
405
406impl SpecificEdgeQuery {
407 pub fn new(edges: Vec<Edge>) -> Self {
412 Self { edges }
413 }
414
415 pub fn single(edge: Edge) -> Self {
420 Self { edges: vec![edge] }
421 }
422}
423
424#[derive(Eq, PartialEq, Clone, Debug)]
426pub struct EdgeWithPropertyPresenceQuery {
427 pub name: Identifier,
429}
430
431nestable_query!(EdgeWithPropertyPresenceQuery, EdgeWithPropertyPresence);
432
433impl EdgeWithPropertyPresenceQuery {
434 pub fn new<T: Into<Identifier>>(name: T) -> Self {
439 Self { name: name.into() }
440 }
441}
442
443#[derive(Eq, PartialEq, Clone, Debug)]
445pub struct EdgeWithPropertyValueQuery {
446 pub name: Identifier,
448 pub value: Json,
450}
451
452nestable_query!(EdgeWithPropertyValueQuery, EdgeWithPropertyValue);
453
454impl EdgeWithPropertyValueQuery {
455 pub fn new<T: Into<Identifier>>(name: T, value: Json) -> Self {
461 Self {
462 name: name.into(),
463 value,
464 }
465 }
466}
467
468#[derive(Eq, PartialEq, Clone, Debug)]
474pub struct PipeQuery {
475 pub inner: Box<Query>,
477
478 pub direction: EdgeDirection,
480
481 pub limit: u32,
483
484 pub t: Option<Identifier>,
486}
487
488nestable_query!(PipeQuery, Pipe);
489
490impl PipeQuery {
491 pub fn new(inner: Box<Query>, direction: EdgeDirection) -> errors::ValidationResult<Self> {
497 match inner.output_type()? {
498 QueryOutputValue::Vertices(_) | QueryOutputValue::Edges(_) => {}
499 _ => return Err(errors::ValidationError::InnerQuery),
500 }
501
502 Ok(Self {
503 inner,
504 direction,
505 limit: u32::MAX,
506 t: None,
507 })
508 }
509
510 pub fn limit(self, limit: u32) -> Self {
515 Self {
516 inner: self.inner,
517 direction: self.direction,
518 limit,
519 t: self.t,
520 }
521 }
522
523 pub fn t(self, t: Identifier) -> Self {
528 Self {
529 inner: self.inner,
530 direction: self.direction,
531 limit: self.limit,
532 t: Some(t),
533 }
534 }
535}
536
537#[derive(Eq, PartialEq, Clone, Debug)]
539pub struct PipePropertyQuery {
540 pub inner: Box<Query>,
542 pub name: Option<Identifier>,
544}
545
546into_query!(PipePropertyQuery, PipeProperty);
547impl CountQueryExt for PipePropertyQuery {}
548
549impl PipePropertyQuery {
550 pub fn new(inner: Box<Query>) -> errors::ValidationResult<Self> {
555 match inner.output_type()? {
556 QueryOutputValue::Vertices(_) | QueryOutputValue::Edges(_) => {}
557 _ => return Err(errors::ValidationError::InnerQuery),
558 }
559 Ok(Self { inner, name: None })
560 }
561
562 pub fn name(self, name: Identifier) -> Self {
567 Self {
568 inner: self.inner,
569 name: Some(name),
570 }
571 }
572}
573
574#[derive(Eq, PartialEq, Clone, Debug)]
576pub struct PipeWithPropertyPresenceQuery {
577 pub inner: Box<Query>,
579 pub name: Identifier,
581 pub exists: bool,
583}
584
585nestable_query!(PipeWithPropertyPresenceQuery, PipeWithPropertyPresence);
586
587impl PipeWithPropertyPresenceQuery {
588 pub fn new<T: Into<Identifier>>(inner: Box<Query>, name: T, exists: bool) -> errors::ValidationResult<Self> {
595 match inner.output_type()? {
596 QueryOutputValue::Vertices(_) | QueryOutputValue::Edges(_) => {}
597 _ => return Err(errors::ValidationError::InnerQuery),
598 }
599 Ok(Self {
600 inner,
601 name: name.into(),
602 exists,
603 })
604 }
605}
606
607#[derive(Eq, PartialEq, Clone, Debug)]
609pub struct PipeWithPropertyValueQuery {
610 pub inner: Box<Query>,
612 pub name: Identifier,
614 pub value: Json,
616 pub equal: bool,
618}
619
620nestable_query!(PipeWithPropertyValueQuery, PipeWithPropertyValue);
621
622impl PipeWithPropertyValueQuery {
623 pub fn new<T: Into<Identifier>>(
631 inner: Box<Query>,
632 name: T,
633 value: Json,
634 equal: bool,
635 ) -> errors::ValidationResult<Self> {
636 match inner.output_type()? {
637 QueryOutputValue::Vertices(_) | QueryOutputValue::Edges(_) => {}
638 _ => return Err(errors::ValidationError::InnerQuery),
639 }
640 Ok(Self {
641 inner,
642 name: name.into(),
643 value,
644 equal,
645 })
646 }
647}
648
649#[derive(Eq, PartialEq, Clone, Debug)]
665pub struct IncludeQuery {
666 pub inner: Box<Query>,
668}
669
670nestable_query!(IncludeQuery, Include);
671
672impl IncludeQuery {
673 pub fn new(inner: Box<Query>) -> Self {
678 Self { inner }
679 }
680}
681
682#[derive(Eq, PartialEq, Clone, Debug)]
691pub struct CountQuery {
692 pub inner: Box<Query>,
694}
695
696into_query!(CountQuery, Count);
697
698impl CountQuery {
699 pub fn new(inner: Box<Query>) -> errors::ValidationResult<Self> {
704 match inner.output_type()? {
705 QueryOutputValue::Vertices(_)
706 | QueryOutputValue::Edges(_)
707 | QueryOutputValue::VertexProperties(_)
708 | QueryOutputValue::EdgeProperties(_) => {}
709 _ => return Err(errors::ValidationError::InnerQuery),
710 }
711 Ok(Self { inner })
712 }
713}
714
715#[derive(Clone, Debug, PartialEq)]
717pub enum QueryOutputValue {
718 Vertices(Vec<crate::Vertex>),
720 Edges(Vec<crate::Edge>),
722 Count(u64),
724 VertexProperties(Vec<crate::VertexProperties>),
726 EdgeProperties(Vec<crate::EdgeProperties>),
728}
729
730#[cfg(test)]
731mod tests {
732 use crate::{
733 ijson, AllVertexQuery, CountQuery, CountQueryExt, EdgeDirection, Identifier, PipePropertyQuery, PipeQuery,
734 PipeWithPropertyPresenceQuery, PipeWithPropertyValueQuery, Query, ValidationError,
735 };
736 use std::str::FromStr;
737
738 fn expect_inner_query_err<T: core::fmt::Debug>(result: Result<T, ValidationError>) {
739 match result {
740 Err(ValidationError::InnerQuery) => (),
741 _ => assert!(false, "unexpected result: {:?}", result),
742 }
743 }
744
745 #[test]
746 fn should_convert_str_to_edge_direction() {
747 assert_eq!(EdgeDirection::from_str("outbound").unwrap(), EdgeDirection::Outbound);
748 assert_eq!(EdgeDirection::from_str("inbound").unwrap(), EdgeDirection::Inbound);
749 assert!(EdgeDirection::from_str("foo").is_err());
750 }
751
752 #[test]
753 fn should_convert_edge_direction_to_string() {
754 let s: String = EdgeDirection::Outbound.into();
755 assert_eq!(s, "outbound".to_string());
756 let s: String = EdgeDirection::Inbound.into();
757 assert_eq!(s, "inbound".to_string());
758 }
759
760 #[test]
761 fn should_fail_for_nested_count_queries() {
762 let q: Query = AllVertexQuery.count().unwrap().into();
763 expect_inner_query_err(CountQuery::new(Box::new(q.clone())));
764 expect_inner_query_err(PipeQuery::new(Box::new(q.clone()), EdgeDirection::Outbound));
765 expect_inner_query_err(PipePropertyQuery::new(Box::new(q.clone())));
766 expect_inner_query_err(PipeWithPropertyPresenceQuery::new(
767 Box::new(q.clone()),
768 Identifier::new("foo").unwrap(),
769 true,
770 ));
771 expect_inner_query_err(PipeWithPropertyValueQuery::new(
772 Box::new(q.clone()),
773 Identifier::new("foo").unwrap(),
774 ijson!("bar"),
775 true,
776 ));
777 }
778}