Define AST types for schema

This commit is contained in:
Sunli 2020-05-15 11:42:01 +08:00
parent 6de85377f7
commit 752f6adde8
31 changed files with 683 additions and 182 deletions

View File

@ -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<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(),
}
}
}

View File

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

View File

@ -29,7 +29,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,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<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 +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<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,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

View File

@ -0,0 +1,149 @@
use crate::pos::Positioned;
use crate::ParsedValue;
#[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 {
extend: bool,
description: Positioned<String>,
definition: Positioned<TypeDefinition>,
},
DirectiveDefinition(Positioned<DirectiveDefinition>),
}
#[derive(Debug)]
pub struct SchemaDefinition {
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 name: Positioned<String>,
pub directives: Vec<Positioned<Directive>>,
}
#[derive(Debug)]
pub struct ObjectType {
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<ParsedValue>,
pub directives: Vec<Positioned<Directive>>,
}
#[derive(Debug)]
pub struct InterfaceType {
pub name: Positioned<String>,
pub directives: Vec<Positioned<Directive>>,
pub fields: Vec<Positioned<Field>>,
}
#[derive(Debug)]
pub struct UnionType {
pub name: Positioned<String>,
pub directives: Vec<Positioned<Directive>>,
pub types: Vec<Positioned<String>>,
}
#[derive(Debug)]
pub struct EnumType {
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 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<ParsedValue>)>,
}

View File

@ -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<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: &'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))
}

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