From 6c7fb0dff7fc57eafb7a1acc19e9caaace2262ba Mon Sep 17 00:00:00 2001 From: sunli Date: Fri, 15 May 2020 22:27:38 +0800 Subject: [PATCH] Schema parser has been implemented. --- async-graphql-parser/src/error.rs | 3 + async-graphql-parser/src/lib.rs | 6 +- async-graphql-parser/src/query.pest | 7 +- async-graphql-parser/src/query_parser.rs | 23 +- async-graphql-parser/src/schema.pest | 49 +- async-graphql-parser/src/schema.rs | 40 +- async-graphql-parser/src/schema_parser.rs | 761 ++++++++++++++++++ async-graphql-parser/src/utils.rs | 11 +- .../tests/queries/multiline_string.graphql | 7 + feature-comparison.md | 4 +- 10 files changed, 871 insertions(+), 40 deletions(-) create mode 100644 async-graphql-parser/src/schema_parser.rs create mode 100644 async-graphql-parser/tests/queries/multiline_string.graphql diff --git a/async-graphql-parser/src/error.rs b/async-graphql-parser/src/error.rs index a7d65418..158e8bf0 100644 --- a/async-graphql-parser/src/error.rs +++ b/async-graphql-parser/src/error.rs @@ -30,3 +30,6 @@ impl From> for Error { } } } + +/// Parser result +pub type Result = std::result::Result; diff --git a/async-graphql-parser/src/lib.rs b/async-graphql-parser/src/lib.rs index ecba8985..359f6085 100644 --- a/async-graphql-parser/src/lib.rs +++ b/async-graphql-parser/src/lib.rs @@ -9,10 +9,12 @@ pub mod schema; mod error; mod pos; mod query_parser; +mod schema_parser; mod utils; mod value; -pub use error::Error; +pub use error::{Error, Result}; pub use pos::{Pos, Positioned}; -pub use query_parser::{parse_query, parse_value, ParsedValue, Result}; +pub use query_parser::{parse_query, parse_value, ParsedValue}; +pub use schema_parser::parse_schema; pub use value::{UploadValue, Value}; diff --git a/async-graphql-parser/src/query.pest b/async-graphql-parser/src/query.pest index 1a444f04..16fa6962 100644 --- a/async-graphql-parser/src/query.pest +++ b/async-graphql-parser/src/query.pest @@ -6,8 +6,11 @@ int = @{ "-"? ~ ("0" | (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*)) } float = @{ "-"? ~ int ~ "." ~ ASCII_DIGIT+ ~ exp? } exp = @{ ("E" | "e") ~ ("+" | "-")? ~ ASCII_DIGIT+ } -string = @{ "\"" ~ string_inner ~ "\"" } -string_inner = @{ (!("\"" | "\\") ~ ANY)* ~ (escape ~ string_inner)? } +singleline_string = @{ "\"" ~ singleline_inner ~ "\"" } +singleline_inner = @{ (!("\"" | "\\") ~ ANY)* ~ (escape ~ singleline_inner)? } +multiline_inner = @{ (!("\"\"\"" | "\\") ~ ANY)* ~ (escape ~ multiline_inner)? } +multiline_string = @{ "\"\"\"" ~ multiline_inner ~ "\"\"\"" } +string = @{ multiline_string | singleline_string } escape = @{ "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t" | unicode) } unicode = @{ "u" ~ ASCII_HEX_DIGIT{4} } diff --git a/async-graphql-parser/src/query_parser.rs b/async-graphql-parser/src/query_parser.rs index bcf3ac8d..5168af7f 100644 --- a/async-graphql-parser/src/query_parser.rs +++ b/async-graphql-parser/src/query_parser.rs @@ -2,7 +2,7 @@ use crate::pos::Positioned; use crate::query::*; use crate::utils::{to_static_str, unquote_string, PositionCalculator}; use crate::value::Value; -use crate::{Error, Pos}; +use crate::Result; use pest::iterators::Pair; use pest::Parser; use std::borrow::Cow; @@ -14,9 +14,6 @@ use std::ops::Deref; #[grammar = "query.pest"] struct QueryParser; -/// Parser result -pub type Result = std::result::Result; - /// Parse a GraphQL query. pub fn parse_query>(input: T) -> Result { let source = input.into(); @@ -291,14 +288,8 @@ fn parse_value2(pair: Pair, pc: &mut PositionCalculator) -> Result Rule::float => Value::Float(pair.as_str().parse().unwrap()), Rule::int => Value::Int(pair.as_str().parse().unwrap()), Rule::string => Value::String({ - let start_pos = pair.as_span().start_pos().line_col(); - unquote_string( - to_static_str(pair.as_str()), - Pos { - line: start_pos.0, - column: start_pos.1, - }, - )? + let pos = pc.step(&pair); + unquote_string(pair.as_str(), pos)? }), Rule::name => Value::Enum(to_static_str(pair.as_str())), Rule::boolean => Value::Boolean(match pair.as_str() { @@ -331,9 +322,7 @@ fn parse_object_value(pair: Pair, pc: &mut PositionCalculator) -> Result { - map.extend(std::iter::once(parse_object_pair(pair, pc)?)); - } + Rule::pair => map.extend(std::iter::once(parse_object_pair(pair, pc)?)), _ => unreachable!(), } } @@ -344,9 +333,7 @@ fn parse_array_value(pair: Pair, pc: &mut PositionCalculator) -> Result { - array.push(parse_value2(pair, pc)?); - } + Rule::value => array.push(parse_value2(pair, pc)?), _ => unreachable!(), } } diff --git a/async-graphql-parser/src/schema.pest b/async-graphql-parser/src/schema.pest index e8268490..16b2d0f7 100644 --- a/async-graphql-parser/src/schema.pest +++ b/async-graphql-parser/src/schema.pest @@ -6,22 +6,24 @@ int = @{ "-"? ~ ("0" | (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*)) } float = @{ "-"? ~ int ~ "." ~ ASCII_DIGIT+ ~ exp? } exp = @{ ("E" | "e") ~ ("+" | "-")? ~ ASCII_DIGIT+ } -string = @{ "\"" ~ string_inner ~ "\"" } -string_inner = @{ (!("\"" | "\\") ~ ANY)* ~ (escape ~ string_inner)? } +singleline_string = @{ "\"" ~ singleline_inner ~ "\"" } +singleline_inner = @{ (!("\"" | "\\") ~ ANY)* ~ (escape ~ singleline_inner)? } +multiline_inner = @{ (!("\"\"\"" | "\\") ~ ANY)* ~ (escape ~ multiline_inner)? } +multiline_string = @{ "\"\"\"" ~ multiline_inner ~ "\"\"\"" } +string = @{ multiline_string | singleline_string } escape = @{ "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t" | unicode) } unicode = @{ "u" ~ ASCII_HEX_DIGIT{4} } boolean = { "true" | "false" } null = { "null" } name = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHA | ASCII_DIGIT | "_")* } -variable = { "$" ~ name } array = { "[" ~ value* ~ "]" } pair = { name ~ ":" ~ value } object = { "{" ~ pair* ~ "}" } -value = { object | array | variable | float | int | string | null | boolean | name } +value = { object | array | float | int | string | null | boolean | name } // type list_type = { "[" ~ type_ ~ "]" } @@ -29,3 +31,42 @@ nonnull_type = { (list_type | name) ~ "!" } type_ = { nonnull_type | list_type | name } // type system +arguments = { "(" ~ pair* ~ ")" } +directive = { "@" ~ name ~ arguments? } +directives = { directive* } +schema_definition = { extend? ~ "schema" ~ directives? ~ "{" ~ root_operation_type_definition* ~ "}" } +operation_type = @{ "query" | "mutation" | "subscription" } +root_operation_type_definition = { operation_type ~ ":" ~ name } +extend = { "extend" } +sclar_type_definition = { string? ~ extend? ~ "scalar" ~ name ~ directives? } +implements_interfaces = { "implements" ~ "&"? ~ name ~ ("&" ~ name)* } +default_value = { "=" ~ value } +input_value_definition = { string? ~ name ~ ":" ~ type_ ~ default_value? ~ directives? } +arguments_definition = { "(" ~ input_value_definition* ~ ")" } +field_definition = { string? ~ name ~ arguments_definition? ~ ":" ~ type_ ~ directives? } +fields_definition = { "{" ~ field_definition* ~ "}" } +object_type_definition = { string? ~ extend? ~ "type" ~ name ~ implements_interfaces? ~ directives? ~ fields_definition? } +interface_type_definition = { string? ~ extend? ~ "interface" ~ name ~ directives? ~ fields_definition? } +union_member_types = { "=" ~ "|"? ~ name ~ ("|" ~ name)* } +union_type_definition = { string? ~ extend? ~ "union" ~ name ~ directives? ~ union_member_types? } +enum_value_definition = { string? ~ name ~ directives? } +enum_values_definition = { "{" ~ enum_value_definition* ~ "}" } +enum_type_definition = { string? ~ extend? ~ "enum" ~ name ~ directives? ~ enum_values_definition? } +input_fields_definition = { "{" ~ input_value_definition* ~ "}" } +input_type_definition = { string? ~ extend? ~ "input" ~ name ~ directives? ~ input_fields_definition? } +executable_directive_location = @{ "QUERY" | "MUTATION" | "SUBSCRIPTION" | "FIELD" | "FRAGMENT_DEFINITION" | "FRAGMENT_SPREAD" | "INLINE_FRAGMENT" } +type_system_directive_location = @{ "SCHEMA" | "SCALAR" | "OBJECT" | "FIELD_DEFINITION" | "ARGUMENT_DEFINITION" | "INTERFACE" | "UNION" | "ENUM" | "ENUM_VALUE" | "INPUT_OBJECT" | "INPUT_FIELD_DEFINITION"} +directive_location = @{ executable_directive_location | type_system_directive_location } +directive_locations = { "|"? ~ directive_location ~ ("|" ~ directive_location)* } +directive_definition = { string? ~ "directive" ~ "@" ~ name ~ arguments_definition ~ "on" ~ directive_locations } +definition = { + schema_definition | + sclar_type_definition | + object_type_definition | + interface_type_definition | + union_type_definition | + enum_type_definition | + input_type_definition | + directive_definition +} +document = { SOI ~ definition+ ~ EOI} diff --git a/async-graphql-parser/src/schema.rs b/async-graphql-parser/src/schema.rs index a54c6df1..c595d195 100644 --- a/async-graphql-parser/src/schema.rs +++ b/async-graphql-parser/src/schema.rs @@ -1,5 +1,18 @@ use crate::pos::Positioned; -use crate::ParsedValue; +use std::collections::BTreeMap; + +#[derive(Clone, Debug)] +#[allow(missing_docs)] +pub enum Value { + Null, + Int(i64), + Float(f64), + String(String), + Boolean(bool), + Enum(String), + List(Vec), + Object(BTreeMap), +} #[derive(Debug, PartialEq)] pub enum Type { @@ -16,16 +29,13 @@ pub struct Document { #[derive(Debug)] pub enum Definition { SchemaDefinition(Positioned), - TypeDefinition { - extend: bool, - description: Positioned, - definition: Positioned, - }, + TypeDefinition(Positioned), DirectiveDefinition(Positioned), } #[derive(Debug)] pub struct SchemaDefinition { + pub extend: bool, pub directives: Vec>, pub query: Option>, pub mutation: Option>, @@ -44,12 +54,16 @@ pub enum TypeDefinition { #[derive(Debug)] pub struct ScalarType { + pub extend: bool, + pub description: Option>, pub name: Positioned, pub directives: Vec>, } #[derive(Debug)] pub struct ObjectType { + pub extend: bool, + pub description: Option>, pub name: Positioned, pub implements_interfaces: Vec>, pub directives: Vec>, @@ -70,12 +84,14 @@ pub struct InputValue { pub description: Option>, pub name: Positioned, pub ty: Positioned, - pub default_value: Option, + pub default_value: Option>, pub directives: Vec>, } #[derive(Debug)] pub struct InterfaceType { + pub extend: bool, + pub description: Option>, pub name: Positioned, pub directives: Vec>, pub fields: Vec>, @@ -83,13 +99,17 @@ pub struct InterfaceType { #[derive(Debug)] pub struct UnionType { + pub extend: bool, + pub description: Option>, pub name: Positioned, pub directives: Vec>, - pub types: Vec>, + pub members: Vec>, } #[derive(Debug)] pub struct EnumType { + pub extend: bool, + pub description: Option>, pub name: Positioned, pub directives: Vec>, pub values: Vec>, @@ -104,6 +124,8 @@ pub struct EnumValue { #[derive(Debug)] pub struct InputObjectType { + pub extend: bool, + pub description: Option>, pub name: Positioned, pub directives: Vec>, pub fields: Vec>, @@ -145,5 +167,5 @@ pub struct DirectiveDefinition { #[derive(Debug)] pub struct Directive { pub name: Positioned, - pub arguments: Vec<(Positioned, Positioned)>, + pub arguments: Vec<(Positioned, Positioned)>, } diff --git a/async-graphql-parser/src/schema_parser.rs b/async-graphql-parser/src/schema_parser.rs new file mode 100644 index 00000000..ca70eb8d --- /dev/null +++ b/async-graphql-parser/src/schema_parser.rs @@ -0,0 +1,761 @@ +use crate::schema::*; +use crate::utils::{unquote_string, PositionCalculator}; +use crate::{Positioned, Result}; +use pest::iterators::Pair; +use pest::Parser; +use std::collections::BTreeMap; + +#[derive(Parser)] +#[grammar = "schema.pest"] +struct SchemaParser; + +/// Parse a GraphQL schema. +pub fn parse_schema>(input: T) -> Result { + let document_pair: Pair = SchemaParser::parse(Rule::document, input.as_ref())? + .next() + .unwrap(); + let mut definitions = Vec::new(); + let mut pc = PositionCalculator::new(input.as_ref()); + + for pair in document_pair.into_inner() { + match pair.as_rule() { + Rule::definition => { + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::schema_definition => { + definitions.push( + parse_schema_definition(pair, &mut pc)? + .pack(Definition::SchemaDefinition), + ); + } + Rule::sclar_type_definition => definitions.push( + parse_scalar_type(pair, &mut pc)? + .pack(TypeDefinition::Scalar) + .pack(Definition::TypeDefinition), + ), + Rule::object_type_definition => definitions.push( + parse_object_type(pair, &mut pc)? + .pack(TypeDefinition::Object) + .pack(Definition::TypeDefinition), + ), + Rule::interface_type_definition => definitions.push( + parse_interface_type(pair, &mut pc)? + .pack(TypeDefinition::Interface) + .pack(Definition::TypeDefinition), + ), + Rule::union_type_definition => definitions.push( + parse_union_type(pair, &mut pc)? + .pack(TypeDefinition::Union) + .pack(Definition::TypeDefinition), + ), + Rule::enum_type_definition => definitions.push( + parse_enum_type(pair, &mut pc)? + .pack(TypeDefinition::Enum) + .pack(Definition::TypeDefinition), + ), + Rule::input_type_definition => definitions.push( + parse_input_type(pair, &mut pc)? + .pack(TypeDefinition::InputObject) + .pack(Definition::TypeDefinition), + ), + Rule::directive_definition => definitions.push( + parse_directive_definition(pair, &mut pc)? + .pack(Definition::DirectiveDefinition), + ), + _ => unreachable!(), + } + } + } + Rule::EOI => {} + _ => unreachable!(), + } + } + + Ok(Document { definitions }) +} + +fn parse_schema_definition( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result> { + let pos = pc.step(&pair); + let mut extend = false; + let mut directives = None; + let mut query = None; + let mut mutation = None; + let mut subscription = None; + + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::extend => extend = true, + Rule::directives => directives = Some(parse_directives(pair, pc)?), + Rule::root_operation_type_definition => { + let mut op_name = None; + let mut ty_name = None; + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::operation_type => op_name = Some(pair.as_str().to_string()), + Rule::name => { + ty_name = + Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) + } + _ => unreachable!(), + } + } + match op_name.as_deref() { + Some("query") => query = ty_name, + Some("mutation") => mutation = ty_name, + Some("subscription") => subscription = ty_name, + _ => unreachable!(), + } + } + _ => unreachable!(), + } + } + + Ok(Positioned::new( + SchemaDefinition { + extend, + directives: directives.unwrap_or_default(), + query, + mutation, + subscription, + }, + pos, + )) +} + +fn parse_directive(pair: Pair, pc: &mut PositionCalculator) -> Result> { + let pos = pc.step(&pair); + let mut name = None; + let mut arguments = None; + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::name => { + let pos = pc.step(&pair); + name = Some(Positioned::new(pair.as_str().to_string(), pos)) + } + Rule::arguments => arguments = Some(parse_arguments(pair, pc)?), + _ => unreachable!(), + } + } + Ok(Positioned::new( + Directive { + name: name.unwrap(), + arguments: arguments.unwrap_or_default(), + }, + pos, + )) +} + +fn parse_directives( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result>> { + let mut directives = Vec::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::directive => directives.push(parse_directive(pair, pc)?), + _ => unreachable!(), + } + } + Ok(directives) +} + +fn parse_arguments( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result, Positioned)>> { + let mut arguments = Vec::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::pair => arguments.extend(std::iter::once(parse_pair(pair, pc)?)), + _ => unreachable!(), + } + } + Ok(arguments) +} + +fn parse_pair( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result<(Positioned, Positioned)> { + let mut name = None; + let mut value = None; + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), + Rule::value => { + value = { + let pos = pc.step(&pair); + Some(Positioned::new(parse_value(pair, pc)?, pos)) + } + } + _ => unreachable!(), + } + } + Ok((name.unwrap(), value.unwrap())) +} + +fn parse_value(pair: Pair, pc: &mut PositionCalculator) -> Result { + let pair = pair.into_inner().next().unwrap(); + Ok(match pair.as_rule() { + Rule::object => parse_object_value(pair, pc)?, + Rule::array => parse_array_value(pair, pc)?, + Rule::float => Value::Float(pair.as_str().parse().unwrap()), + Rule::int => Value::Int(pair.as_str().parse().unwrap()), + Rule::string => Value::String({ + let pos = pc.step(&pair); + unquote_string(pair.as_str(), pos).map(|s| s.to_string())? + }), + Rule::name => Value::Enum(pair.to_string()), + Rule::boolean => Value::Boolean(match pair.as_str() { + "true" => true, + "false" => false, + _ => unreachable!(), + }), + Rule::null => Value::Null, + _ => unreachable!(), + }) +} + +fn parse_object_pair(pair: Pair, pc: &mut PositionCalculator) -> Result<(String, Value)> { + let mut name = None; + let mut value = None; + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::name => name = Some(pair.as_str().to_string()), + Rule::value => value = Some(parse_value(pair, pc)?), + _ => unreachable!(), + } + } + Ok((name.unwrap(), value.unwrap())) +} + +fn parse_object_value(pair: Pair, pc: &mut PositionCalculator) -> Result { + let mut map = BTreeMap::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::pair => map.extend(std::iter::once(parse_object_pair(pair, pc)?)), + _ => unreachable!(), + } + } + Ok(Value::Object(map)) +} + +fn parse_array_value(pair: Pair, pc: &mut PositionCalculator) -> Result { + let mut array = Vec::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::value => array.push(parse_value(pair, pc)?), + _ => unreachable!(), + } + } + Ok(Value::List(array)) +} + +fn parse_scalar_type( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result> { + let pos = pc.step(&pair); + let mut description = None; + let mut extend = false; + let mut name = None; + let mut directives = None; + + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::string => { + description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) + } + Rule::extend => extend = true, + Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), + Rule::directives => directives = Some(parse_directives(pair, pc)?), + _ => unreachable!(), + } + } + + Ok(Positioned::new( + ScalarType { + extend, + description, + name: name.unwrap(), + directives: directives.unwrap_or_default(), + }, + pos, + )) +} + +fn parse_implements_interfaces( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result>> { + let mut interfaces = Vec::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::name => { + interfaces.push(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) + } + _ => unreachable!(), + } + } + Ok(interfaces) +} + +fn parse_type(pair: Pair, pc: &mut PositionCalculator) -> Result { + let pair = pair.into_inner().next().unwrap(); + match pair.as_rule() { + Rule::nonnull_type => Ok(Type::NonNull(Box::new(parse_type(pair, pc)?))), + Rule::list_type => Ok(Type::List(Box::new(parse_type(pair, pc)?))), + Rule::name => Ok(Type::Named(pair.as_str().to_string())), + Rule::type_ => parse_type(pair, pc), + _ => unreachable!(), + } +} + +fn parse_input_value_definition( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result> { + let pos = pc.step(&pair); + let mut description = None; + let mut name = None; + let mut type_ = None; + let mut default_value = None; + let mut directives = None; + + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::string => { + description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) + } + Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), + Rule::type_ => { + let pos = pc.step(&pair); + type_ = Some(Positioned::new(parse_type(pair, pc)?, pos)); + } + Rule::default_value => { + let pos = pc.step(&pair); + default_value = Some(Positioned::new( + parse_value(pair.into_inner().next().unwrap(), pc)?, + pos, + )); + } + Rule::directives => directives = Some(parse_directives(pair, pc)?), + _ => unreachable!(), + } + } + + Ok(Positioned::new( + InputValue { + description, + name: name.unwrap(), + default_value, + ty: type_.unwrap(), + directives: directives.unwrap_or_default(), + }, + pos, + )) +} + +fn parse_arguments_definition( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result>> { + let mut arguments = Vec::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::input_value_definition => arguments.push(parse_input_value_definition(pair, pc)?), + _ => unreachable!(), + } + } + Ok(arguments) +} + +fn parse_field_definition( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result> { + let pos = pc.step(&pair); + let mut description = None; + let mut name = None; + let mut arguments = None; + let mut type_ = None; + let mut directives = None; + + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::string => { + description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) + } + Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), + Rule::arguments_definition => arguments = Some(parse_arguments_definition(pair, pc)?), + Rule::type_ => { + let pos = pc.step(&pair); + type_ = Some(Positioned::new(parse_type(pair, pc)?, pos)); + } + Rule::directives => directives = Some(parse_directives(pair, pc)?), + _ => unreachable!(), + } + } + + Ok(Positioned::new( + Field { + description, + name: name.unwrap(), + arguments: arguments.unwrap_or_default(), + ty: type_.unwrap(), + directives: directives.unwrap_or_default(), + }, + pos, + )) +} + +fn parse_fields_definition( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result>> { + let mut fields = Vec::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::field_definition => fields.push(parse_field_definition(pair, pc)?), + _ => unreachable!(), + } + } + Ok(fields) +} + +fn parse_object_type( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result> { + let pos = pc.step(&pair); + let mut description = None; + let mut extend = false; + let mut name = None; + let mut implements_interfaces = None; + let mut directives = None; + let mut fields = None; + + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::string => { + description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) + } + Rule::extend => extend = true, + Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), + Rule::implements_interfaces => { + implements_interfaces = Some(parse_implements_interfaces(pair, pc)?) + } + Rule::directives => directives = Some(parse_directives(pair, pc)?), + Rule::fields_definition => fields = Some(parse_fields_definition(pair, pc)?), + _ => unreachable!(), + } + } + + Ok(Positioned::new( + ObjectType { + extend, + description, + name: name.unwrap(), + implements_interfaces: implements_interfaces.unwrap_or_default(), + directives: directives.unwrap_or_default(), + fields: fields.unwrap_or_default(), + }, + pos, + )) +} + +fn parse_interface_type( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result> { + let pos = pc.step(&pair); + let mut description = None; + let mut extend = false; + let mut name = None; + let mut directives = None; + let mut fields = None; + + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::string => { + description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) + } + Rule::extend => extend = true, + Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), + Rule::directives => directives = Some(parse_directives(pair, pc)?), + Rule::fields_definition => fields = Some(parse_fields_definition(pair, pc)?), + _ => unreachable!(), + } + } + + Ok(Positioned::new( + InterfaceType { + extend, + description, + name: name.unwrap(), + directives: directives.unwrap_or_default(), + fields: fields.unwrap_or_default(), + }, + pos, + )) +} + +fn parse_union_members( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result>> { + let mut members = Vec::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::name => members.push(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), + _ => unreachable!(), + } + } + Ok(members) +} + +fn parse_union_type( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result> { + let pos = pc.step(&pair); + let mut description = None; + let mut extend = false; + let mut name = None; + let mut directives = None; + let mut members = None; + + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::string => { + description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) + } + Rule::extend => extend = true, + Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), + Rule::directives => directives = Some(parse_directives(pair, pc)?), + Rule::union_member_types => members = Some(parse_union_members(pair, pc)?), + _ => unreachable!(), + } + } + + Ok(Positioned::new( + UnionType { + extend, + description, + name: name.unwrap(), + directives: directives.unwrap_or_default(), + members: members.unwrap_or_default(), + }, + pos, + )) +} + +fn parse_enum_value( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result> { + let pos = pc.step(&pair); + let mut description = None; + let mut name = None; + let mut directives = None; + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::string => { + description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) + } + Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), + Rule::directives => directives = Some(parse_directives(pair, pc)?), + _ => unreachable!(), + } + } + Ok(Positioned::new( + EnumValue { + description, + name: name.unwrap(), + directives: directives.unwrap_or_default(), + }, + pos, + )) +} + +fn parse_enum_values( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result>> { + let mut values = Vec::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::enum_value_definition => values.push(parse_enum_value(pair, pc)?), + _ => unreachable!(), + } + } + Ok(values) +} + +fn parse_enum_type(pair: Pair, pc: &mut PositionCalculator) -> Result> { + let pos = pc.step(&pair); + let mut description = None; + let mut extend = false; + let mut name = None; + let mut directives = None; + let mut values = None; + + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::string => { + description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) + } + Rule::extend => extend = true, + Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), + Rule::directives => directives = Some(parse_directives(pair, pc)?), + Rule::enum_values_definition => values = Some(parse_enum_values(pair, pc)?), + _ => unreachable!(), + } + } + + Ok(Positioned::new( + EnumType { + extend, + description, + name: name.unwrap(), + directives: directives.unwrap_or_default(), + values: values.unwrap_or_default(), + }, + pos, + )) +} + +fn parse_input_type( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result> { + let pos = pc.step(&pair); + let mut description = None; + let mut extend = false; + let mut name = None; + let mut directives = None; + let mut fields = None; + + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::string => { + description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) + } + Rule::extend => extend = true, + Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), + Rule::directives => directives = Some(parse_directives(pair, pc)?), + Rule::input_fields_definition => fields = Some(parse_arguments_definition(pair, pc)?), + _ => unreachable!(), + } + } + + Ok(Positioned::new( + InputObjectType { + extend, + description, + name: name.unwrap(), + directives: directives.unwrap_or_default(), + fields: fields.unwrap_or_default(), + }, + pos, + )) +} + +fn parse_directive_locations( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result>> { + let mut locations = Vec::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::directive_location => { + let pos = pc.step(&pair); + let loc = match pair.as_str() { + "QUERY" => DirectiveLocation::Query, + "MUTATION" => DirectiveLocation::Mutation, + "SUBSCRIPTION" => DirectiveLocation::Subscription, + "FIELD" => DirectiveLocation::Field, + "FRAGMENT_DEFINITION" => DirectiveLocation::FragmentDefinition, + "FRAGMENT_SPREAD" => DirectiveLocation::FragmentSpread, + "INLINE_FRAGMENT" => DirectiveLocation::InlineFragment, + "SCHEMA" => DirectiveLocation::Schema, + "SCALAR" => DirectiveLocation::Scalar, + "OBJECT" => DirectiveLocation::Object, + "FIELD_DEFINITION" => DirectiveLocation::FieldDefinition, + "ARGUMENT_DEFINITION" => DirectiveLocation::ArgumentDefinition, + "INTERFACE" => DirectiveLocation::Interface, + "UNION" => DirectiveLocation::Union, + "ENUM" => DirectiveLocation::Enum, + "ENUM_VALUE" => DirectiveLocation::EnumValue, + "INPUT_OBJECT" => DirectiveLocation::InputObject, + "INPUT_FIELD_DEFINITION" => DirectiveLocation::InputFieldDefinition, + _ => unreachable!(), + }; + locations.push(Positioned::new(loc, pos)); + } + _ => unreachable!(), + } + } + Ok(locations) +} + +fn parse_directive_definition( + pair: Pair, + pc: &mut PositionCalculator, +) -> Result> { + let pos = pc.step(&pair); + let mut description = None; + let mut name = None; + let mut arguments = None; + let mut locations = None; + + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::string => { + description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) + } + Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), + Rule::arguments_definition => arguments = Some(parse_arguments_definition(pair, pc)?), + Rule::directive_locations => locations = Some(parse_directive_locations(pair, pc)?), + _ => unreachable!(), + } + } + + Ok(Positioned::new( + DirectiveDefinition { + description, + name: name.unwrap(), + arguments: arguments.unwrap(), + locations: locations.unwrap(), + }, + pos, + )) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + + #[test] + fn test_parser() { + for entry in fs::read_dir("tests/schemas").unwrap() { + if let Ok(entry) = entry { + SchemaParser::parse(Rule::document, &fs::read_to_string(entry.path()).unwrap()) + .unwrap(); + } + } + } + + #[test] + fn test_parser_ast() { + for entry in fs::read_dir("tests/schemas").unwrap() { + if let Ok(entry) = entry { + parse_schema(fs::read_to_string(entry.path()).unwrap()).unwrap(); + } + } + } +} diff --git a/async-graphql-parser/src/utils.rs b/async-graphql-parser/src/utils.rs index 7a34e457..a57e0f36 100644 --- a/async-graphql-parser/src/utils.rs +++ b/async-graphql-parser/src/utils.rs @@ -60,9 +60,14 @@ pub fn to_static_str(s: &str) -> &'static str { unsafe { (s as *const str).as_ref().unwrap() } } -pub fn unquote_string(s: &'static str, pos: Pos) -> Result> { - debug_assert!(s.starts_with('"') && s.ends_with('"')); - let s = &s[1..s.len() - 1]; +pub fn unquote_string(s: &str, pos: Pos) -> Result> { + let s = if s.starts_with(r#"""""#) { + &s[3..s.len() - 3] + } else if s.starts_with('"') { + &s[1..s.len() - 1] + } else { + unreachable!() + }; if !s.contains('\\') { return Ok(Cow::Borrowed(to_static_str(s))); diff --git a/async-graphql-parser/tests/queries/multiline_string.graphql b/async-graphql-parser/tests/queries/multiline_string.graphql new file mode 100644 index 00000000..85b3fc2f --- /dev/null +++ b/async-graphql-parser/tests/queries/multiline_string.graphql @@ -0,0 +1,7 @@ +{ + a(s: """ + a + b + c + """) +} \ No newline at end of file diff --git a/feature-comparison.md b/feature-comparison.md index 3879c45d..7d1719dc 100644 --- a/feature-comparison.md +++ b/feature-comparison.md @@ -8,10 +8,10 @@ Comparing Features of Other Rust GraphQL Implementations |----------------|---------------|-----------------| | async/await | 👍 | ⛔️ | | Rustfmt friendly(No DSL) | 👍 | ⛔️ | -| Boilerplate | less | some | +| Boilerplate | Less | Some | +| Type Safety | 👍 | 👍 | | Query | 👍 | 👍 | | Mutation | 👍 | 👍 | -| Type Safety | 👍 | 👍 | | Interfaces | 👍 | 👍 | | Union | 👍 | 👍 | | Dataloading | 👍 | 👍 |