gdl/
parser.rs

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>,                       // variable
449    Vec<String>,                          // labels
450    Option<HashMap<String, CypherValue>>, // properties
451);
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>,                       // variable
467    Option<String>,                       // relationship type
468    Option<HashMap<String, CypherValue>>, // properties
469);
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}