Merge branch 'schema-parser'

This commit is contained in:
sunli 2020-05-15 22:28:08 +08:00
commit 2764a6a3a3
34 changed files with 1535 additions and 203 deletions

View File

@ -0,0 +1,35 @@
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<R: RuleType> From<pest::error::Error<R>> for Error {
fn from(err: pest::error::Error<R>) -> 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<T> = std::result::Result<T, Error>;

View File

@ -3,11 +3,18 @@ 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 schema_parser;
mod utils;
mod value;
pub use error::{Error, Result};
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};
pub use schema_parser::parse_schema;
pub use value::{UploadValue, Value};

View File

@ -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} }
@ -29,7 +32,6 @@ nonnull_type = { (list_type | name) ~ "!" }
type_ = { nonnull_type | list_type | name }
// query
arguments = { "(" ~ pair* ~ ")" }
directive = { "@" ~ name ~ arguments? }
directives = { directive* }

View File

@ -1,102 +1,19 @@
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::Result;
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<pest::error::Error<Rule>> for Error {
fn from(err: pest::error::Error<Rule>) -> 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<T> = std::result::Result<T, Error>;
pub(crate) struct PositionCalculator<'a> {
input: Peekable<Chars<'a>>,
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<Rule>) -> 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<T: Into<String>>(input: T) -> Result<Document> {
let source = input.into();
@ -134,6 +51,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;
@ -365,14 +288,8 @@ fn parse_value2(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Value>
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() {
@ -405,9 +322,7 @@ fn parse_object_value(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<V
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)?));
}
Rule::pair => map.extend(std::iter::once(parse_object_pair(pair, pc)?)),
_ => unreachable!(),
}
}
@ -418,9 +333,7 @@ fn parse_array_value(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Va
let mut array = Vec::new();
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::value => {
array.push(parse_value2(pair, pc)?);
}
Rule::value => array.push(parse_value2(pair, pc)?),
_ => unreachable!(),
}
}
@ -647,103 +560,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<Cow<'static, str>> {
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::*;

View File

@ -0,0 +1,72 @@
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+ }
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 | "_")* }
array = { "[" ~ value* ~ "]" }
pair = { name ~ ":" ~ value }
object = { "{" ~ pair* ~ "}" }
value = { object | array | float | int | string | null | boolean | name }
// type
list_type = { "[" ~ type_ ~ "]" }
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}

View File

@ -0,0 +1,171 @@
use crate::pos::Positioned;
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<Value>),
Object(BTreeMap<String, Value>),
}
#[derive(Debug, PartialEq)]
pub enum Type {
Named(String),
List(Box<Type>),
NonNull(Box<Type>),
}
#[derive(Debug)]
pub struct Document {
pub definitions: Vec<Positioned<Definition>>,
}
#[derive(Debug)]
pub enum Definition {
SchemaDefinition(Positioned<SchemaDefinition>),
TypeDefinition(Positioned<TypeDefinition>),
DirectiveDefinition(Positioned<DirectiveDefinition>),
}
#[derive(Debug)]
pub struct SchemaDefinition {
pub extend: bool,
pub directives: Vec<Positioned<Directive>>,
pub query: Option<Positioned<String>>,
pub mutation: Option<Positioned<String>>,
pub subscription: Option<Positioned<String>>,
}
#[derive(Debug)]
pub enum TypeDefinition {
Scalar(Positioned<ScalarType>),
Object(Positioned<ObjectType>),
Interface(Positioned<InterfaceType>),
Union(Positioned<UnionType>),
Enum(Positioned<EnumType>),
InputObject(Positioned<InputObjectType>),
}
#[derive(Debug)]
pub struct ScalarType {
pub extend: bool,
pub description: Option<Positioned<String>>,
pub name: Positioned<String>,
pub directives: Vec<Positioned<Directive>>,
}
#[derive(Debug)]
pub struct ObjectType {
pub extend: bool,
pub description: Option<Positioned<String>>,
pub name: Positioned<String>,
pub implements_interfaces: Vec<Positioned<String>>,
pub directives: Vec<Positioned<Directive>>,
pub fields: Vec<Positioned<Field>>,
}
#[derive(Debug)]
pub struct Field {
pub description: Option<Positioned<String>>,
pub name: Positioned<String>,
pub arguments: Vec<Positioned<InputValue>>,
pub ty: Positioned<Type>,
pub directives: Vec<Positioned<Directive>>,
}
#[derive(Debug)]
pub struct InputValue {
pub description: Option<Positioned<String>>,
pub name: Positioned<String>,
pub ty: Positioned<Type>,
pub default_value: Option<Positioned<Value>>,
pub directives: Vec<Positioned<Directive>>,
}
#[derive(Debug)]
pub struct InterfaceType {
pub extend: bool,
pub description: Option<Positioned<String>>,
pub name: Positioned<String>,
pub directives: Vec<Positioned<Directive>>,
pub fields: Vec<Positioned<Field>>,
}
#[derive(Debug)]
pub struct UnionType {
pub extend: bool,
pub description: Option<Positioned<String>>,
pub name: Positioned<String>,
pub directives: Vec<Positioned<Directive>>,
pub members: Vec<Positioned<String>>,
}
#[derive(Debug)]
pub struct EnumType {
pub extend: bool,
pub description: Option<Positioned<String>>,
pub name: Positioned<String>,
pub directives: Vec<Positioned<Directive>>,
pub values: Vec<Positioned<EnumValue>>,
}
#[derive(Debug)]
pub struct EnumValue {
pub description: Option<Positioned<String>>,
pub name: Positioned<String>,
pub directives: Vec<Positioned<Directive>>,
}
#[derive(Debug)]
pub struct InputObjectType {
pub extend: bool,
pub description: Option<Positioned<String>>,
pub name: Positioned<String>,
pub directives: Vec<Positioned<Directive>>,
pub fields: Vec<Positioned<InputValue>>,
}
#[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<Positioned<String>>,
pub name: Positioned<String>,
pub arguments: Vec<Positioned<InputValue>>,
pub locations: Vec<Positioned<DirectiveLocation>>,
}
#[derive(Debug)]
pub struct Directive {
pub name: Positioned<String>,
pub arguments: Vec<(Positioned<String>, Positioned<Value>)>,
}

View File

@ -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<T: AsRef<str>>(input: T) -> Result<Document> {
let document_pair: Pair<Rule> = 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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<SchemaDefinition>> {
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<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Directive>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Vec<Positioned<Directive>>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Vec<(Positioned<String>, Positioned<Value>)>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<(Positioned<String>, Positioned<Value>)> {
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<Rule>, pc: &mut PositionCalculator) -> Result<Value> {
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<Rule>, 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<Rule>, pc: &mut PositionCalculator) -> Result<Value> {
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<Rule>, pc: &mut PositionCalculator) -> Result<Value> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<ScalarType>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Vec<Positioned<String>>> {
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<Rule>, pc: &mut PositionCalculator) -> Result<Type> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<InputValue>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Vec<Positioned<InputValue>>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<Field>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Vec<Positioned<Field>>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<ObjectType>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<InterfaceType>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Vec<Positioned<String>>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<UnionType>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<EnumValue>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Vec<Positioned<EnumValue>>> {
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<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<EnumType>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<InputObjectType>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Vec<Positioned<DirectiveLocation>>> {
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<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<DirectiveDefinition>> {
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();
}
}
}
}

View File

@ -0,0 +1,158 @@
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<Chars<'a>>,
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<R: RuleType>(&mut self, pair: &Pair<R>) -> 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: &str, pos: Pos) -> Result<Cow<'static, str>> {
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)));
}
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))
}

View File

@ -0,0 +1,7 @@
{
a(s: """
a
b
c
""")
}

View File

@ -0,0 +1 @@
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
union UndefinedUnion

View File

@ -0,0 +1,4 @@
enum Site {
DESKTOP
MOBILE
}

View File

@ -0,0 +1,3 @@
extend enum Site {
VR
}

View File

@ -0,0 +1,3 @@
extend input InputType {
other: Float = 1.23e4
}

View File

@ -0,0 +1,3 @@
extend input InputType {
other: Float = 12300
}

View File

@ -0,0 +1,3 @@
extend interface Bar {
two(argument: InputType!): Type
}

View File

@ -0,0 +1,3 @@
extend type Foo {
seven(argument: [String]): Type
}

View File

@ -0,0 +1 @@
extend scalar CustomScalar @onScalar

View File

@ -0,0 +1,3 @@
type Type1 implements IOne
type Type1 implements IOne & ITwo

View File

@ -0,0 +1,2 @@
type Type1 implements & IOne & ITwo
type Type2 implements & IOne

View File

@ -0,0 +1,3 @@
type Type1 implements IOne & ITwo
type Type2 implements IOne

View File

@ -0,0 +1,4 @@
input InputType {
key: String!
answer: Int = 42
}

View File

@ -0,0 +1,3 @@
interface Bar {
one: Type
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,3 @@
schema {
query: Query
}

View File

@ -0,0 +1 @@
type UndefinedType

View File

@ -0,0 +1,2 @@
"This is the best scalar type"
scalar BestType @perfectness(value: 100500)

View File

@ -0,0 +1,3 @@
type Foo {
bar: Type
}

View File

@ -0,0 +1 @@
union Feed = Story | Article | Advert

View File

@ -0,0 +1 @@
extend union Feed = Photo | Video

View File

@ -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 | 👍 | 👍 |