diff --git a/async-graphql-parser/src/error.rs b/async-graphql-parser/src/error.rs new file mode 100644 index 00000000..a7d65418 --- /dev/null +++ b/async-graphql-parser/src/error.rs @@ -0,0 +1,32 @@ +use crate::Pos; +use pest::error::LineColLocation; +use pest::RuleType; +use std::fmt; + +/// Parser error +#[derive(Error, Debug, PartialEq)] +pub struct Error { + pub pos: Pos, + pub message: String, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message) + } +} + +impl From> for Error { + fn from(err: pest::error::Error) -> Self { + Error { + pos: { + let (line, column) = match err.line_col { + LineColLocation::Pos((line, column)) => (line, column), + LineColLocation::Span((line, column), _) => (line, column), + }; + Pos { line, column } + }, + message: err.to_string(), + } + } +} diff --git a/async-graphql-parser/src/lib.rs b/async-graphql-parser/src/lib.rs index 95871538..ecba8985 100644 --- a/async-graphql-parser/src/lib.rs +++ b/async-graphql-parser/src/lib.rs @@ -3,11 +3,16 @@ extern crate pest_derive; #[macro_use] extern crate thiserror; -mod pos; pub mod query; +pub mod schema; + +mod error; +mod pos; mod query_parser; +mod utils; mod value; +pub use error::Error; pub use pos::{Pos, Positioned}; -pub use query_parser::{parse_query, parse_value, Error, ParsedValue, Result}; +pub use query_parser::{parse_query, parse_value, ParsedValue, Result}; pub use value::{UploadValue, Value}; diff --git a/async-graphql-parser/src/query.pest b/async-graphql-parser/src/query.pest index ed92e17e..1a444f04 100644 --- a/async-graphql-parser/src/query.pest +++ b/async-graphql-parser/src/query.pest @@ -29,7 +29,6 @@ nonnull_type = { (list_type | name) ~ "!" } type_ = { nonnull_type | list_type | name } // query - arguments = { "(" ~ pair* ~ ")" } directive = { "@" ~ name ~ arguments? } directives = { directive* } diff --git a/async-graphql-parser/src/query_parser.rs b/async-graphql-parser/src/query_parser.rs index 6e988261..bcf3ac8d 100644 --- a/async-graphql-parser/src/query_parser.rs +++ b/async-graphql-parser/src/query_parser.rs @@ -1,102 +1,22 @@ use crate::pos::Positioned; use crate::query::*; +use crate::utils::{to_static_str, unquote_string, PositionCalculator}; use crate::value::Value; -use crate::Pos; -use arrayvec::ArrayVec; -use pest::error::LineColLocation; +use crate::{Error, Pos}; use pest::iterators::Pair; use pest::Parser; use std::borrow::Cow; use std::collections::BTreeMap; use std::fmt; -use std::iter::Peekable; use std::ops::Deref; -use std::str::Chars; #[derive(Parser)] #[grammar = "query.pest"] struct QueryParser; -/// Parser error -#[derive(Error, Debug, PartialEq)] -pub struct Error { - pub pos: Pos, - pub message: String, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.message) - } -} - -impl From> for Error { - fn from(err: pest::error::Error) -> Self { - Error { - pos: { - let (line, column) = match err.line_col { - LineColLocation::Pos((line, column)) => (line, column), - LineColLocation::Span((line, column), _) => (line, column), - }; - Pos { line, column } - }, - message: err.to_string(), - } - } -} - /// Parser result pub type Result = std::result::Result; -pub(crate) struct PositionCalculator<'a> { - input: Peekable>, - pos: usize, - line: usize, - column: usize, -} - -impl<'a> PositionCalculator<'a> { - fn new(input: &'a str) -> PositionCalculator<'a> { - Self { - input: input.chars().peekable(), - pos: 0, - line: 1, - column: 1, - } - } - - pub fn step(&mut self, pair: &Pair) -> Pos { - let pos = pair.as_span().start(); - debug_assert!(pos >= self.pos); - for _ in 0..pos - self.pos { - match self.input.next() { - Some('\r') => { - if let Some(&'\n') = self.input.peek() { - self.input.next(); - self.line += 1; - self.column = 1; - } else { - self.column += 1; - } - } - Some('\n') => { - self.line += 1; - self.column = 1; - } - Some(_) => { - self.column += 1; - } - None => break, - } - } - self.pos = pos; - Pos { - line: self.line, - column: self.column, - } - } -} - /// Parse a GraphQL query. pub fn parse_query>(input: T) -> Result { let source = input.into(); @@ -134,6 +54,12 @@ pub struct ParsedValue { value: Value, } +impl fmt::Debug for ParsedValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.value) + } +} + impl Deref for ParsedValue { type Target = Value; @@ -647,103 +573,6 @@ fn parse_fragment_definition( )) } -#[inline] -fn to_static_str(s: &str) -> &'static str { - unsafe { (s as *const str).as_ref().unwrap() } -} - -fn unquote_string(s: &'static str, pos: Pos) -> Result> { - debug_assert!(s.starts_with('"') && s.ends_with('"')); - let s = &s[1..s.len() - 1]; - - if !s.contains('\\') { - return Ok(Cow::Borrowed(to_static_str(s))); - } - - let mut chars = s.chars(); - let mut res = String::with_capacity(s.len()); - let mut temp_code_point = ArrayVec::<[u8; 4]>::new(); - - while let Some(c) = chars.next() { - match c { - '\\' => { - match chars.next().expect("slash cant be at the end") { - c @ '"' | c @ '\\' | c @ '/' => res.push(c), - 'b' => res.push('\u{0010}'), - 'f' => res.push('\u{000C}'), - 'n' => res.push('\n'), - 'r' => res.push('\r'), - 't' => res.push('\t'), - 'u' => { - temp_code_point.clear(); - for _ in 0..4 { - match chars.next() { - Some(inner_c) if inner_c.is_digit(16) => { - temp_code_point.push(inner_c as u8) - } - Some(inner_c) => { - return Err(Error { - pos, - message: format!( - "{} is not a valid unicode code point", - inner_c - ), - }); - } - None => { - return Err(Error { - pos, - message: format!( - "{} must have 4 characters after it", - unsafe { - std::str::from_utf8_unchecked( - temp_code_point.as_slice(), - ) - } - ), - }); - } - } - } - - // convert our hex string into a u32, then convert that into a char - match u32::from_str_radix( - unsafe { std::str::from_utf8_unchecked(temp_code_point.as_slice()) }, - 16, - ) - .map(std::char::from_u32) - { - Ok(Some(unicode_char)) => res.push(unicode_char), - _ => { - return Err(Error { - pos, - message: format!( - "{} is not a valid unicode code point", - unsafe { - std::str::from_utf8_unchecked( - temp_code_point.as_slice(), - ) - } - ), - }); - } - } - } - c => { - return Err(Error { - pos, - message: format!("bad escaped char {:?}", c), - }); - } - } - } - c => res.push(c), - } - } - - Ok(Cow::Owned(res)) -} - #[cfg(test)] mod tests { use super::*; diff --git a/async-graphql-parser/src/schema.pest b/async-graphql-parser/src/schema.pest new file mode 100644 index 00000000..e8268490 --- /dev/null +++ b/async-graphql-parser/src/schema.pest @@ -0,0 +1,31 @@ +WHITESPACE = _{ " " | "," | "\t" | "\u{feff}" | NEWLINE } +COMMENT = _{ "#" ~ (!"\n" ~ ANY)* } + +// value +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)? } +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 } + +// type +list_type = { "[" ~ type_ ~ "]" } +nonnull_type = { (list_type | name) ~ "!" } +type_ = { nonnull_type | list_type | name } + +// type system diff --git a/async-graphql-parser/src/schema.rs b/async-graphql-parser/src/schema.rs new file mode 100644 index 00000000..a54c6df1 --- /dev/null +++ b/async-graphql-parser/src/schema.rs @@ -0,0 +1,149 @@ +use crate::pos::Positioned; +use crate::ParsedValue; + +#[derive(Debug, PartialEq)] +pub enum Type { + Named(String), + List(Box), + NonNull(Box), +} + +#[derive(Debug)] +pub struct Document { + pub definitions: Vec>, +} + +#[derive(Debug)] +pub enum Definition { + SchemaDefinition(Positioned), + TypeDefinition { + extend: bool, + description: Positioned, + definition: Positioned, + }, + DirectiveDefinition(Positioned), +} + +#[derive(Debug)] +pub struct SchemaDefinition { + pub directives: Vec>, + pub query: Option>, + pub mutation: Option>, + pub subscription: Option>, +} + +#[derive(Debug)] +pub enum TypeDefinition { + Scalar(Positioned), + Object(Positioned), + Interface(Positioned), + Union(Positioned), + Enum(Positioned), + InputObject(Positioned), +} + +#[derive(Debug)] +pub struct ScalarType { + pub name: Positioned, + pub directives: Vec>, +} + +#[derive(Debug)] +pub struct ObjectType { + pub name: Positioned, + pub implements_interfaces: Vec>, + pub directives: Vec>, + pub fields: Vec>, +} + +#[derive(Debug)] +pub struct Field { + pub description: Option>, + pub name: Positioned, + pub arguments: Vec>, + pub ty: Positioned, + pub directives: Vec>, +} + +#[derive(Debug)] +pub struct InputValue { + pub description: Option>, + pub name: Positioned, + pub ty: Positioned, + pub default_value: Option, + pub directives: Vec>, +} + +#[derive(Debug)] +pub struct InterfaceType { + pub name: Positioned, + pub directives: Vec>, + pub fields: Vec>, +} + +#[derive(Debug)] +pub struct UnionType { + pub name: Positioned, + pub directives: Vec>, + pub types: Vec>, +} + +#[derive(Debug)] +pub struct EnumType { + pub name: Positioned, + pub directives: Vec>, + pub values: Vec>, +} + +#[derive(Debug)] +pub struct EnumValue { + pub description: Option>, + pub name: Positioned, + pub directives: Vec>, +} + +#[derive(Debug)] +pub struct InputObjectType { + pub name: Positioned, + pub directives: Vec>, + pub fields: Vec>, +} + +#[derive(Debug)] +pub enum DirectiveLocation { + // executable + Query, + Mutation, + Subscription, + Field, + FragmentDefinition, + FragmentSpread, + InlineFragment, + + // type_system + Schema, + Scalar, + Object, + FieldDefinition, + ArgumentDefinition, + Interface, + Union, + Enum, + EnumValue, + InputObject, + InputFieldDefinition, +} + +#[derive(Debug)] +pub struct DirectiveDefinition { + pub description: Option>, + pub name: Positioned, + pub arguments: Vec>, + pub locations: Vec>, +} + +#[derive(Debug)] +pub struct Directive { + pub name: Positioned, + pub arguments: Vec<(Positioned, Positioned)>, +} diff --git a/async-graphql-parser/src/utils.rs b/async-graphql-parser/src/utils.rs new file mode 100644 index 00000000..7a34e457 --- /dev/null +++ b/async-graphql-parser/src/utils.rs @@ -0,0 +1,153 @@ +use crate::{Error, Pos, Result}; +use arrayvec::ArrayVec; +use pest::iterators::Pair; +use pest::RuleType; +use std::borrow::Cow; +use std::iter::Peekable; +use std::str::Chars; + +pub struct PositionCalculator<'a> { + input: Peekable>, + pos: usize, + line: usize, + column: usize, +} + +impl<'a> PositionCalculator<'a> { + pub fn new(input: &'a str) -> PositionCalculator<'a> { + Self { + input: input.chars().peekable(), + pos: 0, + line: 1, + column: 1, + } + } + + pub fn step(&mut self, pair: &Pair) -> Pos { + let pos = pair.as_span().start(); + debug_assert!(pos >= self.pos); + for _ in 0..pos - self.pos { + match self.input.next() { + Some('\r') => { + if let Some(&'\n') = self.input.peek() { + self.input.next(); + self.line += 1; + self.column = 1; + } else { + self.column += 1; + } + } + Some('\n') => { + self.line += 1; + self.column = 1; + } + Some(_) => { + self.column += 1; + } + None => break, + } + } + self.pos = pos; + Pos { + line: self.line, + column: self.column, + } + } +} + +#[inline] +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]; + + if !s.contains('\\') { + return Ok(Cow::Borrowed(to_static_str(s))); + } + + let mut chars = s.chars(); + let mut res = String::with_capacity(s.len()); + let mut temp_code_point = ArrayVec::<[u8; 4]>::new(); + + while let Some(c) = chars.next() { + match c { + '\\' => { + match chars.next().expect("slash cant be at the end") { + c @ '"' | c @ '\\' | c @ '/' => res.push(c), + 'b' => res.push('\u{0010}'), + 'f' => res.push('\u{000C}'), + 'n' => res.push('\n'), + 'r' => res.push('\r'), + 't' => res.push('\t'), + 'u' => { + temp_code_point.clear(); + for _ in 0..4 { + match chars.next() { + Some(inner_c) if inner_c.is_digit(16) => { + temp_code_point.push(inner_c as u8) + } + Some(inner_c) => { + return Err(Error { + pos, + message: format!( + "{} is not a valid unicode code point", + inner_c + ), + }); + } + None => { + return Err(Error { + pos, + message: format!( + "{} must have 4 characters after it", + unsafe { + std::str::from_utf8_unchecked( + temp_code_point.as_slice(), + ) + } + ), + }); + } + } + } + + // convert our hex string into a u32, then convert that into a char + match u32::from_str_radix( + unsafe { std::str::from_utf8_unchecked(temp_code_point.as_slice()) }, + 16, + ) + .map(std::char::from_u32) + { + Ok(Some(unicode_char)) => res.push(unicode_char), + _ => { + return Err(Error { + pos, + message: format!( + "{} is not a valid unicode code point", + unsafe { + std::str::from_utf8_unchecked( + temp_code_point.as_slice(), + ) + } + ), + }); + } + } + } + c => { + return Err(Error { + pos, + message: format!("bad escaped char {:?}", c), + }); + } + } + } + c => res.push(c), + } + } + + Ok(Cow::Owned(res)) +} diff --git a/async-graphql-parser/tests/schemas/directive.graphql b/async-graphql-parser/tests/schemas/directive.graphql new file mode 100644 index 00000000..96019ccc --- /dev/null +++ b/async-graphql-parser/tests/schemas/directive.graphql @@ -0,0 +1 @@ +directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT diff --git a/async-graphql-parser/tests/schemas/directive_descriptions.graphql b/async-graphql-parser/tests/schemas/directive_descriptions.graphql new file mode 100644 index 00000000..b17eaacd --- /dev/null +++ b/async-graphql-parser/tests/schemas/directive_descriptions.graphql @@ -0,0 +1,19 @@ +""" +Directs the executor to include this field or fragment only when the `if` argument is true. +""" +directive @include( + """ + Included when true. + """ + if: Boolean! +) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +""" +Directs the executor to skip this field or fragment when the `if` argument is true. +""" +directive @skip( + """ + Skipped when true. + """ + if: Boolean! +) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT diff --git a/async-graphql-parser/tests/schemas/directive_descriptions_canonical.graphql b/async-graphql-parser/tests/schemas/directive_descriptions_canonical.graphql new file mode 100644 index 00000000..6bc4d733 --- /dev/null +++ b/async-graphql-parser/tests/schemas/directive_descriptions_canonical.graphql @@ -0,0 +1,13 @@ +""" + Directs the executor to include this field or fragment only when the `if` argument is true. +""" +directive @include(""" + Included when true. +""" if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +""" + Directs the executor to skip this field or fragment when the `if` argument is true. +""" +directive @skip(""" + Skipped when true. +""" if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT diff --git a/async-graphql-parser/tests/schemas/empty_union.graphql b/async-graphql-parser/tests/schemas/empty_union.graphql new file mode 100644 index 00000000..cba4f2f4 --- /dev/null +++ b/async-graphql-parser/tests/schemas/empty_union.graphql @@ -0,0 +1 @@ +union UndefinedUnion diff --git a/async-graphql-parser/tests/schemas/enum.graphql b/async-graphql-parser/tests/schemas/enum.graphql new file mode 100644 index 00000000..78072603 --- /dev/null +++ b/async-graphql-parser/tests/schemas/enum.graphql @@ -0,0 +1,4 @@ +enum Site { + DESKTOP + MOBILE +} diff --git a/async-graphql-parser/tests/schemas/extend_enum.graphql b/async-graphql-parser/tests/schemas/extend_enum.graphql new file mode 100644 index 00000000..12ce6561 --- /dev/null +++ b/async-graphql-parser/tests/schemas/extend_enum.graphql @@ -0,0 +1,3 @@ +extend enum Site { + VR +} diff --git a/async-graphql-parser/tests/schemas/extend_input.graphql b/async-graphql-parser/tests/schemas/extend_input.graphql new file mode 100644 index 00000000..64ddf3ed --- /dev/null +++ b/async-graphql-parser/tests/schemas/extend_input.graphql @@ -0,0 +1,3 @@ +extend input InputType { + other: Float = 1.23e4 +} diff --git a/async-graphql-parser/tests/schemas/extend_input_canonical.graphql b/async-graphql-parser/tests/schemas/extend_input_canonical.graphql new file mode 100644 index 00000000..df70f320 --- /dev/null +++ b/async-graphql-parser/tests/schemas/extend_input_canonical.graphql @@ -0,0 +1,3 @@ +extend input InputType { + other: Float = 12300 +} diff --git a/async-graphql-parser/tests/schemas/extend_interface.graphql b/async-graphql-parser/tests/schemas/extend_interface.graphql new file mode 100644 index 00000000..5536dc77 --- /dev/null +++ b/async-graphql-parser/tests/schemas/extend_interface.graphql @@ -0,0 +1,3 @@ +extend interface Bar { + two(argument: InputType!): Type +} diff --git a/async-graphql-parser/tests/schemas/extend_object.graphql b/async-graphql-parser/tests/schemas/extend_object.graphql new file mode 100644 index 00000000..5e6920a3 --- /dev/null +++ b/async-graphql-parser/tests/schemas/extend_object.graphql @@ -0,0 +1,3 @@ +extend type Foo { + seven(argument: [String]): Type +} diff --git a/async-graphql-parser/tests/schemas/extend_scalar.graphql b/async-graphql-parser/tests/schemas/extend_scalar.graphql new file mode 100644 index 00000000..a6dc49cf --- /dev/null +++ b/async-graphql-parser/tests/schemas/extend_scalar.graphql @@ -0,0 +1 @@ +extend scalar CustomScalar @onScalar diff --git a/async-graphql-parser/tests/schemas/implements.graphql b/async-graphql-parser/tests/schemas/implements.graphql new file mode 100644 index 00000000..a84b001a --- /dev/null +++ b/async-graphql-parser/tests/schemas/implements.graphql @@ -0,0 +1,3 @@ +type Type1 implements IOne + +type Type1 implements IOne & ITwo diff --git a/async-graphql-parser/tests/schemas/implements_amp.graphql b/async-graphql-parser/tests/schemas/implements_amp.graphql new file mode 100644 index 00000000..e4ff2d38 --- /dev/null +++ b/async-graphql-parser/tests/schemas/implements_amp.graphql @@ -0,0 +1,2 @@ +type Type1 implements & IOne & ITwo +type Type2 implements & IOne diff --git a/async-graphql-parser/tests/schemas/implements_amp_canonical.graphql b/async-graphql-parser/tests/schemas/implements_amp_canonical.graphql new file mode 100644 index 00000000..f066126b --- /dev/null +++ b/async-graphql-parser/tests/schemas/implements_amp_canonical.graphql @@ -0,0 +1,3 @@ +type Type1 implements IOne & ITwo + +type Type2 implements IOne diff --git a/async-graphql-parser/tests/schemas/input_type.graphql b/async-graphql-parser/tests/schemas/input_type.graphql new file mode 100644 index 00000000..db7634ba --- /dev/null +++ b/async-graphql-parser/tests/schemas/input_type.graphql @@ -0,0 +1,4 @@ +input InputType { + key: String! + answer: Int = 42 +} diff --git a/async-graphql-parser/tests/schemas/interface.graphql b/async-graphql-parser/tests/schemas/interface.graphql new file mode 100644 index 00000000..d167a481 --- /dev/null +++ b/async-graphql-parser/tests/schemas/interface.graphql @@ -0,0 +1,3 @@ +interface Bar { + one: Type +} diff --git a/async-graphql-parser/tests/schemas/kitchen-sink.graphql b/async-graphql-parser/tests/schemas/kitchen-sink.graphql new file mode 100644 index 00000000..f94f47c8 --- /dev/null +++ b/async-graphql-parser/tests/schemas/kitchen-sink.graphql @@ -0,0 +1,117 @@ +# Copyright (c) 2015-present, Facebook, Inc. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +schema { + query: QueryType + mutation: MutationType +} + +""" +This is a description +of the `Foo` type. +""" +type Foo implements Bar & Baz { + one: Type + two(argument: InputType!): Type + three(argument: InputType, other: String): Int + four(argument: String = "string"): String + five(argument: [String] = ["string", "string"]): String + six(argument: InputType = {key: "value"}): Type + seven(argument: Int = null): Type +} + +type AnnotatedObject @onObject(arg: "value") { + annotatedField(arg: Type = "default" @onArg): Type @onField +} + +type UndefinedType + +extend type Foo { + seven(argument: [String]): Type +} + +extend type Foo @onType + +interface Bar { + one: Type + four(argument: String = "string"): String +} + +interface AnnotatedInterface @onInterface { + annotatedField(arg: Type @onArg): Type @onField +} + +interface UndefinedInterface + +extend interface Bar { + two(argument: InputType!): Type +} + +extend interface Bar @onInterface + +union Feed = Story | Article | Advert + +union AnnotatedUnion @onUnion = A | B + +union AnnotatedUnionTwo @onUnion = | A | B + +union UndefinedUnion + +extend union Feed = Photo | Video + +extend union Feed @onUnion + +scalar CustomScalar + +scalar AnnotatedScalar @onScalar + +extend scalar CustomScalar @onScalar + +enum Site { + DESKTOP + MOBILE +} + +enum AnnotatedEnum @onEnum { + ANNOTATED_VALUE @onEnumValue + OTHER_VALUE +} + +enum UndefinedEnum + +extend enum Site { + VR +} + +extend enum Site @onEnum + +input InputType { + key: String! + answer: Int = 42 +} + +input AnnotatedInput @onInputObject { + annotatedField: Type @onField +} + +input UndefinedInput + +extend input InputType { + other: Float = 1.23e4 +} + +extend input InputType @onInputObject + +directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +directive @include(if: Boolean!) + on FIELD + | FRAGMENT_SPREAD + | INLINE_FRAGMENT + +directive @include2(if: Boolean!) on + | FIELD + | FRAGMENT_SPREAD + | INLINE_FRAGMENT diff --git a/async-graphql-parser/tests/schemas/kitchen-sink_canonical.graphql b/async-graphql-parser/tests/schemas/kitchen-sink_canonical.graphql new file mode 100644 index 00000000..7bdb4fad --- /dev/null +++ b/async-graphql-parser/tests/schemas/kitchen-sink_canonical.graphql @@ -0,0 +1,106 @@ +schema { + query: QueryType + mutation: MutationType +} + +""" + This is a description + of the `Foo` type. +""" +type Foo implements Bar & Baz { + one: Type + two(argument: InputType!): Type + three(argument: InputType, other: String): Int + four(argument: String = "string"): String + five(argument: [String] = ["string", "string"]): String + six(argument: InputType = {key: "value"}): Type + seven(argument: Int = null): Type +} + +type AnnotatedObject @onObject(arg: "value") { + annotatedField(arg: Type = "default" @onArg): Type @onField +} + +type UndefinedType + +extend type Foo { + seven(argument: [String]): Type +} + +extend type Foo @onType + +interface Bar { + one: Type + four(argument: String = "string"): String +} + +interface AnnotatedInterface @onInterface { + annotatedField(arg: Type @onArg): Type @onField +} + +interface UndefinedInterface + +extend interface Bar { + two(argument: InputType!): Type +} + +extend interface Bar @onInterface + +union Feed = Story | Article | Advert + +union AnnotatedUnion @onUnion = A | B + +union AnnotatedUnionTwo @onUnion = A | B + +union UndefinedUnion + +extend union Feed = Photo | Video + +extend union Feed @onUnion + +scalar CustomScalar + +scalar AnnotatedScalar @onScalar + +extend scalar CustomScalar @onScalar + +enum Site { + DESKTOP + MOBILE +} + +enum AnnotatedEnum @onEnum { + ANNOTATED_VALUE @onEnumValue + OTHER_VALUE +} + +enum UndefinedEnum + +extend enum Site { + VR +} + +extend enum Site @onEnum + +input InputType { + key: String! + answer: Int = 42 +} + +input AnnotatedInput @onInputObject { + annotatedField: Type @onField +} + +input UndefinedInput + +extend input InputType { + other: Float = 12300 +} + +extend input InputType @onInputObject + +directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +directive @include2(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT diff --git a/async-graphql-parser/tests/schemas/minimal.graphql b/async-graphql-parser/tests/schemas/minimal.graphql new file mode 100644 index 00000000..614a1f13 --- /dev/null +++ b/async-graphql-parser/tests/schemas/minimal.graphql @@ -0,0 +1,3 @@ +schema { + query: Query +} diff --git a/async-graphql-parser/tests/schemas/minimal_type.graphql b/async-graphql-parser/tests/schemas/minimal_type.graphql new file mode 100644 index 00000000..c2df5588 --- /dev/null +++ b/async-graphql-parser/tests/schemas/minimal_type.graphql @@ -0,0 +1 @@ +type UndefinedType diff --git a/async-graphql-parser/tests/schemas/scalar_type.graphql b/async-graphql-parser/tests/schemas/scalar_type.graphql new file mode 100644 index 00000000..43955310 --- /dev/null +++ b/async-graphql-parser/tests/schemas/scalar_type.graphql @@ -0,0 +1,2 @@ +"This is the best scalar type" +scalar BestType @perfectness(value: 100500) diff --git a/async-graphql-parser/tests/schemas/simple_object.graphql b/async-graphql-parser/tests/schemas/simple_object.graphql new file mode 100644 index 00000000..a454e38e --- /dev/null +++ b/async-graphql-parser/tests/schemas/simple_object.graphql @@ -0,0 +1,3 @@ +type Foo { + bar: Type +} diff --git a/async-graphql-parser/tests/schemas/union.graphql b/async-graphql-parser/tests/schemas/union.graphql new file mode 100644 index 00000000..ddadd4a8 --- /dev/null +++ b/async-graphql-parser/tests/schemas/union.graphql @@ -0,0 +1 @@ +union Feed = Story | Article | Advert diff --git a/async-graphql-parser/tests/schemas/union_extension.graphql b/async-graphql-parser/tests/schemas/union_extension.graphql new file mode 100644 index 00000000..a18d4648 --- /dev/null +++ b/async-graphql-parser/tests/schemas/union_extension.graphql @@ -0,0 +1 @@ +extend union Feed = Photo | Video