1use std::{collections::HashMap, fmt, str::FromStr};
2
3use nom::{
4 branch::alt,
5 bytes::complete::{escaped, tag, take_while, take_while1},
6 character::complete::{alpha1, alphanumeric1, char, digit0, digit1, none_of},
7 combinator::{all_consuming, cut, map, opt, recognize},
8 error::{context, Error},
9 multi::{many0, many1, separated_list0},
10 sequence::{delimited, pair, preceded, terminated, tuple},
11 Finish, IResult,
12};
13
14#[derive(Debug, Default, PartialEq)]
15pub(crate) struct Node {
16 pub(crate) variable: Option<String>,
17 pub(crate) labels: Vec<String>,
18 pub(crate) properties: HashMap<String, CypherValue>,
19}
20
21impl Node {
22 pub fn new(
23 variable: Option<String>,
24 labels: Vec<String>,
25 properties: HashMap<String, CypherValue>,
26 ) -> Self {
27 Self {
28 variable,
29 labels,
30 properties,
31 }
32 }
33}
34
35impl
36 From<(
37 Option<String>,
38 Vec<String>,
39 Option<HashMap<String, CypherValue>>,
40 )> for Node
41{
42 fn from(
43 (variable, labels, properties): (
44 Option<String>,
45 Vec<String>,
46 Option<HashMap<String, CypherValue>>,
47 ),
48 ) -> Self {
49 Node {
50 variable,
51 labels,
52 properties: properties.unwrap_or_default(),
53 }
54 }
55}
56
57impl FromStr for Node {
58 type Err = Error<String>;
59
60 fn from_str(s: &str) -> Result<Self, Self::Err> {
61 match all_consuming(node)(s).finish() {
62 Ok((_remaining, node)) => Ok(node),
63 Err(Error { input, code }) => Err(Error {
64 input: input.to_string(),
65 code,
66 }),
67 }
68 }
69}
70
71#[derive(Debug, Default, PartialEq)]
72pub(crate) struct Relationship {
73 pub(crate) variable: Option<String>,
74 pub(crate) rel_type: Option<String>,
75 pub(crate) direction: Direction,
76 pub(crate) properties: HashMap<String, CypherValue>,
77}
78#[derive(Debug, PartialEq, Eq, Copy, Clone)]
79pub(crate) enum Direction {
80 Outgoing,
81 Incoming,
82}
83
84impl Default for Direction {
85 fn default() -> Self {
86 Direction::Outgoing
87 }
88}
89
90impl Relationship {
91 fn outgoing(
92 variable: Option<String>,
93 rel_type: Option<String>,
94 properties: HashMap<String, CypherValue>,
95 ) -> Self {
96 Relationship {
97 variable,
98 rel_type,
99 direction: Direction::Outgoing,
100 properties,
101 }
102 }
103
104 fn incoming(
105 variable: Option<String>,
106 rel_type: Option<String>,
107 properties: HashMap<String, CypherValue>,
108 ) -> Self {
109 Relationship {
110 variable,
111 rel_type,
112 direction: Direction::Incoming,
113 properties,
114 }
115 }
116}
117
118impl FromStr for Relationship {
119 type Err = Error<String>;
120
121 fn from_str(s: &str) -> Result<Self, Self::Err> {
122 match all_consuming(relationship)(s).finish() {
123 Ok((_remainder, relationship)) => Ok(relationship),
124 Err(Error { input, code }) => Err(Error {
125 input: input.to_string(),
126 code,
127 }),
128 }
129 }
130}
131
132#[derive(Debug, PartialEq, Default)]
133pub(crate) struct Path {
134 pub(crate) start: Node,
135 pub(crate) elements: Vec<(Relationship, Node)>,
136}
137
138impl Path {
139 fn new(start: Node, elements: Vec<(Relationship, Node)>) -> Self {
140 Self { start, elements }
141 }
142}
143
144impl From<(Node, Vec<(Relationship, Node)>)> for Path {
145 fn from((start, elements): (Node, Vec<(Relationship, Node)>)) -> Self {
146 Self { start, elements }
147 }
148}
149
150impl FromStr for Path {
151 type Err = Error<String>;
152
153 fn from_str(s: &str) -> Result<Self, Self::Err> {
154 match all_consuming(path)(s).finish() {
155 Ok((_remainder, path)) => Ok(path),
156 Err(Error { input, code }) => Err(Error {
157 input: input.to_string(),
158 code,
159 }),
160 }
161 }
162}
163
164#[derive(Debug, PartialEq, Default)]
165pub(crate) struct Graph {
166 pub(crate) paths: Vec<Path>,
167}
168
169impl Graph {
170 fn new(paths: Vec<Path>) -> Self {
171 Self { paths }
172 }
173}
174
175impl FromStr for Graph {
176 type Err = Error<String>;
177
178 fn from_str(s: &str) -> Result<Self, Self::Err> {
179 match all_consuming(graph)(s).finish() {
180 Ok((_remainder, graph)) => Ok(graph),
181 Err(Error { input, code }) => Err(Error {
182 input: input.to_string(),
183 code,
184 }),
185 }
186 }
187}
188
189#[derive(Debug, PartialEq)]
190pub enum CypherValue {
191 Float(f64),
192 Integer(i64),
193 String(String),
194 Boolean(bool),
195 List(List),
196}
197
198#[derive(Debug, PartialEq)]
199pub struct List(Vec<CypherValue>);
200
201impl fmt::Display for List {
202 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
203 let values = self
204 .0
205 .iter()
206 .map(ToString::to_string)
207 .collect::<Vec<_>>()
208 .join(", ");
209
210 write!(formatter, "[{}]", values)
211 }
212}
213
214impl From<f64> for CypherValue {
215 fn from(value: f64) -> Self {
216 CypherValue::Float(value)
217 }
218}
219
220impl From<i64> for CypherValue {
221 fn from(value: i64) -> Self {
222 CypherValue::Integer(value)
223 }
224}
225
226impl From<String> for CypherValue {
227 fn from(value: String) -> Self {
228 CypherValue::String(value)
229 }
230}
231
232impl From<&str> for CypherValue {
233 fn from(value: &str) -> Self {
234 CypherValue::String(value.into())
235 }
236}
237
238impl From<bool> for CypherValue {
239 fn from(value: bool) -> Self {
240 CypherValue::Boolean(value)
241 }
242}
243
244impl FromStr for CypherValue {
245 type Err = Error<String>;
246
247 fn from_str(s: &str) -> Result<Self, Self::Err> {
248 match all_consuming(cypher_value)(s).finish() {
249 Ok((_remainder, cypher_value)) => Ok(cypher_value),
250 Err(Error { input, code }) => Err(Error {
251 input: input.to_string(),
252 code,
253 }),
254 }
255 }
256}
257
258impl<T: Into<CypherValue>> From<Vec<T>> for CypherValue {
259 fn from(value: Vec<T>) -> Self {
260 let vector = value.into_iter().map(|entry| entry.into()).collect();
261 CypherValue::List(List(vector))
262 }
263}
264
265impl fmt::Display for CypherValue {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
267 match self {
268 CypherValue::Float(float) => write!(f, "{}", float),
269 CypherValue::Integer(integer) => write!(f, "{}", integer),
270 CypherValue::String(string) => f.pad(string),
271 CypherValue::Boolean(boolean) => write!(f, "{}", boolean),
272 CypherValue::List(list) => write!(f, "{}", list),
273 }
274 }
275}
276
277fn is_uppercase_alphabetic(c: char) -> bool {
278 c.is_alphabetic() && c.is_uppercase()
279}
280
281fn is_valid_label_token(c: char) -> bool {
282 c.is_alphanumeric() || c == '_'
283}
284
285fn is_valid_rel_type_token(c: char) -> bool {
286 is_uppercase_alphabetic(c) || c.is_numeric() || c == '_'
287}
288
289fn sp(input: &str) -> IResult<&str, &str> {
290 take_while(|c: char| c.is_ascii_whitespace())(input)
291}
292
293fn neg_sign(input: &str) -> IResult<&str, bool> {
294 map(opt(tag("-")), |t| t.is_some())(input)
295}
296
297fn integer_literal(input: &str) -> IResult<&str, CypherValue> {
298 map(pair(neg_sign, digit1), |(is_negative, num)| {
299 let mut num = i64::from_str(num).unwrap();
300 if is_negative {
301 num = -num;
302 }
303 CypherValue::from(num)
304 })(input)
305}
306
307fn float_literal(input: &str) -> IResult<&str, CypherValue> {
308 map(
309 pair(neg_sign, recognize(tuple((digit0, tag("."), digit0)))),
310 |(is_negative, num)| {
311 let mut num = f64::from_str(num).unwrap();
312 if is_negative {
313 num = -num;
314 }
315 CypherValue::from(num)
316 },
317 )(input)
318}
319
320fn single_quoted_string(input: &str) -> IResult<&str, &str> {
321 let escaped = escaped(none_of("\\\'"), '\\', tag("'"));
322 let escaped_or_empty = alt((escaped, tag("")));
323 delimited(tag("'"), escaped_or_empty, tag("'"))(input)
324}
325
326fn double_quoted_string(input: &str) -> IResult<&str, &str> {
327 let escaped = escaped(none_of("\\\""), '\\', tag("\""));
328 let escaped_or_empty = alt((escaped, tag("")));
329 delimited(tag("\""), escaped_or_empty, tag("\""))(input)
330}
331
332fn string_literal(input: &str) -> IResult<&str, CypherValue> {
333 map(
334 alt((single_quoted_string, double_quoted_string)),
335 CypherValue::from,
336 )(input)
337}
338
339fn boolean_literal(input: &str) -> IResult<&str, CypherValue> {
340 alt((
341 map(tag("true"), |_| CypherValue::Boolean(true)),
342 map(tag("TRUE"), |_| CypherValue::Boolean(true)),
343 map(tag("false"), |_| CypherValue::Boolean(false)),
344 map(tag("FALSE"), |_| CypherValue::Boolean(false)),
345 ))(input)
346}
347
348fn list_literal(input: &str) -> IResult<&str, CypherValue> {
349 context(
350 "list",
351 preceded(
352 char('['),
353 cut(terminated(
354 map(
355 separated_list0(preceded(sp, char(',')), literal),
356 |vector| CypherValue::List(List(vector)),
357 ),
358 preceded(sp, char(']')),
359 )),
360 ),
361 )(input)
362}
363
364fn literal(input: &str) -> IResult<&str, CypherValue> {
365 preceded(
366 sp,
367 alt((
368 float_literal,
369 integer_literal,
370 string_literal,
371 boolean_literal,
372 )),
373 )(input)
374}
375
376fn cypher_value(input: &str) -> IResult<&str, CypherValue> {
377 preceded(sp, alt((literal, list_literal)))(input)
378}
379
380fn variable(input: &str) -> IResult<&str, String> {
381 map(
382 preceded(
383 sp,
384 recognize(pair(
385 alt((alpha1, tag("_"))),
386 many0(alt((alphanumeric1, tag("_")))),
387 )),
388 ),
389 String::from,
390 )(input)
391}
392
393fn key_value_pair(input: &str) -> IResult<&str, (String, CypherValue)> {
394 pair(
395 variable,
396 preceded(preceded(sp, tag(":")), cut(cypher_value)),
397 )(input)
398}
399
400fn key_value_pairs(input: &str) -> IResult<&str, HashMap<String, CypherValue>> {
401 map(
402 pair(
403 key_value_pair,
404 many0(preceded(preceded(sp, tag(",")), key_value_pair)),
405 ),
406 |(head, tail)| std::iter::once(head).chain(tail).collect::<HashMap<_, _>>(),
407 )(input)
408}
409
410fn properties(input: &str) -> IResult<&str, HashMap<String, CypherValue>> {
411 map(
412 delimited(
413 preceded(sp, tag("{")),
414 opt(key_value_pairs),
415 preceded(sp, tag("}")),
416 ),
417 |properties| properties.unwrap_or_default(),
418 )(input)
419}
420
421fn label(input: &str) -> IResult<&str, String> {
422 map(
423 preceded(
424 tag(":"),
425 cut(recognize(pair(
426 take_while1(is_uppercase_alphabetic),
427 take_while(is_valid_label_token),
428 ))),
429 ),
430 String::from,
431 )(input)
432}
433
434fn rel_type(input: &str) -> IResult<&str, String> {
435 map(
436 preceded(
437 tag(":"),
438 cut(recognize(pair(
439 take_while1(is_uppercase_alphabetic),
440 take_while(is_valid_rel_type_token),
441 ))),
442 ),
443 String::from,
444 )(input)
445}
446
447type NodeBody = (
448 Option<String>, Vec<String>, Option<HashMap<String, CypherValue>>, );
452
453fn node_body(input: &str) -> IResult<&str, NodeBody> {
454 delimited(
455 sp,
456 tuple((opt(variable), many0(label), opt(properties))),
457 sp,
458 )(input)
459}
460
461pub(crate) fn node(input: &str) -> IResult<&str, Node> {
462 map(delimited(tag("("), node_body, tag(")")), Node::from)(input)
463}
464
465type RelationshipBody = (
466 Option<String>, Option<String>, Option<HashMap<String, CypherValue>>, );
470
471fn relationship_body(input: &str) -> IResult<&str, RelationshipBody> {
472 delimited(
473 tag("["),
474 delimited(
475 sp,
476 tuple((opt(variable), opt(rel_type), opt(properties))),
477 sp,
478 ),
479 tag("]"),
480 )(input)
481}
482
483pub(crate) fn relationship(input: &str) -> IResult<&str, Relationship> {
484 alt((
485 map(
486 delimited(tag("-"), opt(relationship_body), tag("->")),
487 |relationship| match relationship {
488 Some((variable, rel_type, properties)) => {
489 Relationship::outgoing(variable, rel_type, properties.unwrap_or_default())
490 }
491 None => Relationship::outgoing(None, None, HashMap::default()),
492 },
493 ),
494 map(
495 delimited(tag("<-"), opt(relationship_body), tag("-")),
496 |relationship| match relationship {
497 Some((variable, rel_type, properties)) => {
498 Relationship::incoming(variable, rel_type, properties.unwrap_or_default())
499 }
500 None => Relationship::incoming(None, None, HashMap::default()),
501 },
502 ),
503 ))(input)
504}
505
506pub(crate) fn path(input: &str) -> IResult<&str, Path> {
507 map(pair(node, many0(pair(relationship, cut(node)))), Path::from)(input)
508}
509
510pub(crate) fn graph(input: &str) -> IResult<&str, Graph> {
511 map(
512 many1(terminated(preceded(sp, path), preceded(sp, opt(tag(","))))),
513 Graph::new,
514 )(input)
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520 use pretty_assertions::assert_eq as pretty_assert_eq;
521 use test_case::test_case;
522
523 impl Node {
524 fn with_variable(variable: impl Into<String>) -> Self {
525 Node {
526 variable: Some(variable.into()),
527 ..Node::default()
528 }
529 }
530
531 fn with_labels<I, T>(labels: I) -> Self
532 where
533 I: IntoIterator<Item = T>,
534 T: Into<String>,
535 {
536 Node {
537 labels: labels.into_iter().map(Into::into).collect(),
538 ..Node::default()
539 }
540 }
541
542 fn with_variable_and_labels<I, T>(variable: impl Into<String>, labels: I) -> Self
543 where
544 I: IntoIterator<Item = T>,
545 T: Into<String>,
546 {
547 Node {
548 variable: Some(variable.into()),
549 labels: labels.into_iter().map(Into::into).collect(),
550 ..Node::default()
551 }
552 }
553
554 fn from<I, T>(
555 variable: impl Into<String>,
556 labels: I,
557 properties: Vec<(T, CypherValue)>,
558 ) -> Self
559 where
560 I: IntoIterator<Item = T>,
561 T: Into<String>,
562 {
563 Node {
564 variable: Some(variable.into()),
565 labels: labels.into_iter().map(Into::into).collect(),
566 properties: properties
567 .into_iter()
568 .map(|(k, v)| (Into::into(k), v))
569 .collect::<HashMap<_, _>>(),
570 }
571 }
572 }
573
574 impl Relationship {
575 fn outgoing_with_variable(variable: impl Into<String>) -> Self {
576 Self::outgoing(Some(variable.into()), None, HashMap::default())
577 }
578
579 fn outgoing_with_variable_and_rel_type(
580 variable: impl Into<String>,
581 rel_type: impl Into<String>,
582 ) -> Self {
583 Self::outgoing(
584 Some(variable.into()),
585 Some(rel_type.into()),
586 HashMap::default(),
587 )
588 }
589
590 fn incoming_with_variable(variable: impl Into<String>) -> Self {
591 Self::incoming(Some(variable.into()), None, HashMap::default())
592 }
593
594 fn incoming_with_variable_and_rel_type(
595 variable: impl Into<String>,
596 rel_type: impl Into<String>,
597 ) -> Self {
598 Self::incoming(
599 Some(variable.into()),
600 Some(rel_type.into()),
601 HashMap::default(),
602 )
603 }
604
605 fn from<I, T>(
606 variable: impl Into<String>,
607 rel_type: T,
608 direction: Direction,
609 properties: Vec<(T, CypherValue)>,
610 ) -> Self
611 where
612 T: Into<String>,
613 {
614 Relationship {
615 variable: Some(variable.into()),
616 rel_type: Some(rel_type.into()),
617 direction,
618 properties: properties
619 .into_iter()
620 .map(|(k, v)| (Into::into(k), v))
621 .collect::<HashMap<_, _>>(),
622 }
623 }
624 }
625
626 #[test_case("0", CypherValue::from(0) ; "int: zero")]
627 #[test_case("-0", CypherValue::from(0) ; "int: signed zero")]
628 #[test_case("42", CypherValue::from(42) ; "int: positive")]
629 #[test_case("-42", CypherValue::from(-42) ; "int: negative")]
630 #[test_case("0.0", CypherValue::from(0.0) ; "float: zero v1")]
631 #[test_case("0.", CypherValue::from(0.0) ; "float: zero v2")]
632 #[test_case(".0", CypherValue::from(0.0) ; "float: zero v3")]
633 #[test_case("-0.0", CypherValue::from(0.0) ; "float: signed zero")]
634 #[test_case("-.0", CypherValue::from(0.0) ; "float: signed zero v2")]
635 #[test_case("13.37", CypherValue::from(13.37) ; "float: positive")]
636 #[test_case("-42.2", CypherValue::from(-42.2) ; "float: negative")]
637 #[test_case("'foobar'", CypherValue::from("foobar") ; "sq string: alpha")]
638 #[test_case("'1234'", CypherValue::from("1234") ; "sq string: numeric")]
639 #[test_case("' '", CypherValue::from(" ") ; "sq string: whitespacec")]
640 #[test_case("''", CypherValue::from("") ; "sq string: empty")]
641 #[test_case(r#"'foobar\'s'"#, CypherValue::from(r#"foobar\'s"#) ; "sq string: escaped")]
642 #[test_case("\"foobar\"", CypherValue::from("foobar") ; "dq string: alpha")]
643 #[test_case("\"1234\"", CypherValue::from("1234") ; "dq string: numeric")]
644 #[test_case("\" \"", CypherValue::from(" ") ; "dq string: whitespacec")]
645 #[test_case("\"\"", CypherValue::from("") ; "dq string: empty")]
646 #[test_case(r#""foobar\"s""#, CypherValue::from(r#"foobar\"s"#) ; "dq string: escaped")]
647 #[test_case("true", CypherValue::from(true) ; "bool: true")]
648 #[test_case("FALSE", CypherValue::from(false) ; "bool: false")]
649 #[test_case("[1, -2, 3]", CypherValue::from(vec![1, -2, 3]) ; "list: [1, -2, 3]")]
650 #[test_case("[1.0, 2.5, .1]", CypherValue::from(vec![1.0, 2.5, 0.1]) ; "list: [1.0, 2.5, 0.1]")]
651 #[test_case("[true, false]", CypherValue::from(vec![true, false]) ; "list: [true, false]")]
652 #[test_case(r#"["ab", "cd"]"#, CypherValue::from(vec!["ab", "cd"]) ; "list: [\"ab\", \"cd\"]")]
653 fn cypher_value(input: &str, expected: CypherValue) {
654 assert_eq!(input.parse(), Ok(expected))
655 }
656
657 #[test]
658 fn cypher_value_from() {
659 assert_eq!(CypherValue::from(42), CypherValue::from(42));
660 assert_eq!(CypherValue::from(13.37), CypherValue::from(13.37));
661 assert_eq!(CypherValue::from("foobar"), CypherValue::from("foobar"));
662 assert_eq!(CypherValue::from(true), CypherValue::from(true));
663 }
664
665 #[test]
666 fn cypher_value_display() {
667 assert_eq!("42", format!("{}", CypherValue::from(42)));
668 assert_eq!("13.37", format!("{}", CypherValue::from(13.37)));
669 assert_eq!("foobar", format!("{}", CypherValue::from("foobar")));
670 assert_eq!("00foobar", format!("{:0>8}", CypherValue::from("foobar")));
671 assert_eq!("true", format!("{}", CypherValue::from(true)));
672 }
673
674 #[test]
675 fn list_display_test() {
676 let list = CypherValue::List(List(vec![
677 CypherValue::Float(13.37),
678 CypherValue::Integer(42),
679 CypherValue::Boolean(true),
680 CypherValue::String(String::from("foobar")),
681 ]));
682
683 assert_eq!(format!("{}", list), "[13.37, 42, true, foobar]");
684 }
685
686 #[test_case("key:42", ("key".to_string(), CypherValue::from(42)))]
687 #[test_case("key: 1337", ("key".to_string(), CypherValue::from(1337)))]
688 #[test_case("key2: 1337", ("key2".to_string(), CypherValue::from(1337)))]
689 #[test_case("key2: 'foobar'", ("key2".to_string(), CypherValue::from("foobar")))]
690 #[test_case("key3: true", ("key3".to_string(), CypherValue::from(true)))]
691 fn key_value_pair_test(input: &str, expected: (String, CypherValue)) {
692 assert_eq!(key_value_pair(input).unwrap().1, expected)
693 }
694
695 #[test_case("{key1: 42}", vec![("key1".to_string(), CypherValue::from(42))])]
696 #[test_case("{key1: 13.37 }", vec![("key1".to_string(), CypherValue::from(13.37))])]
697 #[test_case("{ key1: 42, key2: 1337 }", vec![("key1".to_string(), CypherValue::from(42)), ("key2".to_string(), CypherValue::from(1337))])]
698 #[test_case("{ key1: 42, key2: 1337, key3: 'foobar' }", vec![("key1".to_string(), CypherValue::from(42)), ("key2".to_string(), CypherValue::from(1337)), ("key3".to_string(), CypherValue::from("foobar"))])]
699 fn properties_test(input: &str, expected: Vec<(String, CypherValue)>) {
700 let expected = expected.into_iter().collect::<HashMap<_, _>>();
701 assert_eq!(properties(input).unwrap().1, expected)
702 }
703
704 #[test_case("foobar"; "multiple alphabetical")]
705 #[test_case("_foobar"; "starts with underscore")]
706 #[test_case("__foo_bar"; "mixed underscore")]
707 #[test_case("f"; "single alphabetical lowercase")]
708 #[test_case("F"; "single alphabetical uppercase")]
709 #[test_case("f1234"; "alphanumeric")]
710 fn variable_positive(input: &str) {
711 let result = variable(input);
712 assert!(result.is_ok());
713 let result = result.unwrap().1;
714 assert_eq!(result, input)
715 }
716
717 #[test_case("1234"; "numerical")]
718 #[test_case("+foo"; "special char")]
719 #[test_case("."; "another special char")]
720 fn variable_negative(input: &str) {
721 assert!(variable(input).is_err())
722 }
723
724 #[test_case(":Foobar"; "alphabetical")]
725 #[test_case(":F"; "alphabetical single char")]
726 #[test_case(":F42"; "alphanumerical")]
727 #[test_case(":F_42"; "alphanumerical and underscore")]
728 fn labels_positive(input: &str) {
729 let result = label(input);
730 assert!(result.is_ok());
731 let result = result.unwrap().1;
732 assert_eq!(format!(":{}", result), input)
733 }
734
735 #[test_case(":foobar"; "lower case")]
736 #[test_case(":_"; "colon and single underscore")]
737 #[test_case("_"; "single underscore")]
738 #[test_case(":1234"; "numerical")]
739 fn labels_negative(input: &str) {
740 assert!(label(input).is_err())
741 }
742
743 #[test_case(":FOOBAR"; "multiple alphabetical")]
744 #[test_case(":F"; "single alphabetical")]
745 #[test_case(":F42"; "alphanumerical")]
746 #[test_case(":F_42"; "alphanumerical and underscore")]
747 fn rel_types_positive(input: &str) {
748 let result = rel_type(input);
749 assert!(result.is_ok());
750 let result = result.unwrap().1;
751 assert_eq!(format!(":{}", result), input)
752 }
753
754 #[test_case("()", Node::default(); "empty node")]
755 #[test_case("( )", Node::default(); "empty node with space")]
756 #[test_case("( )", Node::default(); "empty node with many spaces")]
757 #[test_case("(n0)", Node::with_variable("n0"); "n0")]
758 #[test_case("( n0 )", Node::with_variable("n0"); "n0 with space")]
759 #[test_case("(:A)", Node::with_labels(vec!["A"]))]
760 #[test_case("(:A:B)", Node::with_labels(vec!["A", "B"]) )]
761 #[test_case("( :A:B )", Node::with_labels(vec!["A", "B"]); ":A:B with space" )]
762 #[test_case("(n0:A)", Node::with_variable_and_labels("n0", vec!["A"]))]
763 #[test_case("(n0:A:B)", Node::with_variable_and_labels("n0", vec!["A", "B"]))]
764 #[test_case("( n0:A:B )", Node::with_variable_and_labels("n0", vec!["A", "B"]); "n0:A:B with space")]
765 #[test_case("(n0 { foo: 42 })", Node::from("n0", vec![], vec![("foo", CypherValue::from(42))]))]
766 #[test_case("(n0:A:B { foo: 42 })", Node::from("n0", vec!["A", "B"], vec![("foo", CypherValue::from(42))]))]
767 fn node_test(input: &str, expected: Node) {
768 assert_eq!(input.parse(), Ok(expected));
769 }
770
771 #[test_case("(42:A)" ; "numeric variable")]
772 #[test_case("(" ; "no closing")]
773 #[test_case(")" ; "no opening")]
774 fn node_negative(input: &str) {
775 assert!(input.parse::<Node>().is_err())
776 }
777
778 #[test_case("-->", Relationship { direction: Direction::Outgoing, ..Relationship::default()})]
779 #[test_case("-[]->", Relationship { direction: Direction::Outgoing, ..Relationship::default()}; "outgoing: with body")]
780 #[test_case("-[ ]->", Relationship { direction: Direction::Outgoing, ..Relationship::default()}; "outgoing: body with space")]
781 #[test_case("<--", Relationship { direction: Direction::Incoming, ..Relationship::default()}; "incoming: no body")]
782 #[test_case("<-[]-", Relationship { direction: Direction::Incoming, ..Relationship::default()}; "incoming: with body")]
783 #[test_case("<-[ ]-", Relationship { direction: Direction::Incoming, ..Relationship::default()}; "incoming: body with space")]
784 #[test_case("-[r0]->", Relationship { variable: Some("r0".to_string()), direction: Direction::Outgoing, ..Relationship::default() }; "r0 outgoing")]
785 #[test_case("-[ r0 ]->", Relationship { variable: Some("r0".to_string()), direction: Direction::Outgoing, ..Relationship::default() }; "r0 outgoing with space")]
786 #[test_case("<-[r0]-", Relationship { variable: Some("r0".to_string()), direction: Direction::Incoming, ..Relationship::default() }; "r0 incoming")]
787 #[test_case("<-[ r0 ]-", Relationship { variable: Some("r0".to_string()), direction: Direction::Incoming, ..Relationship::default() }; "r0 incoming with space")]
788 #[test_case("-[:BAR]->", Relationship { rel_type: Some("BAR".to_string()), direction: Direction::Outgoing, ..Relationship::default() }; "BAR outgoing")]
789 #[test_case("-[ :BAR ]->", Relationship { rel_type: Some("BAR".to_string()), direction: Direction::Outgoing, ..Relationship::default() }; "BAR outgoing with space")]
790 #[test_case("<-[:BAR]-", Relationship { rel_type: Some("BAR".to_string()), direction: Direction::Incoming, ..Relationship::default() }; "BAR incoming")]
791 #[test_case("<-[ :BAR ]-", Relationship { rel_type: Some("BAR".to_string()), direction: Direction::Incoming, ..Relationship::default() }; "BAR incoming with space")]
792 #[test_case("-[r0:BAR]->", Relationship { variable: Some("r0".to_string()), rel_type: Some("BAR".to_string()), direction: Direction::Outgoing, ..Relationship::default() }; "r0:Bar outgoing")]
793 #[test_case("-[ r0:BAR ]->", Relationship { variable: Some("r0".to_string()), rel_type: Some("BAR".to_string()), direction: Direction::Outgoing, ..Relationship::default()}; "r0:Bar outgoing with space")]
794 #[test_case("<-[r0:BAR]-", Relationship { variable: Some("r0".to_string()), rel_type: Some("BAR".to_string()), direction: Direction::Incoming, ..Relationship::default() }; "r0:Bar incoming")]
795 #[test_case("<-[ r0:BAR ]-", Relationship { variable: Some("r0".to_string()), rel_type: Some("BAR".to_string()), direction: Direction::Incoming, ..Relationship::default() }; "r0:Bar incoming with space")]
796 #[test_case("<-[{ foo: 42 }]-", Relationship { variable: None, rel_type: None, direction: Direction::Incoming, properties: std::iter::once(("foo".to_string(), CypherValue::from(42))).into_iter().collect::<HashMap<_,_>>() }; "with properties")]
797 #[test_case("<-[r0 { foo: 42 }]-", Relationship { variable: Some("r0".to_string()), rel_type: None, direction: Direction::Incoming, properties: std::iter::once(("foo".to_string(), CypherValue::from(42))).into_iter().collect::<HashMap<_,_>>() }; "r0 with properties")]
798 #[test_case("<-[:BAR { foo: 42 }]-", Relationship { variable: None, rel_type: Some("BAR".to_string()), direction: Direction::Incoming, properties: std::iter::once(("foo".to_string(), CypherValue::from(42))).into_iter().collect::<HashMap<_,_>>() }; "Bar with properties")]
799 #[test_case("<-[r0:BAR { foo: 42 }]-", Relationship { variable: Some("r0".to_string()), rel_type: Some("BAR".to_string()), direction: Direction::Incoming, properties: std::iter::once(("foo".to_string(), CypherValue::from(42))).into_iter().collect::<HashMap<_,_>>() }; "r0:Bar with properties")]
800 fn relationship_test(input: &str, expected: Relationship) {
801 assert_eq!(input.parse(), Ok(expected));
802 }
803
804 #[test_case("-[->" ; "outgoing: no closing")]
805 #[test_case("-]->" ; "outgoing: no opening")]
806 #[test_case("->" ; "outgoing: no hyphen")]
807 #[test_case("<-[-" ; "incoming: no closing")]
808 #[test_case("<-]-" ; "incoming: no opening")]
809 #[test_case("<-" ; "incoming: no hyphen")]
810 fn relationship_negative(input: &str) {
811 assert!(input.parse::<Relationship>().is_err());
812 }
813
814 #[test]
815 fn path_node_only() {
816 assert_eq!(
817 "(a)".parse(),
818 Ok(Path {
819 start: Node::with_variable("a"),
820 elements: vec![]
821 })
822 );
823 }
824
825 #[test]
826 fn path_one_hop_path() {
827 assert_eq!(
828 "(a)-->(b)".parse(),
829 Ok(Path {
830 start: Node::with_variable("a"),
831 elements: vec![(
832 Relationship::outgoing(None, None, HashMap::default()),
833 Node::with_variable("b")
834 )]
835 })
836 );
837 }
838
839 #[test]
840 fn path_two_hop_path() {
841 assert_eq!(
842 "(a)-->(b)<--(c)".parse(),
843 Ok(Path {
844 start: Node::with_variable("a"),
845 elements: vec![
846 (
847 Relationship::outgoing(None, None, HashMap::default()),
848 Node::with_variable("b")
849 ),
850 (
851 Relationship::incoming(None, None, HashMap::default()),
852 Node::with_variable("c")
853 ),
854 ]
855 })
856 );
857 }
858
859 #[test]
860 fn path_with_node_labels_and_relationship_types() {
861 assert_eq!(
862 "(a:A)<-[:R]-(:B)-[rel]->(c)-[]->(d:D1:D2)<--(:E1:E2)-[r:REL]->()".parse(),
863 Ok(Path {
864 start: Node::with_variable_and_labels("a", vec!["A"]),
865 elements: vec![
866 (
867 Relationship::incoming(None, Some("R".to_string()), HashMap::default()),
868 Node::new(None, vec!["B".to_string()], HashMap::default())
869 ),
870 (
871 Relationship::outgoing_with_variable("rel"),
872 Node::with_variable("c")
873 ),
874 (
875 Relationship::outgoing(None, None, HashMap::default()),
876 Node::with_variable_and_labels("d", vec!["D1", "D2"])
877 ),
878 (
879 Relationship::incoming(None, None, HashMap::default()),
880 Node::new(
881 None,
882 vec!["E1".to_string(), "E2".to_string()],
883 HashMap::default()
884 )
885 ),
886 (
887 Relationship::outgoing_with_variable_and_rel_type("r", "REL"),
888 Node::default()
889 ),
890 ]
891 })
892 );
893 }
894
895 #[test_case("(42:A)" ; "numeric variable")]
896 #[test_case("(a)-->(42:A)" ; "numeric variable one hop")]
897 fn path_negative(input: &str) {
898 assert!(input.parse::<Path>().is_err())
899 }
900
901 #[test]
902 fn graph_one_paths() {
903 pretty_assert_eq!(
904 "(a)-->(b)".parse(),
905 Ok(Graph::new(vec![Path {
906 start: Node::with_variable("a"),
907 elements: vec![(
908 Relationship::outgoing(None, None, HashMap::default()),
909 Node::with_variable("b")
910 )]
911 }]))
912 );
913 }
914
915 #[test_case("(a)(b)"; "no comma")]
916 #[test_case("(a) (b)"; "one space")]
917 #[test_case("(a) (b)"; "more space")]
918 #[test_case("(a),(b)"; "comma")]
919 #[test_case("(a), (b)"; "comma and space")]
920 #[test_case("(a) ,(b)"; "comma and space in front")]
921 #[test_case("(a) , (b)"; "comma and more space")]
922 #[test_case("(a) , (b)"; "comma and moore space")]
923 #[test_case("(a) , (b),"; "comma and mooore space")]
924 #[test_case("(a)\n(b)"; "new line")]
925 #[test_case("(a),\n(b)"; "new line and comma on same line")]
926 #[test_case("(a)\n,(b)"; "new line and comma on next line")]
927 #[test_case("(a)\n\r,(b)\n\r"; "new line at the end")]
928 fn graph_two_paths(input: &str) {
929 pretty_assert_eq!(
930 input.parse(),
931 Ok(Graph::new(vec![
932 Path {
933 start: Node::with_variable("a"),
934 elements: vec![]
935 },
936 Path {
937 start: Node::with_variable("b"),
938 elements: vec![]
939 }
940 ]))
941 );
942 }
943
944 #[test]
945 fn graph_trailing_comma() {
946 pretty_assert_eq!(
947 "(a),".parse(),
948 Ok(Graph::new(vec![Path {
949 start: Node::with_variable("a"),
950 elements: vec![]
951 }]))
952 );
953 }
954}