diff --git a/.gitignore b/.gitignore index ace562d..6bec6a2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target/ **/*.rs.bk Cargo.lock .idea +pest diff --git a/Cargo.toml b/Cargo.toml index e86a636..cf2043a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" authors = ["Vincent Prouillet "] [dependencies] -pest = "0.4" +pest = "^1.0.0-beta" +pest_derive = "^1.0.0-beta" diff --git a/src/graphql.pest b/src/graphql.pest new file mode 100644 index 0000000..ee858bb --- /dev/null +++ b/src/graphql.pest @@ -0,0 +1,64 @@ +whitespace = _{ (" " | "\t" | "\r" | "\n") + } + +comment = _{ "#" ~ (!("\n") ~ any)* ~ "\n" } +letters = _{ 'A'..'Z' | 'a'..'z' } +exp = _{ ("e" | "E") ~ ("+" | "-")? ~ '1'..'9'+ } +hex = _{ '0'..'9' | 'a'..'f' | 'A'..'F' } +unicode = _{ "u" ~ hex ~ hex ~ hex ~ hex } +escape = _{ "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t" | unicode) } + +op_true = { "true" } +op_false = { "false" } +boolean = _{ op_true | op_false } +null = { "null" } +int = @{ "-"? ~ ("0" | '1'..'9' ~ '0'..'9'*) } +float = @{ + "-"? ~ + ( + '1'..'9'+ ~ exp | + "0" ~ "." ~ '0'..'9'+ ~ exp? | + '1'..'9' ~ '0'..'9'* ~ "." ~ '0'..'9'+ ~ exp? + ) +} +string = @{ "\"" ~ (escape | !("\"" | "\\") ~ any)* ~ "\"" } +variable = @{ "$" ~ name } +enum_val = @{ !(boolean | null) ~ name } +list = @{ "" ~ value ~ "" } +arg = { name ~ ":" ~ value } +object = { "{" ~ (arg ~ ("," ~ arg)*)? ~ "}" } + +name = @{ ("_" | letters) ~ ("_" | letters | '0'..'9')* } +value = @{ variable | float | int | string | boolean | null | enum_val | list | object } + +// More variables stuff +named_type = { name } +list_type = {"" ~ types ~ ""} +non_null_type = { (named_type | list_type) ~ "!"} +types = { named_type | list_type | non_null_type } +default_value = { "=" ~ value } +variable_def = { variable ~ ":" ~ types ~ default_value? } +variable_defs = { "(" ~ variable_def? ~ ("," ~ variable_def)* ~ ")" } + +// Directive +directive = { "@" ~ name ~ args? } + +// Selections +selection = { field | fragment_spread | fragment_inline } +selection_set = { "{" ~ selection+ ~ "}" } + +// Field +alias = { name ~ ":"} +args = { "(" ~ arg ~ ("," ~ arg)* ~ ","? ~ ")"} +field = { alias? ~ name ~ args? ~ directive? ~ selection_set? } + +// Fragments +fragment_name = { !"on" ~ name } +fragment_def = { "fragment" ~ fragment_name ~ "on" ~ name ~ directive? ~ selection_set } +fragment_spread = @{ "..." ~ fragment_name ~ directive? } +fragment_inline = { "..." ~ ("on" ~ name)? ~ directive? ~ selection_set } + +query = { "query" ~ name? ~ variable_defs? ~ selection_set } +mutation = { "mutation" ~ name? ~ variable_defs? ~ selection_set } +operation = { query | mutation | selection_set } + +document = { soi ~ (operation | fragment_def)+ ~ eoi } diff --git a/src/lib.rs b/src/lib.rs index 1485356..15a22b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,28 +2,24 @@ #![recursion_limit = "300"] -#[macro_use] extern crate pest; +#[macro_use] +extern crate pest_derive; use std::result::Result; use std::collections::HashMap; -use pest::prelude::*; +use pest::Parser; +use pest::iterators::Pair; +use pest::inputs::Input; -/// A type literal in a query -#[derive(Clone, Debug, PartialEq)] -pub enum Type { - /// A type like Int - Named(String), - /// A non-nullable type like Int! - NonNullNamed(String), - /// A nullable type like [Int]. - /// The nullable part is the list, not Int in that example - List(Vec), - /// A non-nullable list like [Int]!, the types inside can be null - NonNullList(Vec), -} +#[cfg(test)] +mod tests; + +// This include forces recompiling this source file if the grammar file changes. +const _GRAMMAR: &'static str = include_str!("graphql.pest"); + /// Input to fields and directives #[derive(Clone, Debug, PartialEq)] @@ -40,19 +36,35 @@ pub enum InputValue { Object, } -#[derive(Clone, Debug, PartialEq)] -pub enum OperationType { - Query, - Mutation, -} - +/// What can be found in a SelectionSet #[derive(Clone, Debug, PartialEq)] pub enum Selection { Field(Box), FragmentSpread(Box), - InlineFragment(Box), + FragmentInline(Box), +} + +pub type SelectionSet = Vec; + +/// A type literal in a query +#[derive(Clone, Debug, PartialEq)] +pub enum Type { + /// A type like Int + Named(String), + /// A non-nullable type like Int! + NonNullNamed(String), + /// A nullable type like [Int]. + /// The nullable part is the list, not Int in that example + List(Vec), + /// A non-nullable list like [Int]!, the types inside can be null + NonNullList(Vec), } +#[derive(Clone, Debug, PartialEq)] +pub enum OperationType { + Query, + Mutation, +} #[derive(Clone, Debug, PartialEq)] pub struct Directive { @@ -67,10 +79,10 @@ pub struct FragmentSpread { } #[derive(Clone, Debug, PartialEq)] -pub struct InlineFragment { +pub struct FragmentInline { pub type_condition: String, pub directives: Vec, - pub selection_set: Selection, + pub selection_set: SelectionSet, } #[derive(Clone, Debug, PartialEq)] @@ -79,7 +91,19 @@ pub struct Field { pub name: String, pub arguments: HashMap, pub directives: Vec, - pub selection_set: Selection, + pub selection_set: SelectionSet, +} + +impl Default for Field { + fn default() -> Self { + Field { + alias: None, + name: String::new(), + arguments: HashMap::new(), + directives: vec![], + selection_set: vec![], + } + } } /// All nodes in GraphQL AST @@ -89,416 +113,100 @@ pub enum Node { Document, } - -impl_rdp! { - grammar! { - whitespace = _{ ([" "] | ["\t"] | ["\r"] | ["\n"]) + } - - comment = _{ ["#"] ~ (!(["\n"]) ~ any)* ~ ["\n"] } - letters = _{ ['A'..'Z'] | ['a'..'z'] } - exp = _{ (["e"] | ["E"]) ~ (["+"] | ["-"])? ~ ['1'..'9']+ } - hex = _{ ['0'..'9'] | ['a'..'f'] | ['A'..'F'] } - unicode = _{ ["u"] ~ hex ~ hex ~ hex ~ hex } - escape = _{ ["\\"] ~ (["\""] | ["\\"] | ["/"] | ["b"] | ["f"] | ["n"] | ["r"] | ["t"] | unicode) } - - op_true = { ["true"] } - op_false = { ["false"] } - boolean = _{ op_true | op_false } - null = { ["null"] } - int = @{ ["-"]? ~ (["0"] | ['1'..'9'] ~ ['0'..'9']*) } - float = @{ - ["-"]? ~ - ( - ['1'..'9']+ ~ exp | - ["0"] ~ ["."] ~ ['0'..'9']+ ~ exp? | - ['1'..'9'] ~ ['0'..'9']* ~ ["."] ~ ['0'..'9']+ ~ exp? - ) - } - string = @{ ["\""] ~ (escape | !(["\""] | ["\\"]) ~ any)* ~ ["\""] } - variable = @{ ["$"] ~ name } - enum_val = @{ !(boolean | null) ~ name } - list = @{ ["["] ~ value ~ ["]"] } - arg = { name ~ [":"] ~ value } - object = { ["{"] ~ (arg ~ ([","] ~ arg)*)? ~ ["}"] } - - name = @{ (["_"] | letters) ~ (["_"] | letters | ['0'..'9'])* } - value = @{ variable | float | int | string | boolean | null | enum_val | list | object } - - // More variables stuff - named_type = { name } - list_type = {["["] ~ types ~ ["]"]} - non_null_type = { (named_type | list_type) ~ ["!"]} - types = { named_type | list_type | non_null_type } - default_value = { ["="] ~ value } - variable_def = { variable ~ [":"] ~ types ~ default_value? } - variable_defs = { ["("] ~ variable_def? ~ ([","] ~ variable_def)* ~ [")"] } - - // Directive - directive = { ["@"] ~ name ~ args? } - - // Selections - selection = { field | fragment_spread | fragment_inline } - selection_set = { ["{"] ~ selection+ ~ ["}"] } - - // Field - alias = { name ~ [":"]} - args = { ["("] ~ arg ~ ([","] ~ arg)* ~ [","]? ~ [")"]} - field = { alias? ~ name ~ args? ~ directive? ~selection_set? } - - // Fragments - fragment_name = { !["on"] ~ name } - fragment_def = { ["fragment"] ~ fragment_name ~ ["on"] ~ name ~ directive? ~ selection_set } - fragment_spread = @{ ["..."] ~ fragment_name ~ directive? } - fragment_inline = { ["..."] ~ (["on"] ~ name)? ~ directive? ~ selection_set } - - query = { ["query"] ~ name? ~ variable_defs? ~ selection_set } - mutation = { ["mutation"] ~ name? ~ variable_defs? ~ selection_set } - operation = { query | mutation | selection_set } - - document = @{ soi ~ (operation | fragment_def)+ ~ eoi } - } +#[derive(Clone, Debug, PartialEq)] +pub struct FragmentDefinition { + name: String, + on: String, + directive: Option, + selection_set: SelectionSet, } -pub fn parse(input: &str) -> Result<(), String> { - let mut parser = Rdp::new(StringInput::new(input)); - - if !parser.document() { - let (_, pos) = parser.expected(); - let (line_no, col_no) = parser.input().line_col(pos); - return Err(format!("Invalid GraphQL syntax at line {}, column {}", line_no, col_no)); - } - -// parser.main() - Ok(()) +#[derive(Clone, Debug, PartialEq)] +pub enum Operation { + SelectionSet(SelectionSet), } +pub type Document = Vec; -#[cfg(test)] -mod tests { - use pest::prelude::*; - use super::Rdp; - - #[test] - fn test_lex_int() { - let input = vec!["123", "-10", "0"]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - assert!(parser.int()); - assert!(parser.end()); - } - } - - #[test] - fn test_lex_float() { - let input = vec![ - "123.1", "-10.1", "0.123", "12.43", - "123e4", "123E4", "123e-4", "123e+4", - "-1.123e4", "-1.123E4", "-1.123e-4", "-1.123e+4", "-1.123e4567", - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - assert!(parser.float()); - assert!(parser.end()); - } - } - - #[test] - fn test_lex_null() { - let mut parser = Rdp::new(StringInput::new("null")); - assert!(parser.null()); - assert!(parser.end()); - } - - #[test] - fn test_lex_boolean() { - let input = vec!["true", "false"]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - assert!(parser.boolean()); - assert!(parser.end()); - } - } - - #[test] - fn test_lex_escape() { - let input = vec![r#"\n"#, r#"\""#]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - assert!(parser.escape()); - assert!(parser.end()); - } - } - - #[test] - fn test_lex_string() { - let input = vec![ - r#""simple""#, r#"" white space ""#, r#""quote \"""#, - r#""escaped \\n\\r\\b\\t\\f""#, r#""slashes \\\\ \\/""#, - r#""unicode \\u1234\\u5678\\u90AB\\uCDEF""#, - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - assert!(parser.string()); - assert!(parser.end()); - } - } - - #[test] - fn test_lex_comment() { - let input = vec!["# a comment \n", "#\n"]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - assert!(parser.comment()); - assert!(parser.end()); - } - } - - #[test] - fn test_lex_name() { - let input = vec![ - "name", "Name", "NAME", "other_name", "othername", - "name12", "__type" - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - assert!(parser.name()); - assert!(parser.end()); - } - } - - #[test] - fn test_lex_object() { - let input = vec![ - "{}", "{ lon: 12.43 }", "{ lon: 12.43, lat: -53.211 }", - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - println!("{:?}", i); - assert!(parser.object()); - assert!(parser.end()); - } - } - - #[test] - fn test_lex_args() { - let input = vec![ - "(size: small)", r#"(size: "small")"#, "(size: SMALL)", - "(size: $size)", "(first: 10, after: 20)", - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - println!("{:?}", i); - assert!(parser.args()); - assert!(parser.end()); - } - } +#[derive(Parser)] +#[grammar = "graphql.pest"] +pub struct GraphQLParser; - #[test] - fn test_lex_field() { - let input = vec![ - "name", "pic: profilePic(size: small)", "newName: name", - "pic: profilePic(size: 140)", "field1(first: 10, after: 20)", - "field1(first: 10, after: 20,)", - "alias: field1(first:10, after:$foo,) @include(if: $foo)" - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - println!("{:?}", i); - assert!(parser.field()); - assert!(parser.end()); - } - } - #[test] - fn test_lex_directive() { - let input = vec![ - "@defer", "@stream", "@live", "@include(if: $foo)", - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - println!("{:?}", i); - assert!(parser.directive()); - assert!(parser.end()); - } +fn parse_field(pair: Pair) -> Field { + let mut field = Field::default(); + for p in pair.into_inner() { + match p.as_rule() { + Rule::alias => field.alias = Some(p.into_span().as_str().to_string()), + Rule::name => field.name = p.into_span().as_str().to_string(), + Rule::args => unreachable!(), + Rule::directive => unreachable!(), + Rule::selection_set => field.selection_set = parse_selection_set(p), + _ => unreachable!() + }; } - #[test] - fn test_lex_selection_set() { - let input = vec![ -r#"{ - id - firstName - lastName -}"#, -r#"{ - me { - id - firstName - lastName - birthday { - month - day - } - friends { - name - } - } -}"#, -r#"{ - user(id: 4) { - name - } -}"#, -r#"{ - user(id: 4) { - id - name - smallPic: profilePic(size: 64) - bigPic: profilePic(size: 1024) - } -}"#, -r#"{ - me { - ...userData - } -}"#, -r#"{ - me { - ... on User { - friends { - count - } - } - } -}"#, -r#"{ - hero(episode: $episode) { - name - friends { - name - } - } -}"#, - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - println!("{:?}", i); - assert!(parser.selection_set()); - assert!(parser.end()); - } - } - - #[test] - fn test_lex_fragment_def() { - let input = vec![ -r#"fragment friendFields on User { - id - name - profilePic(size: 50) -}"#, - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - println!("{:?}", i); - assert!(parser.fragment_def()); - assert!(parser.end()); - } - } + field +} - #[test] - fn test_lex_inline() { - let input = vec![ -r#"... on User { - friends { - count - } -}"#, -r#"... @include(if: $expandedInfo) { - firstName - lastName - birthday -}"#, - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - println!("{:?}", i); - assert!(parser.fragment_inline()); - assert!(parser.end()); - } +fn parse_value(pair: Pair) -> InputValue { + match pair.as_rule() { + Rule::variable => InputValue::Variable(pair.into_span().as_str().to_string()), + Rule::string => InputValue::Variable(pair.into_span().as_str().to_string()), + Rule::enum_val => InputValue::Enum(pair.into_span().as_str().to_string()), + Rule::boolean => { + match pair.into_span().as_str() { + "false" => InputValue::Boolean(false), + "true" => InputValue::Boolean(true), + _ => unreachable!() + } + }, + Rule::float => InputValue::Float(pair.into_span().as_str().parse().unwrap()), + Rule::int => InputValue::Int(pair.into_span().as_str().parse().unwrap()), + Rule::null => InputValue::Null, + + _ => unreachable!("woops"), } +} - #[test] - fn test_lex_fragment_spread() { - let mut parser = Rdp::new(StringInput::new("...userData")); - assert!(parser.fragment_spread()); - assert!(parser.end()); +fn parse_selection(pair: Pair) -> Selection { + match pair.as_rule() { + Rule::field => Selection::Field(Box::new(parse_field(pair))), + Rule::fragment_spread => unreachable!(), + Rule::fragment_inline => unreachable!(), + _ => unreachable!("woops"), } +} - #[test] - fn test_variable_defs() { - let input = vec![ - "()", "($episode: Episode)", "($episode: Episode, $user: Int)", - "($episode: Episode = 1, $user: Int)", - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - println!("{:?}", i); - assert!(parser.variable_defs()); - assert!(parser.end()); - } - } +fn parse_selection_set(pair: Pair) -> SelectionSet { + let selections = pair.into_inner().map(|pos| { + let mut pair = pos.into_inner(); + parse_selection(pair.next().unwrap()) + }); - #[test] - fn test_lex_query() { - let input = vec![ -r#"query HeroNameAndFriends($episode: Episode) { - hero(episode: $episode) { - name - friends { - name - } - } -}"#, -r#"query inlineFragmentNoType($expandedInfo: Boolean) { - user(handle: "zuck") { - id - name - ... @include(if: $expandedInfo) { - firstName - lastName - birthday - } - } -}"#, -//r#"{ -// nearestThing(location: { lon: 12.43, lat: -53.211 }) -//}"#, - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - println!("{:?}", i); - assert!(parser.query()); - assert!(parser.end()); - } - } + selections.collect() +} - #[test] - fn test_lex_mutation() { - let input = vec![ -r#"mutation likeStory { - like(story: 123) { - story { - id - } - } -}"#, - ]; - for i in input { - let mut parser = Rdp::new(StringInput::new(i)); - println!("{:?}", i); - assert!(parser.mutation()); - assert!(parser.end()); - } - } +pub fn parse(input: &str) -> Document { + let pairs = GraphQLParser::parse_str(Rule::document, input).unwrap_or_else(|e| panic!("{}", e)); + + for pair in pairs { + match pair.as_rule() { + Rule::document => { + let content = pair.into_inner().map(|pos| { + let mut pair = pos.into_inner(); + let next_pair = pair.next().unwrap(); + match next_pair.as_rule() { + Rule::selection_set => Operation::SelectionSet(parse_selection_set(next_pair)), + Rule::operation => unreachable!("operation rule"), + _ => unreachable!("unknown doc rule"), + } + }).collect::>(); + return content; + }, + _ => println!("Not handled yet"), + } + } + unreachable!("End of parse") } diff --git a/src/tests/lexing.rs b/src/tests/lexing.rs new file mode 100644 index 0000000..d1d1ff0 --- /dev/null +++ b/src/tests/lexing.rs @@ -0,0 +1,278 @@ +use pest::Parser; + +use ::{GraphQLParser, Rule}; + +#[test] +fn lex_int() { + let input = vec!["123", "-10", "0"]; + for i in input { + GraphQLParser::parse_str(Rule::int, i).unwrap(); + } +} + +#[test] +fn lex_float() { + let input = vec![ + "123.1", "-10.1", "0.123", "12.43", + "123e4", "123E4", "123e-4", "123e+4", + "-1.123e4", "-1.123E4", "-1.123e-4", "-1.123e+4", "-1.123e4567", + ]; + for i in input { + GraphQLParser::parse_str(Rule::float, i).unwrap(); + } +} + +#[test] +fn lex_null() { + GraphQLParser::parse_str(Rule::null, "null").unwrap(); +} + +#[test] +fn lex_boolean() { + let input = vec!["true", "false"]; + for i in input { + GraphQLParser::parse_str(Rule::boolean, i).unwrap(); + } +} + +#[test] +fn lex_escape() { + let input = vec![r#"\n"#, r#"\""#]; + for i in input { + GraphQLParser::parse_str(Rule::escape, i).unwrap(); + } +} + +#[test] +fn lex_string() { + let input = vec![ + r#""simple""#, r#"" white space ""#, r#""quote \"""#, + r#""escaped \\n\\r\\b\\t\\f""#, r#""slashes \\\\ \\/""#, + r#""unicode \\u1234\\u5678\\u90AB\\uCDEF""#, + ]; + for i in input { + GraphQLParser::parse_str(Rule::string, i).unwrap(); + } +} + +#[test] +fn lex_comment() { + let input = vec!["# a comment \n", "#\n"]; + for i in input { + GraphQLParser::parse_str(Rule::comment, i).unwrap(); + } +} + +#[test] +fn lex_name() { + let input = vec![ + "name", "Name", "NAME", "other_name", "othername", + "name12", "__type" + ]; + for i in input { + GraphQLParser::parse_str(Rule::name, i).unwrap(); + } +} + +#[test] +fn lex_object() { + let input = vec![ + "{}", "{ lon: 12.43 }", "{ lon: 12.43, lat: -53.211 }", + ]; + for i in input { + GraphQLParser::parse_str(Rule::object, i).unwrap(); + } +} + +#[test] +fn lex_args() { + let input = vec![ + "(size: small)", r#"(size: "small")"#, "(size: SMALL)", + "(size: $size)", "(first: 10, after: 20)", + ]; + for i in input { + GraphQLParser::parse_str(Rule::args, i).unwrap(); + } +} + +#[test] +fn lex_field() { + let input = vec![ + "name", "pic: profilePic(size: small)", "newName: name", + "pic: profilePic(size: 140)", "field1(first: 10, after: 20)", + "field1(first: 10, after: 20,)", + "alias: field1(first:10, after:$foo,) @include(if: $foo)" + ]; + for i in input { + GraphQLParser::parse_str(Rule::field, i).unwrap(); + } +} + +#[test] +fn lex_directive() { + let input = vec![ + "@defer", "@stream", "@live", "@include(if: $foo)", + ]; + for i in input { + GraphQLParser::parse_str(Rule::directive, i).unwrap(); + } +} + +#[test] +fn lex_selection_set() { + let input = vec![ + r#"{ + id + firstName + lastName +}"#, + r#"{ + me { + id + firstName + lastName + birthday { + month + day + } + friends { + name + } + } +}"#, + r#"{ + user(id: 4) { + name + } +}"#, + r#"{ + user(id: 4) { + id + name + smallPic: profilePic(size: 64) + bigPic: profilePic(size: 1024) + } +}"#, + r#"{ + me { + ...userData + } +}"#, + r#"{ + me { + ... on User { + friends { + count + } + } + } +}"#, + r#"{ + hero(episode: $episode) { + name + friends { + name + } + } +}"#, + ]; + for i in input { + GraphQLParser::parse_str(Rule::selection_set, i).unwrap(); + } +} + +#[test] +fn lex_fragment_def() { + let input = vec![ + r#"fragment friendFields on User { + id + name + profilePic(size: 50) +}"#, + ]; + for i in input { + GraphQLParser::parse_str(Rule::fragment_def, i).unwrap(); + } +} + +#[test] +fn lex_fragment_inline() { + let input = vec![ + r#"... on User { + friends { + count + } +}"#, + r#"... @include(if: $expandedInfo) { + firstName + lastName + birthday +}"#, + ]; + for i in input { + GraphQLParser::parse_str(Rule::fragment_inline, i).unwrap(); + } +} + +#[test] +fn lex_fragment_spread() { + GraphQLParser::parse_str(Rule::fragment_spread, "...userData").unwrap(); +} + +#[test] +fn variable_defs() { + let input = vec![ + "()", "($episode: Episode)", "($episode: Episode, $user: Int)", + "($episode: Episode = 1, $user: Int)", + ]; + for i in input { + GraphQLParser::parse_str(Rule::variable_defs, i).unwrap(); + } +} + +#[test] +fn lex_query() { + let input = vec![ + r#"query HeroNameAndFriends($episode: Episode) { + hero(episode: $episode) { + name + friends { + name + } + } +}"#, + r#"query inlineFragmentNoType($expandedInfo: Boolean) { + user(handle: "zuck") { + id + name + ... @include(if: $expandedInfo) { + firstName + lastName + birthday + } + } +}"#, + //r#"{ + // nearestThing(location: { lon: 12.43, lat: -53.211 }) + //}"#, + ]; + for i in input { + GraphQLParser::parse_str(Rule::query, i).unwrap(); + } +} + +#[test] +fn lex_mutation() { + let input = vec![ + r#"mutation likeStory { + like(story: 123) { + story { + id + } + } +}"#, + ]; + for i in input { + GraphQLParser::parse_str(Rule::mutation, i).unwrap(); + } +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..0c1248f --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,2 @@ +mod lexing; +mod parsing; diff --git a/src/tests/parsing.rs b/src/tests/parsing.rs new file mode 100644 index 0000000..b4af717 --- /dev/null +++ b/src/tests/parsing.rs @@ -0,0 +1,13 @@ +use ::{parse}; + + +#[test] +fn can_parse_simple_doc() { + let res = parse(r#"{ + id + firstName + lastName +}"#); + println!("{:#?}", res); + assert!(false); +} diff --git a/tests/parser.rs b/tests/parser.rs deleted file mode 100644 index e69de29..0000000