Rewrite async-graphql-parser

This commit is contained in:
Koxiaet 2020-09-06 06:38:31 +01:00
parent 1ef34f2c39
commit 81d85c2535
79 changed files with 1817 additions and 3076 deletions

View File

@ -117,7 +117,7 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
#[#crate_name::async_trait::async_trait]
impl #crate_name::OutputValueType for #ident {
async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::query::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
Ok(#crate_name::EnumType::to_value(self).into())
}
}

View File

@ -243,9 +243,9 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
};
resolvers.push(quote! {
if ctx.name.node == #name {
if ctx.node.name.node == #name {
#(#get_params)*
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
let ctx_obj = ctx.with_selection_set(&ctx.node.selection_set);
return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj, ctx.item).await;
}
});
@ -309,7 +309,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#(#resolvers)*
Err(#crate_name::QueryError::FieldNotFound {
field_name: ctx.name.to_string(),
field_name: ctx.node.name.to_string(),
object: #gql_typename.to_string(),
}.into_error(ctx.position()))
}
@ -328,7 +328,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #ident #generics {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::query::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#crate_name::do_resolve(ctx, self).await
}
}

View File

@ -96,7 +96,7 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #crate_name::OutputValueType for #ident {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::query::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#crate_name::do_resolve(ctx, self).await
}
}

View File

@ -185,7 +185,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
if typename == &<#entity_type as #crate_name::Type>::type_name() {
if let (#(#key_pat),*) = (#(#key_getter),*) {
#(#requires_getter)*
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
let ctx_obj = ctx.with_selection_set(&ctx.node.selection_set);
return #crate_name::OutputValueType::resolve(&#do_find, &ctx_obj, ctx.item).await;
}
}
@ -418,10 +418,10 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
});
resolvers.push(quote! {
if ctx.name.node == #field_name {
if ctx.node.name.node == #field_name {
#(#get_params)*
#guard
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
let ctx_obj = ctx.with_selection_set(&ctx.node.selection_set);
let res = #resolve_obj;
#post_guard
return #crate_name::OutputValueType::resolve(&res, &ctx_obj, ctx.item).await;
@ -496,7 +496,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#(#resolvers)*
Err(#crate_name::QueryError::FieldNotFound {
field_name: ctx.name.to_string(),
field_name: ctx.node.name.to_string(),
object: #gql_typename.to_string(),
}.into_error(ctx.position()))
}
@ -519,7 +519,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #self_ty #where_clause {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::query::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#crate_name::do_resolve(ctx, self).await
}
}

View File

@ -63,7 +63,7 @@ pub fn generate(scalar_args: &args::Scalar, item_impl: &mut ItemImpl) -> Result<
async fn resolve(
&self,
_: &#crate_name::ContextSelectionSet<'_>,
_field: &#crate_name::Positioned<#crate_name::parser::query::Field>
_field: &#crate_name::Positioned<#crate_name::parser::types::Field>
) -> #crate_name::Result<#crate_name::serde_json::Value> {
Ok(#crate_name::ScalarType::to_value(self).into())
}

View File

@ -135,10 +135,10 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
});
resolvers.push(quote! {
if ctx.name.node == #field_name {
if ctx.node.name.node == #field_name {
#guard
let res = self.#ident(ctx).await.map_err(|err| err.into_error_with_path(ctx.position(), ctx.path_node.as_ref()))?;
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
let ctx_obj = ctx.with_selection_set(&ctx.node.selection_set);
#post_guard
return #crate_name::OutputValueType::resolve(&res, &ctx_obj, ctx.item).await;
}
@ -192,7 +192,7 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#(#resolvers)*
Err(#crate_name::QueryError::FieldNotFound {
field_name: ctx.name.to_string(),
field_name: ctx.node.name.to_string(),
object: #gql_typename.to_string(),
}.into_error(ctx.position()))
}
@ -201,7 +201,7 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #ident #generics #where_clause {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::query::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#crate_name::do_resolve(ctx, self).await
}
}

View File

@ -248,10 +248,10 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
}
create_stream.push(quote! {
if ctx.name.node == #field_name {
if ctx.node.name.node == #field_name {
#(#get_params)*
#guard
let field_name = ::std::sync::Arc::new(ctx.result_name().to_string());
let field_name = ::std::sync::Arc::new(ctx.item.node.response_key().node.clone());
let field = ::std::sync::Arc::new(ctx.item.clone());
let pos = ctx.position();
@ -272,7 +272,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
parent: None,
segment: #crate_name::QueryPathSegment::Name(&field_name),
}),
&field.selection_set,
&field.node.selection_set,
&resolve_id,
);
#crate_name::OutputValueType::resolve(&msg, &ctx_selection_set, &*field).await
@ -344,7 +344,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
) -> #crate_name::Result<::std::pin::Pin<Box<dyn #crate_name::futures::Stream<Item = #crate_name::Result<#crate_name::serde_json::Value>> + Send>>> {
#(#create_stream)*
Err(#crate_name::QueryError::FieldNotFound {
field_name: ctx.name.to_string(),
field_name: ctx.node.name.to_string(),
object: #gql_typename.to_string(),
}.into_error(ctx.position()))
}

View File

@ -132,7 +132,7 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<Tok
impl #generics #crate_name::ObjectType for #ident #generics {
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> {
Err(#crate_name::QueryError::FieldNotFound {
field_name: ctx.name.to_string(),
field_name: ctx.node.name.to_string(),
object: #gql_typename.to_string(),
}.into_error(ctx.position()))
}
@ -151,7 +151,7 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<Tok
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #ident #generics {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::query::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::Result<#crate_name::serde_json::Value> {
#crate_name::do_resolve(ctx, self).await
}
}

View File

@ -1,5 +1,5 @@
use itertools::Itertools;
use proc_macro2::{Span, TokenStream};
use proc_macro2::{Span, TokenStream, TokenTree};
use proc_macro_crate::crate_name;
use quote::quote;
use syn::{
@ -12,8 +12,7 @@ pub fn get_crate_name(internal: bool) -> TokenStream {
quote! { crate }
} else {
let name = crate_name("async-graphql").unwrap_or_else(|_| "async_graphql".to_owned());
let id = Ident::new(&name, Span::call_site());
quote! { #id }
TokenTree::from(Ident::new(&name, Span::call_site())).into()
}
}

View File

@ -15,8 +15,5 @@ categories = ["network-programming", "asynchronous"]
[dependencies]
pest = "2.1.3"
pest_derive = "2.1.0"
thiserror = "1.0.11"
serde_json = "1.0.48"
arrayvec = "0.5.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.57"
serde = { version = "1.0.115", features = ["derive"] }

View File

@ -1,35 +0,0 @@
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

@ -0,0 +1,69 @@
WHITESPACE = _{ " " | "," | "\t" | "\u{feff}" | line_terminator }
COMMENT = _{ "#" ~ (!line_terminator ~ ANY)* }
line_terminator = @{ "\r\n" | "\r" | "\n" }
document = { SOI ~ definition+ ~ EOI }
definition = { operation_definition | fragment_definition }
operation_definition = { named_operation_definition | selection_set }
named_operation_definition = { operation_type ~ name? ~ variable_definitions? ~ directives? ~ selection_set }
operation_type = { "query" | "mutation" | "subscription" }
variable_definitions = { "(" ~ variable_definition* ~ ")" }
variable_definition = { variable ~ ":" ~ type_ ~ default_value? }
variable = { "$" ~ name }
default_value = { "=" ~ value }
type_ = @{ (name | "[" ~ type_ ~ "]") ~ "!"? }
// float must come before int; other than that order does not matter
value = { variable | float | int | string | boolean | null | enum_ | list | object }
float = @{ int ~ ((fractional ~ exponent) | fractional | exponent) }
fractional = { "." ~ ASCII_DIGIT+ }
exponent = { ("E" | "e") ~ ("+" | "-")? ~ ASCII_DIGIT+ }
int = @{ "-"? ~ ("0" | (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*)) }
boolean = { "true" | "false" }
string = ${ ("\"\"\"" ~ block_string_content ~ "\"\"\"") | ("\"" ~ string_content ~ "\"") }
block_string_content = @{ block_string_character* }
block_string_character = {
(!("\"\"\"" | "\\\"\"\"") ~ ANY)
| "\\\"\"\""
}
string_content = @{ string_character* }
string_character = {
(!("\"" | "\\" | line_terminator) ~ ANY)
| ("\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t"))
| ("\\u" ~ unicode_scalar_value_hex)
}
// Spec inconsistency:
// In GraphQL, strings can contain any Unicode code point. However in Rust strings can only contain
// Unicode Scalar Values. To avoid having to use Vec<u8> everywhere we deviate from the spec
// slightly and disallow non-scalar value code points at the parsing level.
unicode_scalar_value_hex = { !(^"d" ~ ('8'..'9' | 'a'..'f' | 'A'..'F')) ~ ASCII_HEX_DIGIT{4} }
null = { "null" }
enum_ = ${ !(boolean | null) ~ name }
list = { "[" ~ value* ~ "]" }
name_value = { name ~ ":" ~ value }
object = { "{" ~ name_value* ~ "}" }
selection_set = { "{" ~ selection+ ~ "}" }
selection = { field | inline_fragment | fragment_spread }
field = { alias? ~ name ~ arguments? ~ directives? ~ selection_set? }
alias = { name ~ ":" }
arguments = { "(" ~ name_value* ~ ")" }
fragment_spread = { "..." ~ name ~ directives? }
inline_fragment = { "..." ~ type_condition? ~ directives? ~ selection_set }
fragment_definition = { "fragment" ~ name ~ type_condition ~ directives? ~ selection_set }
type_condition = { "on" ~ name }
directives = { directive* }
directive = { "@" ~ name ~ arguments? }
name = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHA | ASCII_DIGIT | "_")* }

View File

@ -1,22 +1,54 @@
//! A parser for GraphQL. Used in the [`async-graphql`](https://crates.io/crates/async-graphql)
//! crate.
//!
//! It uses the [pest](https://crates.io/crates/pest) crate to parse the input and then transforms
//! it into Rust types.
#![forbid(unsafe_code)]
#![warn(clippy::pedantic)]
#![allow(clippy::doc_markdown, clippy::find_map, clippy::filter_map, clippy::module_name_repetitions, clippy::wildcard_imports, clippy::enum_glob_use)]
#[macro_use]
extern crate pest_derive;
#[macro_use]
extern crate thiserror;
use std::fmt;
use pest::RuleType;
use pest::error::LineColLocation;
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;
pub use schema_parser::parse_schema;
pub use value::{UploadValue, Value};
pub use parser::parse_query;
pub mod types;
mod pos;
mod parser;
mod utils;
/// Parser error.
#[derive(Debug, PartialEq)]
pub struct Error {
/// The position at which the error occurred.
pub pos: Pos,
/// The error message.
pub message: String,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for Error {}
impl<R: RuleType> From<pest::error::Error<R>> for Error {
fn from(err: pest::error::Error<R>) -> Self {
Error {
pos: {
match err.line_col {
LineColLocation::Pos((line, column)) | LineColLocation::Span((line, column), _) => Pos { line, column },
}
},
message: err.to_string(),
}
}
}
/// An alias for `Result<T, Error>`.
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -0,0 +1,479 @@
use crate::pos::Positioned;
use crate::types::*;
use crate::utils::{string_value, block_string_value, PositionCalculator};
use crate::Result;
use pest::iterators::{Pair, Pairs};
use pest::Parser;
use pest_derive::Parser;
#[derive(Parser)]
#[grammar = "graphql.pest"]
struct GraphQLParser;
/// Parse a GraphQL query.
///
/// # Errors
///
/// Fails if the query is not a valid GraphQL document.
pub fn parse_query<T: AsRef<str>>(input: T) -> Result<Document> {
let mut pc = PositionCalculator::new(input.as_ref());
Ok(parse_document(exactly_one(GraphQLParser::parse(Rule::document, input.as_ref())?), &mut pc))
}
fn parse_document(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Document {
debug_assert_eq!(pair.as_rule(), Rule::document);
Document {
definitions: pair.into_inner().filter(|pair| pair.as_rule() != Rule::EOI).map(|pair| parse_definition(pair, pc)).collect(),
}
}
fn parse_definition(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Definition {
debug_assert_eq!(pair.as_rule(), Rule::definition);
let pair = exactly_one(pair.into_inner());
match pair.as_rule() {
Rule::operation_definition => Definition::Operation(parse_operation_definition(pair, pc)),
Rule::fragment_definition => Definition::Fragment(parse_fragment_definition(pair, pc)),
_ => unreachable!(),
}
}
fn parse_operation_definition(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Positioned<OperationDefinition> {
debug_assert_eq!(pair.as_rule(), Rule::operation_definition);
let pos = pc.step(&pair);
let pair = exactly_one(pair.into_inner());
Positioned::new(match pair.as_rule() {
Rule::named_operation_definition => parse_named_operation_definition(pair, pc),
Rule::selection_set => OperationDefinition {
ty: OperationType::Query,
name: None,
variable_definitions: Vec::new(),
directives: Vec::new(),
selection_set: parse_selection_set(pair, pc),
},
_ => unreachable!(),
}, pos)
}
fn parse_named_operation_definition(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> OperationDefinition {
debug_assert_eq!(pair.as_rule(), Rule::named_operation_definition);
let mut pairs = pair.into_inner();
let ty = parse_operation_type(&pairs.next().unwrap(), pc);
let name = next_if_rule(&mut pairs, Rule::name).map(|pair| parse_name(&pair, pc));
let variable_definitions = next_if_rule(&mut pairs, Rule::variable_definitions).map(|pair| parse_variable_definitions(pair, pc));
let directives = next_if_rule(&mut pairs, Rule::directives).map(|pair| parse_directives(pair, pc));
let selection_set = parse_selection_set(pairs.next().unwrap(), pc);
debug_assert_eq!(pairs.next(), None);
OperationDefinition {
ty: ty.node,
name,
variable_definitions: variable_definitions.unwrap_or_default(),
directives: directives.unwrap_or_default(),
selection_set
}
}
fn parse_operation_type(pair: &Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<OperationType> {
debug_assert_eq!(pair.as_rule(), Rule::operation_type);
let pos = pc.step(&pair);
Positioned::new(match pair.as_str() {
"query" => OperationType::Query,
"mutation" => OperationType::Mutation,
"subscription" => OperationType::Subscription,
_ => unreachable!(),
}, pos)
}
fn parse_variable_definitions(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Vec<Positioned<VariableDefinition>> {
debug_assert_eq!(pair.as_rule(), Rule::variable_definitions);
pair.into_inner().map(|pair| parse_variable_definition(pair, pc)).collect()
}
fn parse_variable_definition(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Positioned<VariableDefinition> {
debug_assert_eq!(pair.as_rule(), Rule::variable_definition);
let pos = pc.step(&pair);
let mut pairs = pair.into_inner();
let variable = parse_variable(pairs.next().unwrap(), pc);
let var_type = parse_type(&pairs.next().unwrap(), pc);
let default_value = pairs.next().map(|pair| parse_default_value(pair, pc));
debug_assert_eq!(pairs.peek(), None);
Positioned::new(
VariableDefinition {
name: variable,
var_type,
default_value,
},
pos,
)
}
fn parse_variable(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<String> {
debug_assert_eq!(pair.as_rule(), Rule::variable);
parse_name(&exactly_one(pair.into_inner()), pc)
}
fn parse_default_value(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<Value> {
debug_assert_eq!(pair.as_rule(), Rule::default_value);
parse_value(exactly_one(pair.into_inner()), pc)
}
fn parse_type(pair: &Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<Type> {
debug_assert_eq!(pair.as_rule(), Rule::type_);
Positioned::new(Type::new(pair.as_str()), pc.step(&pair))
}
fn parse_value(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<Value> {
debug_assert_eq!(pair.as_rule(), Rule::value);
let pos = pc.step(&pair);
let pair = exactly_one(pair.into_inner());
Positioned::new(
match pair.as_rule() {
Rule::variable => Value::Variable(parse_variable(pair, pc).node),
Rule::float | Rule::int => Value::Number(pair.as_str().parse().expect("failed to parse number")),
Rule::string => Value::String({
let pair = exactly_one(pair.into_inner());
match pair.as_rule() {
Rule::block_string_content => block_string_value(pair.as_str()),
Rule::string_content => string_value(pair.as_str()),
_ => unreachable!(),
}
}),
Rule::boolean => Value::Boolean(match pair.as_str() {
"true" => true,
"false" => false,
_ => unreachable!(),
}),
Rule::null => Value::Null,
Rule::enum_ => Value::Enum(parse_name(&exactly_one(pair.into_inner()), pc).node),
Rule::list => Value::List(
pair.into_inner()
.map(|pair| parse_value(pair, pc).node)
.collect(),
),
Rule::object => Value::Object(
pair.into_inner()
.map(|pair| {
let (name, value) = parse_name_value(pair, pc);
(name.node, value.node)
})
.collect(),
),
_ => unreachable!(),
},
pos,
)
}
fn parse_name_value(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> (Positioned<String>, Positioned<Value>) {
debug_assert_eq!(pair.as_rule(), Rule::name_value);
let mut pairs = pair.into_inner();
let name = parse_name(&pairs.next().unwrap(), pc);
let value = parse_value(pairs.next().unwrap(), pc);
debug_assert_eq!(pairs.next(), None);
(name, value)
}
fn parse_selection_set(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Positioned<SelectionSet> {
debug_assert_eq!(pair.as_rule(), Rule::selection_set);
let pos = pc.step(&pair);
Positioned::new(
SelectionSet {
items: pair
.into_inner()
.map(|pair| parse_selection(pair, pc))
.collect(),
},
pos,
)
}
fn parse_selection(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<Selection> {
debug_assert_eq!(pair.as_rule(), Rule::selection);
let pos = pc.step(&pair);
let pair = exactly_one(pair.into_inner());
Positioned::new(match pair.as_rule() {
Rule::field => Selection::Field(parse_field(pair, pc)),
Rule::fragment_spread => Selection::FragmentSpread(parse_fragment_spread(pair, pc)),
Rule::inline_fragment => Selection::InlineFragment(parse_inline_fragment(pair, pc)),
_ => unreachable!(),
}, pos)
}
fn parse_field(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<Field> {
debug_assert_eq!(pair.as_rule(), Rule::field);
let pos = pc.step(&pair);
let mut pairs = pair.into_inner();
let alias = next_if_rule(&mut pairs, Rule::alias)
.map(|pair| parse_alias(pair, pc))
;
let name = parse_name(&pairs.next().unwrap(), pc);
let arguments = next_if_rule(&mut pairs, Rule::arguments)
.map(|pair| parse_arguments(pair, pc))
;
let directives = next_if_rule(&mut pairs, Rule::directives)
.map(|pair| parse_directives(pair, pc))
;
let selection_set = next_if_rule(&mut pairs, Rule::selection_set)
.map(|pair| parse_selection_set(pair, pc))
;
Positioned::new(
Field {
alias,
name,
arguments: arguments.unwrap_or_default(),
directives: directives.unwrap_or_default(),
selection_set: selection_set.unwrap_or_default(),
},
pos,
)
}
fn parse_alias(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<String> {
debug_assert_eq!(pair.as_rule(), Rule::alias);
parse_name(&exactly_one(pair.into_inner()), pc)
}
fn parse_arguments(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Vec<(Positioned<String>, Positioned<Value>)> {
debug_assert_eq!(pair.as_rule(), Rule::arguments);
pair.into_inner()
.map(|pair| parse_name_value(pair, pc))
.collect()
}
fn parse_fragment_spread(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Positioned<FragmentSpread> {
debug_assert_eq!(pair.as_rule(), Rule::fragment_spread);
let pos = pc.step(&pair);
let mut pairs = pair.into_inner();
let fragment_name = parse_name(&pairs.next().unwrap(), pc);
let directives = pairs
.next()
.map(|pair| parse_directives(pair, pc))
;
debug_assert_eq!(pairs.peek(), None);
Positioned::new(
FragmentSpread {
fragment_name,
directives: directives.unwrap_or_default(),
},
pos,
)
}
fn parse_inline_fragment(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Positioned<InlineFragment> {
debug_assert_eq!(pair.as_rule(), Rule::inline_fragment);
let pos = pc.step(&pair);
let mut pairs = pair.into_inner();
let type_condition = next_if_rule(&mut pairs, Rule::type_condition)
.map(|pair| parse_type_condition(pair, pc))
;
let directives = next_if_rule(&mut pairs, Rule::directives)
.map(|pair| parse_directives(pair, pc))
;
let selection_set = parse_selection_set(pairs.next().unwrap(), pc);
debug_assert_eq!(pairs.next(), None);
Positioned::new(
InlineFragment {
type_condition,
directives: directives.unwrap_or_default(),
selection_set,
},
pos,
)
}
fn parse_fragment_definition(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Positioned<FragmentDefinition> {
debug_assert_eq!(pair.as_rule(), Rule::fragment_definition);
let pos = pc.step(&pair);
let mut pairs = pair.into_inner();
let name = parse_name(&pairs.next().unwrap(), pc);
let type_condition = parse_type_condition(pairs.next().unwrap(), pc);
let directives = next_if_rule(&mut pairs, Rule::directives)
.map(|pair| parse_directives(pair, pc))
;
let selection_set = parse_selection_set(pairs.next().unwrap(), pc);
debug_assert_eq!(pairs.next(), None);
Positioned::new(
FragmentDefinition {
name,
type_condition,
directives: directives.unwrap_or_default(),
selection_set,
},
pos,
)
}
fn parse_type_condition(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Positioned<TypeCondition> {
debug_assert_eq!(pair.as_rule(), Rule::type_condition);
let pos = pc.step(&pair);
Positioned::new(
TypeCondition {
on: parse_name(&exactly_one(pair.into_inner()), pc),
},
pos,
)
}
fn parse_directives(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Vec<Positioned<Directive>> {
debug_assert_eq!(pair.as_rule(), Rule::directives);
pair.into_inner().map(|pair| parse_directive(pair, pc)).collect()
}
fn parse_directive(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<Directive> {
debug_assert_eq!(pair.as_rule(), Rule::directive);
let pos = pc.step(&pair);
let mut pairs = pair.into_inner();
let name = parse_name(&pairs.next().unwrap(), pc);
let arguments = pairs
.next()
.map(|pair| parse_arguments(pair, pc));
debug_assert_eq!(pairs.peek(), None);
Positioned::new(
Directive {
name,
arguments: arguments.unwrap_or_default(),
},
pos,
)
}
fn parse_name(pair: &Pair<Rule>, pc: &mut PositionCalculator) -> Positioned<String> {
debug_assert_eq!(pair.as_rule(), Rule::name);
Positioned::new(pair.as_str().to_owned(), pc.step(&pair))
}
// Parser helper functions
fn next_if_rule<'a>(pairs: &mut Pairs<'a, Rule>, rule: Rule) -> Option<Pair<'a, Rule>> {
if pairs.peek().map_or(false, |pair| pair.as_rule() == rule) {
Some(pairs.next().unwrap())
} else {
None
}
}
fn exactly_one<T>(iter: impl IntoIterator<Item = T>) -> T {
let mut iter = iter.into_iter();
let res = iter.next().unwrap();
debug_assert!(matches!(iter.next(), None));
res
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_parser() {
for entry in fs::read_dir("tests/queries").unwrap() {
if let Ok(entry) = entry {
GraphQLParser::parse(Rule::document, &fs::read_to_string(entry.path()).unwrap())
.unwrap();
}
}
}
#[test]
fn test_parser_ast() {
for entry in fs::read_dir("tests/queries").unwrap() {
if let Ok(entry) = entry {
parse_query(fs::read_to_string(entry.path()).unwrap()).unwrap();
}
}
}
#[test]
fn test_parse_overflowing_int() {
let query_ok = format!("mutation {{ add(big: {}) }} ", std::i32::MAX);
let query_overflow = format!("mutation {{ add(big: {}0000) }} ", std::i32::MAX);
assert!(parse_query(query_ok).is_ok());
assert!(parse_query(query_overflow).is_ok());
}
}

View File

@ -3,15 +3,14 @@ use std::borrow::{Borrow, BorrowMut};
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
/// Original position of element in source code
/// Original position of an element in source code.
#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Default, Hash, Serialize)]
pub struct Pos {
/// One-based line number
/// One-based line number.
pub line: usize,
/// One-based column number
/// One-based column number.
pub column: usize,
}
@ -27,11 +26,12 @@ impl fmt::Display for Pos {
}
}
/// Represents the position of a AST node
#[derive(Clone, Debug, Copy, Default)]
#[allow(missing_docs)]
/// An AST node that stores its original position.
#[derive(Debug, Clone, Copy, Default)]
pub struct Positioned<T: ?Sized> {
/// The position of the node.
pub pos: Pos,
/// The node itself.
pub node: T,
}
@ -40,49 +40,22 @@ impl<T: fmt::Display> fmt::Display for Positioned<T> {
self.node.fmt(f)
}
}
impl<T: Clone> Positioned<T> {
#[inline]
#[allow(missing_docs)]
pub fn clone_inner(&self) -> T {
self.node.clone()
}
}
impl<T: PartialEq> PartialEq for Positioned<T> {
fn eq(&self, other: &Self) -> bool {
self.node.eq(&other.node)
self.node == other.node
}
}
impl<T: Eq> Eq for Positioned<T> {}
impl<T: PartialOrd> PartialOrd for Positioned<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.node.partial_cmp(&other.node)
}
}
impl<T: Ord> Ord for Positioned<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.node.cmp(&other.node)
}
}
impl<T: Ord> Eq for Positioned<T> {}
impl<T: ?Sized> Deref for Positioned<T> {
type Target = T;
fn deref(&self) -> &T {
&self.node
}
}
impl<T: ?Sized> DerefMut for Positioned<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.node
}
}
impl<T: Hash> Hash for Positioned<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.node.hash(state)
@ -102,26 +75,15 @@ impl BorrowMut<str> for Positioned<String> {
}
impl<T> Positioned<T> {
pub(crate) fn new(node: T, pos: Pos) -> Positioned<T> {
/// Create a new positioned node from the node and its position.
#[must_use]
pub const fn new(node: T, pos: Pos) -> Positioned<T> {
Positioned { node, pos }
}
/// Get the inner node.
#[inline]
pub fn into_inner(self) -> T {
self.node
}
/// Get start position
#[inline]
pub fn position(&self) -> Pos {
self.pos
}
#[inline]
pub(crate) fn pack<F: FnOnce(Self) -> R, R>(self, f: F) -> Positioned<R> {
Positioned {
pos: self.pos,
node: f(self),
}
}
}

View File

@ -1,51 +0,0 @@
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 | "_")* }
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 }
// query
arguments = { "(" ~ pair* ~ ")" }
directive = { "@" ~ name ~ arguments? }
directives = { directive* }
alias = { name ~ ":" }
field = { alias? ~ name ~ arguments? ~ directives? ~ selection_set? }
fragment_spread = { "..." ~ name ~ directives? }
type_condition = { "on" ~ name }
inline_fragment = { "..." ~ type_condition? ~ directives? ~ selection_set }
selection = { field | inline_fragment | fragment_spread }
selection_set = { "{" ~ selection+ ~ "}" }
operation_type = { "query" | "mutation" | "subscription" }
default_value = { "=" ~ value }
variable_definition = { variable ~ ":" ~ type_ ~ default_value? }
variable_definitions = { "(" ~ variable_definition* ~ ")" }
named_operation_definition = { operation_type ~ name? ~ variable_definitions? ~ directives? ~ selection_set }
fragment_definition = { "fragment" ~ name ~ type_condition ~ directives? ~ selection_set }
document = { SOI ~ (named_operation_definition | selection_set | fragment_definition)+ ~ EOI}

View File

@ -1,243 +0,0 @@
use crate::pos::Positioned;
use crate::value::Value;
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, PartialEq, Clone)]
pub enum Type {
Named(String),
List(Box<Type>),
NonNull(Box<Type>),
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Named(name) => write!(f, "{}", name),
Type::List(ty) => write!(f, "[{}]", ty),
Type::NonNull(ty) => write!(f, "{}!", ty),
}
}
}
#[derive(Debug, Clone)]
pub struct Directive {
pub name: Positioned<String>,
pub arguments: Vec<(Positioned<String>, Positioned<Value>)>,
}
impl Directive {
pub fn get_argument(&self, name: &str) -> Option<&Positioned<Value>> {
self.arguments
.iter()
.find(|item| item.0.node == name)
.map(|item| &item.1)
}
}
pub type FragmentsMap = HashMap<String, Positioned<FragmentDefinition>>;
/// Query operation type
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum OperationType {
Query,
Mutation,
Subscription,
}
#[derive(Debug, Clone)]
pub struct CurrentOperation {
pub ty: OperationType,
pub variable_definitions: Vec<Positioned<VariableDefinition>>,
pub selection_set: Positioned<SelectionSet>,
}
#[derive(Debug, Clone)]
pub struct Document {
pub(crate) definitions: Vec<Positioned<Definition>>,
pub(crate) fragments: FragmentsMap,
pub(crate) current_operation: Option<CurrentOperation>,
}
impl Document {
#[inline]
pub fn definitions(&self) -> &[Positioned<Definition>] {
&self.definitions
}
#[inline]
pub fn fragments(&self) -> &FragmentsMap {
&self.fragments
}
#[inline]
pub fn current_operation(&self) -> &CurrentOperation {
self.current_operation
.as_ref()
.expect("Must first call retain_operation")
}
pub fn retain_operation(&mut self, operation_name: Option<&str>) -> bool {
let mut fragments = HashMap::new();
for definition in self.definitions.drain(..) {
match definition.node {
Definition::Operation(operation_definition) if self.current_operation.is_none() => {
match operation_definition.node {
OperationDefinition::SelectionSet(s) => {
self.current_operation = Some(CurrentOperation {
ty: OperationType::Query,
variable_definitions: Vec::new(),
selection_set: s,
});
}
OperationDefinition::Query(query)
if query.name.is_none()
|| operation_name.is_none()
|| query.name.as_ref().map(|name| name.node.as_str())
== operation_name.as_deref() =>
{
self.current_operation = Some(CurrentOperation {
ty: OperationType::Query,
variable_definitions: query.node.variable_definitions,
selection_set: query.node.selection_set,
});
}
OperationDefinition::Mutation(mutation)
if mutation.name.is_none()
|| operation_name.is_none()
|| mutation.name.as_ref().map(|name| name.node.as_str())
== operation_name =>
{
self.current_operation = Some(CurrentOperation {
ty: OperationType::Mutation,
variable_definitions: mutation.node.variable_definitions,
selection_set: mutation.node.selection_set,
});
}
OperationDefinition::Subscription(subscription)
if subscription.name.is_none()
|| operation_name.is_none()
|| subscription.name.as_ref().map(|name| name.node.as_str())
== operation_name =>
{
self.current_operation = Some(CurrentOperation {
ty: OperationType::Subscription,
variable_definitions: subscription.node.variable_definitions,
selection_set: subscription.node.selection_set,
});
}
_ => {}
}
}
Definition::Operation(_) => {}
Definition::Fragment(fragment) => {
fragments.insert(fragment.name.clone_inner(), fragment);
}
}
}
self.fragments = fragments;
self.current_operation.is_some()
}
}
#[derive(Debug, Clone)]
pub enum Definition {
Operation(Positioned<OperationDefinition>),
Fragment(Positioned<FragmentDefinition>),
}
#[derive(Debug, Clone)]
pub enum TypeCondition {
On(Positioned<String>),
}
#[derive(Debug, Clone)]
pub struct FragmentDefinition {
pub name: Positioned<String>,
pub type_condition: Positioned<TypeCondition>,
pub directives: Vec<Positioned<Directive>>,
pub selection_set: Positioned<SelectionSet>,
}
#[derive(Debug, Clone)]
pub enum OperationDefinition {
SelectionSet(Positioned<SelectionSet>),
Query(Positioned<Query>),
Mutation(Positioned<Mutation>),
Subscription(Positioned<Subscription>),
}
#[derive(Debug, Clone)]
pub struct Query {
pub name: Option<Positioned<String>>,
pub variable_definitions: Vec<Positioned<VariableDefinition>>,
pub directives: Vec<Positioned<Directive>>,
pub selection_set: Positioned<SelectionSet>,
}
#[derive(Debug, Clone)]
pub struct Mutation {
pub name: Option<Positioned<String>>,
pub variable_definitions: Vec<Positioned<VariableDefinition>>,
pub directives: Vec<Positioned<Directive>>,
pub selection_set: Positioned<SelectionSet>,
}
#[derive(Debug, Clone)]
pub struct Subscription {
pub name: Option<Positioned<String>>,
pub variable_definitions: Vec<Positioned<VariableDefinition>>,
pub directives: Vec<Positioned<Directive>>,
pub selection_set: Positioned<SelectionSet>,
}
#[derive(Debug, Default, Clone)]
pub struct SelectionSet {
pub items: Vec<Positioned<Selection>>,
}
#[derive(Debug, Clone)]
pub struct VariableDefinition {
pub name: Positioned<String>,
pub var_type: Positioned<Type>,
pub default_value: Option<Positioned<Value>>,
}
#[derive(Debug, Clone)]
pub enum Selection {
Field(Positioned<Field>),
FragmentSpread(Positioned<FragmentSpread>),
InlineFragment(Positioned<InlineFragment>),
}
#[derive(Debug, Clone)]
pub struct Field {
pub alias: Option<Positioned<String>>,
pub name: Positioned<String>,
pub arguments: Vec<(Positioned<String>, Positioned<Value>)>,
pub directives: Vec<Positioned<Directive>>,
pub selection_set: Positioned<SelectionSet>,
}
impl Field {
pub fn get_argument(&self, name: &str) -> Option<&Positioned<Value>> {
self.arguments
.iter()
.find(|item| item.0.node == name)
.map(|item| &item.1)
}
}
#[derive(Debug, Clone)]
pub struct FragmentSpread {
pub fragment_name: Positioned<String>,
pub directives: Vec<Positioned<Directive>>,
}
#[derive(Debug, Clone)]
pub struct InlineFragment {
pub type_condition: Option<Positioned<TypeCondition>>,
pub directives: Vec<Positioned<Directive>>,
pub selection_set: Positioned<SelectionSet>,
}

View File

@ -1,530 +0,0 @@
use crate::pos::Positioned;
use crate::query::*;
use crate::utils::{unquote_string, PositionCalculator};
use crate::value::Value;
use crate::{Error, Result};
use pest::iterators::Pair;
use pest::Parser;
use std::collections::BTreeMap;
#[derive(Parser)]
#[grammar = "query.pest"]
struct QueryParser;
/// Parse a GraphQL query.
pub fn parse_query<T: AsRef<str>>(input: T) -> Result<Document> {
let document_pair: Pair<Rule> = QueryParser::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::named_operation_definition => definitions
.push(parse_named_operation_definition(pair, &mut pc)?.pack(Definition::Operation)),
Rule::selection_set => definitions.push(
parse_selection_set(pair, &mut pc)?
.pack(OperationDefinition::SelectionSet)
.pack(Definition::Operation),
),
Rule::fragment_definition => definitions
.push(parse_fragment_definition(pair, &mut pc)?.pack(Definition::Fragment)),
Rule::EOI => {}
_ => unreachable!(),
}
}
Ok(Document {
definitions,
fragments: Default::default(),
current_operation: None,
})
}
fn parse_named_operation_definition(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<OperationDefinition>> {
enum OperationType {
Query,
Mutation,
Subscription,
}
let pos = pc.step(&pair);
let mut operation_type = OperationType::Query;
let mut name = None;
let mut variable_definitions = None;
let mut directives = None;
let mut selection_set = None;
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::operation_type => {
operation_type = match pair.as_str() {
"query" => OperationType::Query,
"mutation" => OperationType::Mutation,
"subscription" => OperationType::Subscription,
_ => unreachable!(),
};
}
Rule::name => {
name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair)));
}
Rule::variable_definitions => {
variable_definitions = Some(parse_variable_definitions(pair, pc)?);
}
Rule::directives => {
directives = Some(parse_directives(pair, pc)?);
}
Rule::selection_set => {
selection_set = Some(parse_selection_set(pair, pc)?);
}
_ => unreachable!(),
}
}
Ok(match operation_type {
OperationType::Query => Positioned::new(
Query {
name,
variable_definitions: variable_definitions.unwrap_or_default(),
directives: directives.unwrap_or_default(),
selection_set: selection_set.unwrap(),
},
pos,
)
.pack(OperationDefinition::Query),
OperationType::Mutation => Positioned::new(
Mutation {
name,
variable_definitions: variable_definitions.unwrap_or_default(),
directives: directives.unwrap_or_default(),
selection_set: selection_set.unwrap(),
},
pos,
)
.pack(OperationDefinition::Mutation),
OperationType::Subscription => Positioned::new(
Subscription {
name,
variable_definitions: variable_definitions.unwrap_or_default(),
directives: directives.unwrap_or_default(),
selection_set: selection_set.unwrap(),
},
pos,
)
.pack(OperationDefinition::Subscription),
})
}
fn parse_default_value(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Value> {
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::value => return Ok(parse_value2(pair, pc)?),
_ => unreachable!(),
}
}
unreachable!()
}
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_variable_definition(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<VariableDefinition>> {
let pos = pc.step(&pair);
let mut variable = None;
let mut ty = None;
let mut default_value = None;
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::variable => variable = Some(parse_variable(pair, pc)?),
Rule::type_ => {
ty = {
let pos = pc.step(&pair);
Some(Positioned::new(parse_type(pair, pc)?, pos))
}
}
Rule::default_value => {
let pos = pc.step(&pair);
default_value = Some(Positioned::new(parse_default_value(pair, pc)?, pos))
}
_ => unreachable!(),
}
}
Ok(Positioned::new(
VariableDefinition {
name: variable.unwrap(),
var_type: ty.unwrap(),
default_value,
},
pos,
))
}
fn parse_variable_definitions(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Result<Vec<Positioned<VariableDefinition>>> {
let mut vars = Vec::new();
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::variable_definition => vars.push(parse_variable_definition(pair, pc)?),
_ => unreachable!(),
}
}
Ok(vars)
}
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_variable(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<String>> {
for pair in pair.into_inner() {
if let Rule::name = pair.as_rule() {
return Ok(Positioned::new(pair.as_str().to_string(), pc.step(&pair)));
}
}
unreachable!()
}
fn parse_value2(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::variable => Value::Variable(parse_variable(pair, pc)?.into_inner()),
Rule::float | Rule::int => {
let pos = pc.step(&pair);
Value::Number(
pair.as_str()
.parse()
.map_err(|err: serde_json::Error| Error {
pos,
message: err.to_string(),
})?,
)
}
Rule::string => Value::String({
let pos = pc.step(&pair);
unquote_string(pair.as_str(), pos)?
}),
Rule::name => Value::Enum(pair.as_str().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_value2(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_value2(pair, pc)?),
_ => unreachable!(),
}
}
Ok(Value::List(array))
}
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_value2(pair, pc)?, pos))
}
}
_ => unreachable!(),
}
}
Ok((name.unwrap(), value.unwrap()))
}
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_alias(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<String>> {
for pair in pair.into_inner() {
if let Rule::name = pair.as_rule() {
return Ok(Positioned::new(pair.as_str().to_string(), pc.step(&pair)));
}
}
unreachable!()
}
fn parse_field(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Field>> {
let pos = pc.step(&pair);
let mut alias = None;
let mut name = None;
let mut directives = None;
let mut arguments = None;
let mut selection_set = None;
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::alias => alias = Some(parse_alias(pair, pc)?),
Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))),
Rule::arguments => arguments = Some(parse_arguments(pair, pc)?),
Rule::directives => directives = Some(parse_directives(pair, pc)?),
Rule::selection_set => selection_set = Some(parse_selection_set(pair, pc)?),
_ => unreachable!(),
}
}
Ok(Positioned::new(
Field {
alias,
name: name.unwrap(),
arguments: arguments.unwrap_or_default(),
directives: directives.unwrap_or_default(),
selection_set: selection_set.unwrap_or_default(),
},
pos,
))
}
fn parse_fragment_spread(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<FragmentSpread>> {
let pos = pc.step(&pair);
let mut name = None;
let mut directives = 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::directives => directives = Some(parse_directives(pair, pc)?),
_ => unreachable!(),
}
}
Ok(Positioned::new(
FragmentSpread {
fragment_name: name.unwrap(),
directives: directives.unwrap_or_default(),
},
pos,
))
}
fn parse_type_condition(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<TypeCondition>> {
for pair in pair.into_inner() {
if let Rule::name = pair.as_rule() {
let pos = pc.step(&pair);
return Ok(Positioned::new(
TypeCondition::On(Positioned::new(pair.as_str().to_string(), pc.step(&pair))),
pos,
));
}
}
unreachable!()
}
fn parse_inline_fragment(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<InlineFragment>> {
let pos = pc.step(&pair);
let mut type_condition = None;
let mut directives = None;
let mut selection_set = None;
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::type_condition => type_condition = Some(parse_type_condition(pair, pc)?),
Rule::directives => directives = Some(parse_directives(pair, pc)?),
Rule::selection_set => selection_set = Some(parse_selection_set(pair, pc)?),
_ => unreachable!(),
}
}
Ok(Positioned::new(
InlineFragment {
type_condition,
directives: directives.unwrap_or_default(),
selection_set: selection_set.unwrap(),
},
pos,
))
}
fn parse_selection_set(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<SelectionSet>> {
let pos = pc.step(&pair);
let mut items = Vec::new();
for pair in pair.into_inner().map(|pair| pair.into_inner()).flatten() {
match pair.as_rule() {
Rule::field => items.push(parse_field(pair, pc)?.pack(Selection::Field)),
Rule::fragment_spread => {
items.push(parse_fragment_spread(pair, pc)?.pack(Selection::FragmentSpread))
}
Rule::inline_fragment => {
items.push(parse_inline_fragment(pair, pc)?.pack(Selection::InlineFragment))
}
_ => unreachable!(),
}
}
Ok(Positioned::new(SelectionSet { items }, pos))
}
fn parse_fragment_definition(
pair: Pair<Rule>,
pc: &mut PositionCalculator,
) -> Result<Positioned<FragmentDefinition>> {
let pos = pc.step(&pair);
let mut name = None;
let mut type_condition = None;
let mut directives = None;
let mut selection_set = 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::type_condition => type_condition = Some(parse_type_condition(pair, pc)?),
Rule::directives => directives = Some(parse_directives(pair, pc)?),
Rule::selection_set => selection_set = Some(parse_selection_set(pair, pc)?),
_ => unreachable!(),
}
}
Ok(Positioned::new(
FragmentDefinition {
name: name.unwrap(),
type_condition: type_condition.unwrap(),
directives: directives.unwrap_or_default(),
selection_set: selection_set.unwrap(),
},
pos,
))
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_parser() {
for entry in fs::read_dir("tests/queries").unwrap() {
if let Ok(entry) = entry {
QueryParser::parse(Rule::document, &fs::read_to_string(entry.path()).unwrap())
.unwrap();
}
}
}
#[test]
fn test_parser_ast() {
for entry in fs::read_dir("tests/queries").unwrap() {
if let Ok(entry) = entry {
parse_query(fs::read_to_string(entry.path()).unwrap()).unwrap();
}
}
}
#[test]
fn test_parse_overflowing_int() {
let query_ok = format!("mutation {{ add(big: {}) }} ", std::i32::MAX);
let query_overflow = format!("mutation {{ add(big: {}0000) }} ", std::i32::MAX);
assert!(parse_query(query_ok).is_ok());
assert!(parse_query(query_overflow).is_ok());
}
}

View File

@ -1,72 +0,0 @@
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 ~ implements_interfaces? ~ 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

@ -1,172 +0,0 @@
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 implements_interfaces: Vec<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

@ -1,766 +0,0 @@
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)?
}),
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 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(
InterfaceType {
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_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,542 @@
//! GraphQL types.
use crate::pos::{Pos, Positioned};
use std::fmt::{self, Formatter, Write};
use serde::{Serialize, Serializer};
use std::collections::{HashMap, BTreeMap};
use std::fs::File;
/// A complete GraphQL file or request string.
///
/// [Reference](https://spec.graphql.org/June2018/#Document).
#[derive(Debug, Clone)]
pub struct Document {
/// The definitions of the document.
pub definitions: Vec<Definition>,
}
impl Document {
/// Convert the document into an [`ExecutableDocument`](struct.ExecutableDocument). Will return
/// `None` if there is no operation in the document.
///
/// The `operation_name` parameter, if set, makes sure that the main operation of the document,
/// if named, will have that name.
#[must_use]
pub fn into_executable(self, operation_name: Option<&str>) -> Option<ExecutableDocument> {
let mut operation = None;
let mut fragments = HashMap::new();
for definition in self.definitions {
match definition {
Definition::Operation(op) if operation_name.zip(op.node.name.as_ref()).map_or(false, |(required_name, op_name)| required_name != op_name.node) => (),
Definition::Operation(op) => {
operation.get_or_insert(op);
}
Definition::Fragment(fragment) => {
fragments.insert(fragment.node.name.node.clone(), fragment);
}
}
}
operation.map(|operation| ExecutableDocument {
operation,
fragments,
})
}
}
/// An executable document. This is a [`Document`](struct.Document.html) with at least one
/// operation, and any number of fragments.
#[derive(Debug, Clone)]
pub struct ExecutableDocument {
/// The main operation of the document.
pub operation: Positioned<OperationDefinition>,
/// The fragments of the document.
pub fragments: HashMap<String, Positioned<FragmentDefinition>>,
}
/// An executable definition in a query; a query, mutation, subscription or fragment definition.
///
/// [Reference](https://spec.graphql.org/June2018/#ExecutableDefinition).
#[derive(Debug, Clone)]
pub enum Definition {
/// The definition of an operation.
Operation(Positioned<OperationDefinition>),
/// The definition of a fragment.
Fragment(Positioned<FragmentDefinition>),
}
impl Definition {
/// Get the position of the definition.
#[must_use]
pub fn pos(&self) -> Pos {
match self {
Self::Operation(op) => op.pos,
Self::Fragment(frag) => frag.pos,
}
}
/// Get a reference to the directives of the definition.
#[must_use]
pub fn directives(&self) -> &Vec<Positioned<Directive>> {
match self {
Self::Operation(op) => &op.node.directives,
Self::Fragment(frag) => &frag.node.directives,
}
}
/// Get a mutable reference to the directives of the definition.
#[must_use]
pub fn directives_mut(&mut self) -> &mut Vec<Positioned<Directive>> {
match self {
Self::Operation(op) => &mut op.node.directives,
Self::Fragment(frag) => &mut frag.node.directives,
}
}
}
/// A GraphQL operation, such as `mutation($content:String!) { makePost(content: $content) { id } }`.
///
/// [Reference](https://spec.graphql.org/June2018/#OperationDefinition).
#[derive(Debug, Clone)]
pub struct OperationDefinition {
/// The type of operation.
pub ty: OperationType,
/// The name of the operation.
pub name: Option<Positioned<String>>,
/// The variable definitions.
pub variable_definitions: Vec<Positioned<VariableDefinition>>,
/// The operation's directives.
pub directives: Vec<Positioned<Directive>>,
/// The operation's selection set.
pub selection_set: Positioned<SelectionSet>,
}
/// The type of an operation; `query`, `mutation` or `subscription`.
///
/// [Reference](https://spec.graphql.org/June2018/#OperationType).
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum OperationType {
/// A query.
Query,
/// A mutation.
Mutation,
/// A subscription.
Subscription,
}
impl fmt::Display for OperationType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
Self::Query => "query",
Self::Mutation => "mutation",
Self::Subscription => "subscription",
})
}
}
/// A variable definition inside a list of variable definitions, for example `$name:String!`.
///
/// [Reference](https://spec.graphql.org/June2018/#VariableDefinition).
#[derive(Debug, Clone)]
pub struct VariableDefinition {
/// The name of the variable, without the preceding `$`.
pub name: Positioned<String>,
/// The type of the variable.
pub var_type: Positioned<Type>,
/// The optional default value of the variable.
pub default_value: Option<Positioned<Value>>,
}
impl VariableDefinition {
/// Get the default value of the variable; this is `default_value` if it is present,
/// `Value::Null` if it is nullable and `None` otherwise.
#[must_use]
pub fn default_value(&self) -> Option<&Value> {
self.default_value.as_ref().map(|value| &value.node).or_else(|| if self.var_type.node.nullable {
Some(&Value::Null)
} else {
None
})
}
}
/// A GraphQL type, for example `String` or `[String!]!`.
///
/// [Reference](https://spec.graphql.org/June2018/#Type).
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Type {
/// The base type.
pub base: BaseType,
/// Whether the type is nullable.
pub nullable: bool,
}
impl Type {
/// Create a type from the type string.
#[must_use]
pub fn new(ty: &str) -> Self {
let (nullable, ty) = if let Some(rest) = ty.strip_suffix('!') {
(false, rest)
} else {
(true, ty)
};
Self {
base: if let Some(ty) = ty.strip_prefix('[').and_then(|ty| ty.strip_suffix(']')) {
BaseType::List(Box::new(Self::new(ty)))
} else {
BaseType::Named(ty.to_owned())
},
nullable,
}
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.base.fmt(f)?;
if !self.nullable {
f.write_char('!')?;
}
Ok(())
}
}
/// A GraphQL base type, for example `String` or `[String!]`. This does not include whether the
/// type is nullable; for that see [Type](struct.Type.html).
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum BaseType {
/// A named type, such as `String`.
Named(String),
/// A list type, such as `[String]`.
List(Box<Type>),
}
impl fmt::Display for BaseType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Named(name) => f.write_str(name),
Self::List(ty) => write!(f, "[{}]", ty),
}
}
}
/// A GraphQL value, for example `1`, `$name` or `"Hello World!"`.
///
/// [Reference](https://spec.graphql.org/June2018/#Value).
#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum Value {
/// `null`.
Null,
/// A variable, without the `$`.
#[serde(serialize_with = "serialize_variable")]
Variable(String),
/// A number.
Number(serde_json::Number),
/// A string.
String(String),
/// A boolean.
Boolean(bool),
/// An enum. These are typically in `SCREAMING_SNAKE_CASE`.
Enum(String),
/// A list of values.
List(Vec<Value>),
/// An object. This is a map of keys to values.
Object(BTreeMap<String, Value>),
/// An uploaded file.
#[serde(serialize_with = "serialize_upload")]
Upload(UploadValue),
}
fn serialize_variable<S: Serializer>(name: &str, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&format!("${}", name))
}
fn serialize_upload<S: Serializer>(_: &UploadValue, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_unit()
}
impl Default for Value {
fn default() -> Self {
Value::Null
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Variable(name) => write!(f, "${}", name),
Value::Number(num) => write!(f, "{}", *num),
Value::String(val) => write_quoted(val, f),
Value::Boolean(true) => f.write_str("true"),
Value::Boolean(false) => f.write_str("false"),
Value::Null | Value::Upload(_) => f.write_str("null"),
Value::Enum(name) => f.write_str(name),
Value::List(items) => {
f.write_char('[')?;
for (i, item) in items.iter().enumerate() {
if i != 0 {
f.write_str(", ")?;
}
item.fmt(f)?;
}
f.write_char(']')
}
Value::Object(items) => {
f.write_str("{")?;
for (i, (name, value)) in items.iter().enumerate() {
write!(f, "{}{}: {}", if i == 0 { "" } else { ", " }, name, value)?;
}
f.write_str("}")
}
}
}
}
fn write_quoted(s: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_char('"')?;
for c in s.chars() {
match c {
'\r' => f.write_str("\\r"),
'\n' => f.write_str("\\n"),
'\t' => f.write_str("\\t"),
'"' => f.write_str("\\\""),
'\\' => f.write_str("\\\\"),
c if c.is_control() => write!(f, "\\u{:04}", c as u32),
c => f.write_char(c),
}?
}
f.write_char('"')
}
impl From<Value> for serde_json::Value {
fn from(value: Value) -> Self {
match value {
Value::Null | Value::Upload(_) => serde_json::Value::Null,
Value::Variable(name) => name.into(),
Value::Number(n) => serde_json::Value::Number(n),
Value::String(s) => s.into(),
Value::Boolean(v) => v.into(),
Value::Enum(e) => e.into(),
Value::List(values) => values
.into_iter()
.map(Into::into)
.collect::<Vec<serde_json::Value>>()
.into(),
Value::Object(obj) => serde_json::Value::Object(
obj.into_iter()
.map(|(name, value)| (name, value.into()))
.collect(),
),
}
}
}
impl From<serde_json::Value> for Value {
fn from(value: serde_json::Value) -> Self {
match value {
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(n) => Value::Boolean(n),
serde_json::Value::Number(n) => Value::Number(n),
serde_json::Value::String(s) => Value::String(s),
serde_json::Value::Array(ls) => Value::List(ls.into_iter().map(Into::into).collect()),
serde_json::Value::Object(obj) => Value::Object(
obj.into_iter()
.map(|(name, value)| (name, value.into()))
.collect(),
),
}
}
}
/// A file upload value.
pub struct UploadValue {
/// The name of the file.
pub filename: String,
/// The content type of the file.
pub content_type: Option<String>,
/// The file data.
pub content: File,
}
impl UploadValue {
/// Attempt to clone the upload value. This type's `Clone` implementation simply calls this and
/// panics on failure.
///
/// # Errors
///
/// Fails if cloning the inner `File` fails.
pub fn try_clone(&self) -> std::io::Result<Self> {
Ok(Self {
filename: self.filename.clone(),
content_type: self.content_type.clone(),
content: self.content.try_clone()?,
})
}
}
impl Clone for UploadValue {
fn clone(&self) -> Self {
self.try_clone().unwrap()
}
}
impl fmt::Debug for UploadValue {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Upload({})", self.filename)
}
}
impl PartialEq for UploadValue {
fn eq(&self, other: &Self) -> bool {
self.filename == other.filename
}
}
impl Eq for UploadValue {}
/// A set of fields to be selected, for example `{ name age }`.
///
/// [Reference](https://spec.graphql.org/June2018/#SelectionSet).
#[derive(Debug, Default, Clone)]
pub struct SelectionSet {
/// The fields to be selected.
pub items: Vec<Positioned<Selection>>,
}
/// A part of an object to be selected; a single field, a fragment spread or an inline fragment.
///
/// [Reference](https://spec.graphql.org/June2018/#Selection).
#[derive(Debug, Clone)]
pub enum Selection {
/// Select a single field, such as `name` or `weightKilos: weight(unit: KILOGRAMS)`.
Field(Positioned<Field>),
/// Select using a fragment.
FragmentSpread(Positioned<FragmentSpread>),
/// Select using an inline fragment.
InlineFragment(Positioned<InlineFragment>),
}
impl Selection {
/// Get a reference to the directives of the selection.
#[must_use]
pub fn directives(&self) -> &Vec<Positioned<Directive>> {
match self {
Self::Field(field) => &field.node.directives,
Self::FragmentSpread(spread) => &spread.node.directives,
Self::InlineFragment(fragment) => &fragment.node.directives,
}
}
/// Get a mutable reference to the directives of the selection.
#[must_use]
pub fn directives_mut(&mut self) -> &mut Vec<Positioned<Directive>> {
match self {
Self::Field(field) => &mut field.node.directives,
Self::FragmentSpread(spread) => &mut spread.node.directives,
Self::InlineFragment(fragment) => &mut fragment.node.directives,
}
}
}
/// A field being selected on an object, such as `name` or `weightKilos: weight(unit: KILOGRAMS)`.
///
/// [Reference](https://spec.graphql.org/June2018/#Field).
#[derive(Debug, Clone)]
pub struct Field {
/// The optional field alias.
pub alias: Option<Positioned<String>>,
/// The name of the field.
pub name: Positioned<String>,
/// The arguments to the field, empty if no arguments are provided.
pub arguments: Vec<(Positioned<String>, Positioned<Value>)>,
/// The directives in the field selector.
pub directives: Vec<Positioned<Directive>>,
/// The subfields being selected in this field, if it is an object. Empty if no fields are
/// being selected.
pub selection_set: Positioned<SelectionSet>,
}
impl Field {
/// Get the response key of the field. This is the alias if present and the name otherwise.
#[must_use]
pub fn response_key(&self) -> &Positioned<String> {
self.alias.as_ref().unwrap_or(&self.name)
}
/// Get the value of the argument with the specified name.
#[must_use]
pub fn get_argument(&self, name: &str) -> Option<&Positioned<Value>> {
self.arguments
.iter()
.find(|item| item.0.node == name)
.map(|item| &item.1)
}
}
/// A fragment selector, such as `... userFields`.
///
/// [Reference](https://spec.graphql.org/June2018/#FragmentSpread).
#[derive(Debug, Clone)]
pub struct FragmentSpread {
/// The name of the fragment being selected.
pub fragment_name: Positioned<String>,
/// The directives in the fragment selector.
pub directives: Vec<Positioned<Directive>>,
}
/// An inline fragment selector, such as `... on User { name }`.
///
/// [Reference](https://spec.graphql.org/June2018/#InlineFragment).
#[derive(Debug, Clone)]
pub struct InlineFragment {
/// The type condition.
pub type_condition: Option<Positioned<TypeCondition>>,
/// The directives in the inline fragment.
pub directives: Vec<Positioned<Directive>>,
/// The selected fields of the fragment.
pub selection_set: Positioned<SelectionSet>,
}
/// The definition of a fragment, such as `fragment userFields on User { name age }`.
///
/// [Reference](https://spec.graphql.org/June2018/#FragmentDefinition).
#[derive(Debug, Clone)]
pub struct FragmentDefinition {
/// The name of the fragment. Any name is allowed except `on`.
pub name: Positioned<String>,
/// The type this fragment operates on.
pub type_condition: Positioned<TypeCondition>,
/// Directives in the fragment.
pub directives: Vec<Positioned<Directive>>,
/// The fragment's selection set.
pub selection_set: Positioned<SelectionSet>,
}
/// A type a fragment can apply to (`on` followed by the type).
///
/// [Reference](https://spec.graphql.org/June2018/#TypeCondition).
#[derive(Debug, Clone)]
pub struct TypeCondition {
/// The type this fragment applies to.
pub on: Positioned<String>,
}
/// A GraphQL directive, such as `@deprecated(reason: "Use the other field")`.
///
/// [Reference](https://spec.graphql.org/June2018/#Directive).
#[derive(Debug, Clone)]
pub struct Directive {
/// The name of the directive.
pub name: Positioned<String>,
/// The arguments to the directive.
pub arguments: Vec<(Positioned<String>, Positioned<Value>)>,
}
impl Directive {
/// Get the argument with the given name.
#[must_use]
pub fn get_argument(&self, name: &str) -> Option<&Positioned<Value>> {
self.arguments
.iter()
.find(|item| item.0.node == name)
.map(|item| &item.1)
}
}

View File

@ -1,12 +1,10 @@
use crate::{Error, Pos, Result};
use arrayvec::ArrayVec;
use crate::Pos;
use pest::iterators::Pair;
use pest::RuleType;
use std::iter::Peekable;
use std::str::Chars;
pub struct PositionCalculator<'a> {
input: Peekable<Chars<'a>>,
input: Chars<'a>,
pos: usize,
line: usize,
column: usize,
@ -15,7 +13,7 @@ pub struct PositionCalculator<'a> {
impl<'a> PositionCalculator<'a> {
pub fn new(input: &'a str) -> PositionCalculator<'a> {
Self {
input: input.chars().peekable(),
input: input.chars(),
pos: 0,
line: 1,
column: 1,
@ -28,13 +26,7 @@ impl<'a> PositionCalculator<'a> {
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;
}
self.column = 1;
}
Some('\n') => {
self.line += 1;
@ -54,88 +46,105 @@ impl<'a> PositionCalculator<'a> {
}
}
pub fn unquote_string(s: &str, pos: Pos) -> Result<String> {
let s = if s.starts_with(r#"""""#) {
&s[3..s.len() - 3]
} else if s.starts_with('"') {
&s[1..s.len() - 1]
} else {
unreachable!()
};
// See https://spec.graphql.org/June2018/#BlockStringValue()
pub(crate) fn block_string_value(raw: &str) -> String {
// Split the string by either \r\n, \r or \n
let lines: Vec<_> = raw
.split("\r\n")
.flat_map(|s| s.split(['\r', '\n'].as_ref()))
.collect();
let mut chars = s.chars();
let mut res = String::with_capacity(s.len());
let mut temp_code_point = ArrayVec::<[u8; 4]>::new();
// Find the common indent
let common_indent = lines
.iter()
.skip(1)
.copied()
.filter_map(|line| line.find(|c| c != '\t' && c != ' '))
.min()
.unwrap_or(0);
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",
std::str::from_utf8(temp_code_point.as_slice())
.unwrap()
),
});
}
}
}
let line_has_content = |line: &str| line.as_bytes().iter().any(|&c| c != b'\t' && c != b' ');
// convert our hex string into a u32, then convert that into a char
match u32::from_str_radix(
std::str::from_utf8(temp_code_point.as_slice()).unwrap(),
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",
std::str::from_utf8(temp_code_point.as_slice()).unwrap()
),
});
}
}
}
c => {
return Err(Error {
pos,
message: format!("bad escaped char {:?}", c),
});
}
}
let first_contentful_line = lines
.iter()
.copied()
.position(line_has_content)
.unwrap_or_else(|| lines.len());
let ending_lines_start = lines
.iter()
.copied()
.rposition(line_has_content)
.map_or(0, |i| i + 1);
lines
.iter()
.copied()
.enumerate()
.take(ending_lines_start)
.skip(first_contentful_line)
// Remove the common indent, but not on the first line
.map(|(i, line)| if i == 0 { line } else { &line[common_indent..] })
// Put a newline between each line
.enumerate()
.flat_map(|(i, line)| {
if i == 0 {
[].as_ref()
} else {
['\n'].as_ref()
}
c => res.push(c),
}
}
Ok(res)
.iter()
.copied()
.chain(line.chars())
})
.collect()
}
#[test]
fn test_block_string_value() {
assert_eq!(block_string_value(""), "");
assert_eq!(block_string_value("\r\n"), "");
assert_eq!(block_string_value("\r\r\r\r\n\n\r\n\r\r"), "");
assert_eq!(block_string_value("abc"), "abc");
assert_eq!(
block_string_value("line 1\r\n line 2\n line 3\r line 4"),
"line 1\nline 2\n line 3\n line 4"
);
dbg!();
assert_eq!(
block_string_value("\r\r some text\r\n \n \n "),
"some text"
);
}
pub(crate) fn string_value(s: &str) -> String {
let mut chars = s.chars();
std::iter::from_fn(|| {
Some(match chars.next()? {
'\\' => match chars.next().expect("backslash at end") {
c @ '\"' | c @ '\\' | c @ '/' => c,
'b' => '\x08',
'f' => '\x0C',
'n' => '\n',
'r' => '\r',
't' => '\t',
'u' => std::char::from_u32(
(0..4)
.map(|_| chars.next().unwrap().to_digit(16).unwrap())
.fold(0, |acc, digit| acc * 16 + digit),
)
.unwrap(),
_ => unreachable!(),
},
other => other,
})
})
.collect()
}
#[test]
fn test_string_value() {
assert_eq!(string_value("abc"), "abc");
assert_eq!(string_value("\\n\\b\\u2a1A"), "\n\x08\u{2A1A}");
assert_eq!(string_value("\\\"\\\\"), "\"\\");
}

View File

@ -1,221 +0,0 @@
use serde::ser::{SerializeMap, SerializeSeq};
use serde::Serializer;
use std::collections::BTreeMap;
use std::fmt;
use std::fmt::Formatter;
use std::fs::File;
pub struct UploadValue {
pub filename: String,
pub content_type: Option<String>,
pub content: File,
}
impl fmt::Debug for UploadValue {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Upload({})", self.filename)
}
}
impl Clone for UploadValue {
fn clone(&self) -> Self {
Self {
filename: self.filename.clone(),
content_type: self.content_type.clone(),
content: self.content.try_clone().unwrap(),
}
}
}
/// Represents a GraphQL value
#[derive(Clone, Debug)]
#[allow(missing_docs)]
pub enum Value {
Null,
Variable(String),
Number(serde_json::Number),
String(String),
Boolean(bool),
Enum(String),
List(Vec<Value>),
Object(BTreeMap<String, Value>),
Upload(UploadValue),
}
impl serde::Serialize for Value {
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
match self {
Value::Null => serializer.serialize_none(),
Value::Variable(variable) => serializer.serialize_str(&format!("${}", variable)),
Value::Number(value) => value.serialize(serializer),
Value::String(value) => serializer.serialize_str(value),
Value::Boolean(value) => serializer.serialize_bool(*value),
Value::Enum(value) => serializer.serialize_str(value),
Value::List(value) => {
let mut seq = serializer.serialize_seq(Some(value.len()))?;
for item in value {
seq.serialize_element(item)?;
}
seq.end()
}
Value::Object(value) => {
let mut map = serializer.serialize_map(Some(value.len()))?;
for (key, value) in value {
map.serialize_entry(key, value)?;
}
map.end()
}
Value::Upload(_) => serializer.serialize_none(),
}
}
}
impl Default for Value {
fn default() -> Self {
Value::Null
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
use Value::*;
match (self, other) {
(Variable(a), Variable(b)) => a.eq(b),
(Number(a), Number(b)) => a.eq(b),
(String(a), String(b)) => a.eq(b),
(Boolean(a), Boolean(b)) => a.eq(b),
(Null, Null) => true,
(Enum(a), Enum(b)) => a.eq(b),
(List(a), List(b)) => {
if a.len() != b.len() {
return false;
}
for i in 0..a.len() {
if !a[i].eq(&b[i]) {
return false;
}
}
true
}
(Object(a), Object(b)) => {
if a.len() != b.len() {
return false;
}
for (key, a_value) in a.iter() {
if let Some(b_value) = b.get(key) {
if !a_value.eq(b_value) {
return false;
}
} else {
return false;
}
}
true
}
(Upload(a), Upload(b)) => a.filename == b.filename,
_ => false,
}
}
}
fn write_quoted(s: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"")?;
for c in s.chars() {
match c {
'\r' => write!(f, "\r")?,
'\n' => writeln!(f)?,
'\t' => write!(f, "\t")?,
'"' => write!(f, "\"")?,
'\\' => write!(f, "\\")?,
'\u{0020}'..='\u{FFFF}' => write!(f, "{}", c)?,
_ => write!(f, "\\u{:04}", c as u32).unwrap(),
}
}
write!(f, "\"")
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Variable(name) => write!(f, "${}", name),
Value::Number(num) => write!(f, "{}", *num),
Value::String(ref val) => write_quoted(val, f),
Value::Boolean(true) => write!(f, "true"),
Value::Boolean(false) => write!(f, "false"),
Value::Null => write!(f, "null"),
Value::Enum(ref name) => write!(f, "{}", name),
Value::List(ref items) => {
write!(f, "[")?;
if !items.is_empty() {
write!(f, "{}", items[0])?;
for item in &items[1..] {
write!(f, ", ")?;
write!(f, "{}", item)?;
}
}
write!(f, "]")
}
Value::Object(items) => {
write!(f, "{{")?;
let mut first = true;
for (name, value) in items {
if first {
first = false;
} else {
write!(f, ", ")?;
}
write!(f, "{}", name)?;
write!(f, ": ")?;
write!(f, "{}", value)?;
}
write!(f, "}}")
}
Value::Upload(_) => write!(f, "null"),
}
}
}
impl From<Value> for serde_json::Value {
fn from(value: Value) -> Self {
match value {
Value::Null => serde_json::Value::Null,
Value::Variable(name) => name.into(),
Value::Number(n) => serde_json::Value::Number(n),
Value::String(s) => s.into(),
Value::Boolean(v) => v.into(),
Value::Enum(e) => e.into(),
Value::List(values) => values
.into_iter()
.map(Into::into)
.collect::<Vec<serde_json::Value>>()
.into(),
Value::Object(obj) => serde_json::Value::Object(
obj.into_iter()
.map(|(name, value)| (name, value.into()))
.collect(),
),
Value::Upload(_) => serde_json::Value::Null,
}
}
}
impl From<serde_json::Value> for Value {
fn from(value: serde_json::Value) -> Self {
match value {
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(n) => Value::Boolean(n),
serde_json::Value::Number(n) => Value::Number(n),
serde_json::Value::String(s) => Value::String(s),
serde_json::Value::Array(ls) => Value::List(ls.into_iter().map(Into::into).collect()),
serde_json::Value::Object(obj) => Value::Object(
obj.into_iter()
.map(|(name, value)| (name, value.into()))
.collect(),
),
}
}
}

View File

@ -3,7 +3,7 @@ use crate::{
registry, Context, ContextSelectionSet, FieldResult, InputValueResult, Positioned, QueryError,
Result, Value,
};
use async_graphql_parser::query::Field;
use crate::parser::types::Field;
use std::borrow::Cow;
use std::future::Future;
use std::pin::Pin;
@ -34,10 +34,10 @@ pub trait Type {
/// Represents a GraphQL input value
pub trait InputValueType: Type + Sized {
/// Parse from `Value`None represent undefined.
/// Parse from `Value`. None represents undefined.
fn parse(value: Option<Value>) -> InputValueResult<Self>;
/// Convert to `Value` for introspection
/// Convert to a `Value` for introspection.
fn to_value(&self) -> Value;
}
@ -95,7 +95,7 @@ pub trait ObjectType: OutputValueType {
/// Query entities with params
async fn find_entity(&self, ctx: &Context<'_>, _params: &Value) -> Result<serde_json::Value> {
Err(QueryError::EntityNotFound.into_error(ctx.position()))
Err(QueryError::EntityNotFound.into_error(ctx.pos))
}
}
@ -243,7 +243,7 @@ impl<T: OutputValueType + Sync> OutputValueType for FieldResult<T> {
Ok(value) => Ok(OutputValueType::resolve(value, ctx, field).await?),
Err(err) => Err(err
.clone()
.into_error_with_path(field.position(), ctx.path_node.as_ref())),
.into_error_with_path(field.pos, ctx.path_node.as_ref())),
}
}
}

View File

@ -1,128 +1,65 @@
use crate::extensions::Extensions;
use crate::parser::query::{Directive, Field, SelectionSet};
use crate::schema::SchemaEnv;
use crate::{
FieldResult, InputValueType, Lookahead, Pos, Positioned, QueryError, Result, Type, Value,
use crate::parser::types::{
Directive, ExecutableDocument, Field, SelectionSet, Value,
};
use async_graphql_parser::query::Document;
use async_graphql_parser::UploadValue;
use crate::schema::SchemaEnv;
use crate::base::Type;
use crate::{FieldResult, InputValueType, Lookahead, Pos, Positioned, QueryError, Result};
use fnv::FnvHashMap;
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};
use std::any::{Any, TypeId};
use std::collections::BTreeMap;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::ops::{Deref, DerefMut};
use std::convert::TryFrom;
use std::fmt::{self, Display, Formatter};
use std::ops::Deref;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
/// Variables of query
#[derive(Debug, Clone, Serialize)]
pub struct Variables(Value);
impl Default for Variables {
fn default() -> Self {
Self(Value::Object(Default::default()))
}
}
impl Deref for Variables {
type Target = BTreeMap<String, Value>;
fn deref(&self) -> &Self::Target {
if let Value::Object(obj) = &self.0 {
obj
} else {
unreachable!()
}
}
}
/// Variables of a query.
#[derive(Debug, Clone, Default, Serialize)]
pub struct Variables(pub BTreeMap<String, Value>);
impl Display for Variables {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl DerefMut for Variables {
fn deref_mut(&mut self) -> &mut Self::Target {
if let Value::Object(obj) = &mut self.0 {
obj
} else {
unreachable!()
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("{")?;
for (i, (name, value)) in self.0.iter().enumerate() {
write!(f, "{}{}: {}", if i == 0 { "" } else { ", " }, name, value)?;
}
f.write_str("}")
}
}
impl Variables {
/// Parse variables from JSON object.
// TODO: Is this supposed to be able to return an error?
pub fn parse_from_json(value: serde_json::Value) -> Result<Self> {
if let Value::Object(obj) = value.into() {
Ok(Variables(Value::Object(obj)))
Ok(if let Value::Object(obj) = value.into() {
Self(obj)
} else {
Ok(Default::default())
}
Default::default()
})
}
pub(crate) fn set_upload(
&mut self,
var_path: &str,
filename: String,
content_type: Option<String>,
content: File,
) {
let mut it = var_path.split('.').peekable();
pub(crate) fn variable_path(&mut self, path: &str) -> Option<&mut Value> {
let mut parts = path.strip_prefix("variables.")?.split('.');
if let Some(first) = it.next() {
if first != "variables" {
return;
}
}
let initial = self.0.get_mut(parts.next().unwrap())?;
let mut current = &mut self.0;
while let Some(s) = it.next() {
let has_next = it.peek().is_some();
if let Ok(idx) = s.parse::<i32>() {
if let Value::List(ls) = current {
if let Some(value) = ls.get_mut(idx as usize) {
if !has_next {
*value = Value::Upload(UploadValue {
filename,
content_type,
content,
});
return;
} else {
current = value;
}
} else {
return;
}
}
} else if let Value::Object(obj) = current {
if let Some(value) = obj.get_mut(s) {
if !has_next {
*value = Value::Upload(UploadValue {
filename,
content_type,
content,
});
return;
} else {
current = value;
}
} else {
return;
}
}
}
parts.try_fold(initial, |current, part| match current {
Value::List(list) => part
.parse::<u32>()
.ok()
.and_then(|idx| usize::try_from(idx).ok())
.and_then(move |idx| list.get_mut(idx)),
Value::Object(obj) => obj.get_mut(part),
_ => None,
})
}
}
/// Schema/Context data.
#[derive(Default)]
/// Schema/Context data
pub struct Data(FnvHashMap<TypeId, Box<dyn Any + Sync + Send>>);
impl Data {
@ -179,8 +116,8 @@ impl<'a> serde::Serialize for QueryPathNode<'a> {
}
}
impl<'a> std::fmt::Display for QueryPathNode<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl<'a> Display for QueryPathNode<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut first = true;
self.for_each(|segment| {
if !first {
@ -242,8 +179,8 @@ impl ResolveId {
}
}
impl std::fmt::Display for ResolveId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl Display for ResolveId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if let Some(parent) = self.parent {
write!(f, "{}:{}", parent, self.current)
} else {
@ -277,7 +214,7 @@ impl<'a, T> Deref for ContextBase<'a, T> {
pub struct QueryEnvInner {
pub extensions: spin::Mutex<Extensions>,
pub variables: Variables,
pub document: Document,
pub document: ExecutableDocument,
pub ctx_data: Arc<Data>,
}
@ -298,7 +235,7 @@ impl QueryEnv {
pub fn new(
extensions: spin::Mutex<Extensions>,
variables: Variables,
document: Document,
document: ExecutableDocument,
ctx_data: Arc<Data>,
) -> QueryEnv {
QueryEnv(Arc::new(QueryEnvInner {
@ -348,13 +285,7 @@ impl<'a, T> ContextBase<'a, T> {
ContextBase {
path_node: Some(QueryPathNode {
parent: self.path_node.as_ref(),
segment: QueryPathSegment::Name(
field
.alias
.as_ref()
.map(|alias| alias.as_str())
.unwrap_or_else(|| field.name.as_str()),
),
segment: QueryPathSegment::Name(&field.node.response_key().node),
}),
item: field,
resolve_id: self.get_child_resolve_id(),
@ -383,7 +314,9 @@ impl<'a, T> ContextBase<'a, T> {
///
/// If both `Schema` and `Query` have the same data type, the data in the `Query` is obtained.
///
/// Returns a FieldError if the specified type data does not exist.
/// # Errors
///
/// Returns a `FieldError` if the specified type data does not exist.
pub fn data<D: Any + Send + Sync>(&self) -> FieldResult<&D> {
self.data_opt::<D>()
.ok_or_else(|| format!("Data `{}` does not exist.", std::any::type_name::<D>()).into())
@ -399,7 +332,7 @@ impl<'a, T> ContextBase<'a, T> {
.unwrap_or_else(|| panic!("Data `{}` does not exist.", std::any::type_name::<D>()))
}
/// Gets the global data defined in the `Context` or `Schema`, returns `None` if the specified type data does not exist.
/// Gets the global data defined in the `Context` or `Schema` or `None` if the specified type data does not exist.
pub fn data_opt<D: Any + Send + Sync>(&self) -> Option<&D> {
self.query_env
.ctx_data
@ -410,109 +343,78 @@ impl<'a, T> ContextBase<'a, T> {
}
fn var_value(&self, name: &str, pos: Pos) -> Result<Value> {
let def = self
.query_env
self.query_env
.document
.current_operation()
.operation
.node
.variable_definitions
.iter()
.find(|def| def.name.node == name);
if let Some(def) = def {
if let Some(var_value) = self.query_env.variables.get(def.name.as_str()) {
return Ok(var_value.clone());
} else if let Some(default) = &def.default_value {
return Ok(default.clone_inner());
}
match def.var_type.deref() {
&async_graphql_parser::query::Type::Named(_)
| &async_graphql_parser::query::Type::List(_) => {
// Nullable types can default to null when not given.
return Ok(Value::Null);
.find(|def| def.node.name.node == name)
.and_then(|def| {
self.query_env
.variables
.0
.get(&def.node.name.node)
.or_else(|| def.node.default_value())
})
.cloned()
.ok_or_else(|| {
QueryError::VarNotDefined {
var_name: name.to_owned(),
}
&async_graphql_parser::query::Type::NonNull(_) => {
// Strict types can not.
}
}
}
Err(QueryError::VarNotDefined {
var_name: name.to_string(),
}
.into_error(pos))
.into_error(pos)
})
}
fn resolved_input_value(&self, mut value: Positioned<Value>) -> Result<Value> {
self.resolve_input_value(&mut value.node, value.pos)?;
Ok(value.node)
}
fn resolve_input_value(&self, value: &mut Value, pos: Pos) -> Result<()> {
match value {
Value::Variable(var_name) => {
*value = self.var_value(&var_name, pos)?;
Ok(())
}
Value::List(ref mut ls) => {
for value in ls {
self.resolve_input_value(value, pos)?;
Value::Variable(var_name) => *value = self.var_value(&var_name, pos)?,
Value::List(ls) => {
for ls_value in ls {
self.resolve_input_value(ls_value, pos)?;
}
Ok(())
}
Value::Object(ref mut obj) => {
for value in obj.values_mut() {
self.resolve_input_value(value, pos)?;
Value::Object(obj) => {
for obj_value in obj.values_mut() {
self.resolve_input_value(obj_value, pos)?;
}
Ok(())
}
_ => Ok(()),
_ => (),
}
Ok(())
}
#[doc(hidden)]
pub fn is_ifdef(&self, directives: &[Positioned<Directive>]) -> bool {
for directive in directives {
if directive.name.node == "ifdef" {
return true;
}
}
false
directives
.iter()
.any(|directive| directive.node.name.node == "ifdef")
}
#[doc(hidden)]
pub fn is_skip(&self, directives: &[Positioned<Directive>]) -> Result<bool> {
for directive in directives {
if directive.name.node == "skip" {
if let Some(value) = directive.get_argument("if") {
let mut inner_value = value.clone_inner();
self.resolve_input_value(&mut inner_value, value.pos)?;
match InputValueType::parse(Some(inner_value)) {
Ok(true) => return Ok(true),
Ok(false) => {}
Err(err) => {
return Err(err.into_error(value.pos, bool::qualified_type_name()))
}
}
} else {
return Err(QueryError::RequiredDirectiveArgs {
directive: "@skip",
arg_name: "if",
arg_type: "Boolean!",
}
.into_error(directive.position()));
}
} else if directive.name.node == "include" {
if let Some(value) = directive.get_argument("if") {
let mut inner_value = value.clone_inner();
self.resolve_input_value(&mut inner_value, value.pos)?;
match InputValueType::parse(Some(inner_value)) {
Ok(false) => return Ok(true),
Ok(true) => {}
Err(err) => {
return Err(err.into_error(value.pos, bool::qualified_type_name()))
}
}
} else {
return Err(QueryError::RequiredDirectiveArgs {
directive: "@include",
arg_name: "if",
arg_type: "Boolean!",
}
.into_error(directive.position()));
}
let include = match &*directive.node.name.node {
"skip" => false,
"include" => true,
_ => continue,
};
let Positioned { node: mut condition_input, pos } = directive.node.get_argument("if").ok_or_else(|| QueryError::RequiredDirectiveArgs {
directive: if include { "@skip" } else { "@include" },
arg_name: "if",
arg_type: "Boolean!",
}.into_error(directive.pos))?.clone();
self.resolve_input_value(&mut condition_input, pos)?;
if include != <bool as InputValueType>::parse(Some(condition_input)).map_err(|e| e.into_error(pos, bool::qualified_type_name()))? {
return Ok(true);
}
}
@ -521,12 +423,12 @@ impl<'a, T> ContextBase<'a, T> {
#[doc(hidden)]
pub fn is_defer(&self, directives: &[Positioned<Directive>]) -> bool {
directives.iter().any(|d| d.name.node == "defer")
directives.iter().any(|d| d.node.name.node == "defer")
}
#[doc(hidden)]
pub fn is_stream(&self, directives: &[Positioned<Directive>]) -> bool {
directives.iter().any(|d| d.name.node == "stream")
directives.iter().any(|d| d.node.name.node == "stream")
}
}
@ -554,38 +456,18 @@ impl<'a> ContextBase<'a, &'a Positioned<Field>> {
name: &str,
default: Option<fn() -> T>,
) -> Result<T> {
let value = self.get_argument(name).cloned();
if let Some(default) = default {
if value.is_none() {
let value = self.item.node.get_argument(name).cloned();
if value.is_none() {
if let Some(default) = default {
return Ok(default());
}
}
let pos = value
.as_ref()
.map(|value| value.position())
.unwrap_or_default();
let value = match value {
Some(value) => {
let mut new_value = value.into_inner();
self.resolve_input_value(&mut new_value, pos)?;
Some(new_value)
}
None => None,
let (pos, value) = match value {
Some(value) => (value.pos, Some(self.resolved_input_value(value)?)),
None => (Pos::default(), None),
};
match InputValueType::parse(value) {
Ok(res) => Ok(res),
Err(err) => Err(err.into_error(pos, T::qualified_type_name())),
}
}
#[doc(hidden)]
pub fn result_name(&self) -> &str {
self.item
.alias
.as_ref()
.map(|alias| alias.as_str())
.unwrap_or_else(|| self.item.name.as_str())
InputValueType::parse(value)
.map_err(|e| e.into_error(pos, T::qualified_type_name()))
}
/// Get the position of the current field in the query code.

View File

@ -81,9 +81,12 @@ impl<E: Display> From<E> for FieldError {
}
}
#[allow(missing_docs)]
/// An error which can be extended into a `FieldError`.
pub trait ErrorExtensions: Sized {
/// Convert the error to a `FieldError`.
fn extend(&self) -> FieldError;
/// Add extensions to the error, using a callback to make the extensions.
fn extend_with<C>(self, cb: C) -> FieldError
where
C: FnOnce(&Self) -> serde_json::Value,

View File

@ -1,6 +1,6 @@
use crate::extensions::{Extension, ResolveInfo};
use crate::{Error, Variables};
use async_graphql_parser::query::{Definition, Document, OperationDefinition, Selection};
use crate::parser::types::{Definition, Document, Selection, OperationType};
use itertools::Itertools;
use log::{error, info, trace};
use std::borrow::Cow;
@ -32,28 +32,14 @@ impl Extension for Logger {
}
fn parse_end(&mut self, document: &Document) {
let mut is_schema = false;
for definition in document.definitions() {
if let Definition::Operation(operation) = &definition.node {
let selection_set = match &operation.node {
OperationDefinition::Query(query) => &query.selection_set,
OperationDefinition::SelectionSet(selection_set) => &selection_set.node,
_ => continue,
};
is_schema = selection_set.items.iter().any(|selection| {
if let Selection::Field(field) = &selection.node {
if field.name.as_str() == "__schema" {
return true;
}
}
false
});
if is_schema {
break;
}
}
}
let is_schema = document
.definitions
.iter()
.filter_map(|definition| match definition {
Definition::Operation(operation) if operation.node.ty == OperationType::Query => Some(operation),
_ => None,
})
.any(|operation| operation.node.selection_set.node.items.iter().any(|selection| matches!(&selection.node, Selection::Field(field) if field.node.name.node == "__schema")));
if is_schema {
self.enabled = false;

View File

@ -11,7 +11,7 @@ pub use self::apollo_tracing::ApolloTracing;
pub use self::logger::Logger;
pub use self::tracing::Tracing;
use crate::Error;
use async_graphql_parser::query::Document;
use crate::parser::types::Document;
use serde_json::Value;
pub(crate) type BoxExtension = Box<dyn Extension>;

View File

@ -141,7 +141,7 @@ pub use error::{
ParseRequestError, QueryError, ResultExt, RuleError,
};
pub use look_ahead::Lookahead;
pub use parser::{Pos, Positioned, Value};
pub use parser::{Pos, Positioned, types::Value};
pub use query::{IntoQueryBuilder, IntoQueryBuilderOpts, QueryBuilder, QueryResponse};
pub use registry::CacheControl;
pub use scalars::{Any, Json, OutputJson, ID};

View File

@ -1,15 +1,15 @@
use async_graphql_parser::query::{Document, Field, Selection, SelectionSet};
use crate::parser::types::{ExecutableDocument, Field, Selection, SelectionSet};
/// A selection performed by a query
pub struct Lookahead<'a> {
pub(crate) document: &'a Document,
pub(crate) document: &'a ExecutableDocument,
pub(crate) field: Option<&'a Field>,
}
impl<'a> Lookahead<'a> {
/// Check if the specified field exists in the current selection.
pub fn field(&self, name: &str) -> Lookahead {
Lookahead {
pub fn field(&self, name: &str) -> Self {
Self {
document: self.document,
field: self
.field
@ -25,28 +25,28 @@ impl<'a> Lookahead<'a> {
}
fn find<'a>(
document: &'a Document,
document: &'a ExecutableDocument,
selection_set: &'a SelectionSet,
name: &str,
) -> Option<&'a Field> {
for item in &selection_set.items {
match &item.node {
Selection::Field(field) => {
if field.name.node == name {
if field.node.name.node == name {
return Some(&field.node);
}
}
Selection::InlineFragment(inline_fragment) => {
if let Some(field) = find(document, &inline_fragment.selection_set.node, name) {
if let Some(field) = find(document, &inline_fragment.node.selection_set.node, name) {
return Some(field);
}
}
Selection::FragmentSpread(fragment_spread) => {
if let Some(fragment) = document
.fragments()
.get(fragment_spread.fragment_name.as_str())
.fragments
.get(&fragment_spread.node.fragment_name.node)
{
if let Some(field) = find(document, &fragment.selection_set.node, name) {
if let Some(field) = find(document, &fragment.node.selection_set.node, name) {
return Some(field);
}
}

View File

@ -1,5 +1,5 @@
use crate::extensions::{ErrorLogger, Extension, ResolveInfo};
use crate::parser::query::{Selection, TypeCondition};
use crate::parser::types::{Selection, TypeCondition};
use crate::registry::MetaType;
use crate::{ContextSelectionSet, Error, ObjectType, QueryError, Result};
use std::future::Future;
@ -23,9 +23,9 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>(
values: &'a mut serde_json::Map<String, serde_json::Value>,
) -> BoxMutationFuture<'a> {
Box::pin(async move {
if ctx.items.is_empty() {
if ctx.item.node.items.is_empty() {
return Err(Error::Query {
pos: ctx.position(),
pos: ctx.item.pos,
path: None,
err: QueryError::MustHaveSubFields {
object: T::type_name().to_string(),
@ -33,14 +33,14 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>(
});
}
for selection in &ctx.item.items {
for selection in &ctx.item.node.items {
match &selection.node {
Selection::Field(field) => {
if ctx.is_skip(&field.directives)? {
if ctx.is_skip(&field.node.directives)? {
continue;
}
if field.name.node == "__typename" {
if field.node.name.node == "__typename" {
values.insert(
"__typename".to_string(),
root.introspection_type_name().to_string().into(),
@ -48,18 +48,18 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>(
continue;
}
if ctx.is_ifdef(&field.directives) {
if ctx.is_ifdef(&field.node.directives) {
if let Some(MetaType::Object { fields, .. }) =
ctx.schema_env.registry.types.get(T::type_name().as_ref())
{
if !fields.contains_key(field.name.as_str()) {
if !fields.contains_key(&field.node.name.node) {
continue;
}
}
}
let ctx_field = ctx.with_field(field);
let field_name = ctx_field.result_name().to_string();
let ctx_field = ctx.with_field(&field);
let field_name = ctx_field.item.node.response_key().node.clone();
let resolve_info = ResolveInfo {
resolve_id: ctx_field.resolve_id,
@ -70,16 +70,16 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>(
.registry
.types
.get(T::type_name().as_ref())
.and_then(|ty| ty.field_by_name(field.name.as_str()))
.and_then(|ty| ty.field_by_name(&field.node.name.node))
.map(|field| &field.ty)
{
Some(ty) => &ty,
None => {
return Err(Error::Query {
pos: field.position(),
pos: field.pos,
path: None,
err: QueryError::FieldNotFound {
field_name: field.name.to_string(),
field_name: field.node.name.node.clone(),
object: T::type_name().to_string(),
},
});
@ -105,44 +105,44 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>(
.resolve_end(&resolve_info);
}
Selection::FragmentSpread(fragment_spread) => {
if ctx.is_skip(&fragment_spread.directives)? {
if ctx.is_skip(&fragment_spread.node.directives)? {
continue;
}
if let Some(fragment) = ctx
.query_env
.document
.fragments()
.get(fragment_spread.fragment_name.as_str())
.fragments
.get(&fragment_spread.node.fragment_name.node)
{
do_resolve(
&ctx.with_selection_set(&fragment.selection_set),
&ctx.with_selection_set(&fragment.node.selection_set),
root,
values,
)
.await?;
} else {
return Err(Error::Query {
pos: fragment_spread.position(),
pos: fragment_spread.pos,
path: None,
err: QueryError::UnknownFragment {
name: fragment_spread.fragment_name.to_string(),
name: fragment_spread.node.fragment_name.node.clone(),
},
});
}
}
Selection::InlineFragment(inline_fragment) => {
if ctx.is_skip(&inline_fragment.directives)? {
if ctx.is_skip(&inline_fragment.node.directives)? {
continue;
}
if let Some(TypeCondition::On(name)) =
inline_fragment.type_condition.as_ref().map(|v| &v.node)
if let Some(TypeCondition { on: name }) =
inline_fragment.node.type_condition.as_ref().map(|v| &v.node)
{
let mut futures = Vec::new();
root.collect_inline_fields(
name,
&ctx.with_selection_set(&inline_fragment.selection_set),
&name.node,
&ctx.with_selection_set(&inline_fragment.node.selection_set),
&mut futures,
)?;
for fut in futures {
@ -151,7 +151,7 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>(
}
} else {
do_resolve(
&ctx.with_selection_set(&inline_fragment.selection_set),
&ctx.with_selection_set(&inline_fragment.node.selection_set),
root,
values,
)

View File

@ -7,7 +7,7 @@ use crate::{
do_resolve, ContextBase, Error, ObjectType, Pos, QueryEnv, QueryError, Result, Schema,
SubscriptionType, Variables,
};
use async_graphql_parser::query::OperationType;
use crate::parser::types::{OperationType, Value, UploadValue};
use std::any::Any;
use std::fs::File;
use std::sync::atomic::AtomicUsize;
@ -115,8 +115,15 @@ impl QueryBuilder {
content_type: Option<String>,
content: File,
) {
self.variables
.set_upload(var_path, filename, content_type, content);
let variable = match self.variables.variable_path(var_path) {
Some(variable) => variable,
None => return,
};
*variable = Value::Upload(UploadValue {
filename,
content_type,
content,
});
}
/// Execute the query, always return a complete result.
@ -129,13 +136,14 @@ impl QueryBuilder {
Mutation: ObjectType + Send + Sync + 'static,
Subscription: SubscriptionType + Send + Sync + 'static,
{
let (mut document, cache_control, extensions) =
let (document, cache_control, extensions) =
schema.prepare_query(&self.query_source, &self.variables, &self.extensions)?;
// execute
let inc_resolve_id = AtomicUsize::default();
if !document.retain_operation(self.operation_name.as_deref()) {
return if let Some(operation_name) = self.operation_name {
let document = match document.into_executable(self.operation_name.as_deref()) {
Some(document) => document,
None => return if let Some(operation_name) = self.operation_name {
Err(Error::Query {
pos: Pos::default(),
path: None,
@ -150,8 +158,8 @@ impl QueryBuilder {
err: QueryError::MissingOperation,
})
}
.log_error(&extensions);
}
.log_error(&extensions),
};
let env = QueryEnv::new(
extensions,
@ -163,13 +171,13 @@ impl QueryBuilder {
path_node: None,
resolve_id: ResolveId::root(),
inc_resolve_id: &inc_resolve_id,
item: &env.document.current_operation().selection_set,
item: &env.document.operation.node.selection_set,
schema_env: &schema.env,
query_env: &env,
};
env.extensions.lock().execution_start();
let data = match &env.document.current_operation().ty {
let data = match &env.document.operation.node.ty {
OperationType::Query => do_resolve(&ctx, &schema.query).await?,
OperationType::Mutation => do_mutation_resolve(&ctx, &schema.mutation).await?,
OperationType::Subscription => {

View File

@ -1,4 +1,4 @@
use crate::parser::query::Type as ParsedType;
use crate::parser::types::{Type as ParsedType, BaseType as ParsedBaseType};
use crate::validators::InputValueValidator;
use crate::{model, Any, Type as _, Value};
use indexmap::map::IndexMap;
@ -8,17 +8,9 @@ use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use std::sync::Arc;
fn parse_non_null(type_name: &str) -> Option<&str> {
if type_name.ends_with('!') {
Some(&type_name[..type_name.len() - 1])
} else {
None
}
}
fn parse_list(type_name: &str) -> Option<&str> {
if type_name.starts_with('[') {
Some(&type_name[1..type_name.len() - 1])
fn strip_brackets(type_name: &str) -> Option<&str> {
if let Some(rest) = type_name.strip_prefix('[') {
Some(&rest[..rest.len() - 1])
} else {
None
}
@ -43,9 +35,9 @@ impl<'a> std::fmt::Display for MetaTypeName<'a> {
impl<'a> MetaTypeName<'a> {
pub fn create(type_name: &str) -> MetaTypeName {
if let Some(type_name) = parse_non_null(type_name) {
if let Some(type_name) = type_name.strip_suffix('!') {
MetaTypeName::NonNull(type_name)
} else if let Some(type_name) = parse_list(type_name) {
} else if let Some(type_name) = strip_brackets(type_name) {
MetaTypeName::List(type_name)
} else {
MetaTypeName::Named(type_name)
@ -61,11 +53,7 @@ impl<'a> MetaTypeName<'a> {
}
pub fn is_non_null(&self) -> bool {
if let MetaTypeName::NonNull(_) = self {
true
} else {
false
}
matches!(self, MetaTypeName::NonNull(_))
}
pub fn unwrap_non_null(&self) -> Self {
@ -153,7 +141,7 @@ pub struct MetaEnumValue {
/// ```
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct CacheControl {
/// Scope is public, default is true
/// Scope is public, default is true.
pub public: bool,
/// Cache max age, default is 0.
@ -171,13 +159,10 @@ impl Default for CacheControl {
impl CacheControl {
/// Get 'Cache-Control' header value.
#[must_use]
pub fn value(&self) -> Option<String> {
if self.max_age > 0 {
if !self.public {
Some(format!("max-age={}, private", self.max_age))
} else {
Some(format!("max-age={}", self.max_age))
}
Some(format!("max-age={}{}", self.max_age, if self.public { "" } else { ", private" }))
} else {
None
}
@ -353,8 +338,9 @@ impl Registry {
) -> String {
let name = T::type_name();
if !self.types.contains_key(name.as_ref()) {
// Inserting a fake type before calling the function allows recursive types to exist.
self.types.insert(
name.to_string(),
name.clone().into_owned(),
MetaType::Object {
name: "".to_string(),
description: None,
@ -365,7 +351,7 @@ impl Registry {
},
);
let ty = f(self);
self.types.insert(name.to_string(), ty);
*self.types.get_mut(&*name).unwrap() = ty;
}
T::qualified_type_name()
}
@ -406,19 +392,15 @@ impl Registry {
}
pub fn concrete_type_by_parsed_type(&self, query_type: &ParsedType) -> Option<&MetaType> {
match query_type {
ParsedType::NonNull(ty) => self.concrete_type_by_parsed_type(ty),
ParsedType::List(ty) => self.concrete_type_by_parsed_type(ty),
ParsedType::Named(name) => self.types.get(name.as_str()),
match &query_type.base {
ParsedBaseType::Named(name) => self.types.get(name.as_str()),
ParsedBaseType::List(ty) => self.concrete_type_by_parsed_type(ty),
}
}
fn create_federation_fields<'a, I: Iterator<Item = &'a MetaField>>(sdl: &mut String, it: I) {
for field in it {
if field.name.starts_with("__") {
continue;
}
if field.name == "_service" || field.name == "_entities" {
if field.name.starts_with("__") || matches!(&*field.name, "_service" | "_entities") {
continue;
}

View File

@ -1,6 +1,6 @@
use crate::base::BoxFieldFuture;
use crate::extensions::{ErrorLogger, Extension, ResolveInfo};
use crate::parser::query::{Selection, TypeCondition};
use crate::parser::types::Selection;
use crate::registry::MetaType;
use crate::{ContextSelectionSet, Error, ObjectType, QueryError, Result};
use futures::{future, TryFutureExt};
@ -10,7 +10,7 @@ pub async fn do_resolve<'a, T: ObjectType + Send + Sync>(
ctx: &'a ContextSelectionSet<'a>,
root: &'a T,
) -> Result<serde_json::Value> {
let mut futures = Vec::with_capacity(ctx.item.items.len());
let mut futures = Vec::with_capacity(ctx.item.node.items.len());
collect_fields(ctx, root, &mut futures)?;
let res = futures::future::try_join_all(futures).await?;
let mut map = serde_json::Map::new();
@ -34,9 +34,9 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
root: &'a T,
futures: &mut Vec<BoxFieldFuture<'a>>,
) -> Result<()> {
if ctx.items.is_empty() {
if ctx.node.items.is_empty() {
return Err(Error::Query {
pos: ctx.position(),
pos: ctx.pos,
path: None,
err: QueryError::MustHaveSubFields {
object: T::type_name().to_string(),
@ -44,17 +44,17 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
});
}
for selection in &ctx.item.items {
for selection in &ctx.item.node.items {
match &selection.node {
Selection::Field(field) => {
if ctx.is_skip(&field.directives)? {
if ctx.is_skip(&field.node.directives)? {
continue;
}
if field.name.node == "__typename" {
if field.node.name.node == "__typename" {
// Get the typename
let ctx_field = ctx.with_field(field);
let field_name = ctx_field.result_name().to_string();
let field_name = ctx_field.item.node.response_key().node.clone();
futures.push(Box::pin(
future::ok::<serde_json::Value, Error>(
root.introspection_type_name().to_string().into(),
@ -64,11 +64,11 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
continue;
}
if ctx.is_ifdef(&field.directives) {
if ctx.is_ifdef(&field.node.directives) {
if let Some(MetaType::Object { fields, .. }) =
ctx.schema_env.registry.types.get(T::type_name().as_ref())
{
if !fields.contains_key(field.name.as_str()) {
if !fields.contains_key(field.node.name.node.as_str()) {
continue;
}
}
@ -78,7 +78,7 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
let ctx = ctx.clone();
async move {
let ctx_field = ctx.with_field(field);
let field_name = ctx_field.result_name().to_string();
let field_name = ctx_field.item.node.response_key().node.clone();
let resolve_info = ResolveInfo {
resolve_id: ctx_field.resolve_id,
@ -89,16 +89,16 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
.registry
.types
.get(T::type_name().as_ref())
.and_then(|ty| ty.field_by_name(field.name.as_str()))
.and_then(|ty| ty.field_by_name(field.node.name.node.as_str()))
.map(|field| &field.ty)
{
Some(ty) => &ty,
None => {
return Err(Error::Query {
pos: field.position(),
pos: field.pos,
path: None,
err: QueryError::FieldNotFound {
field_name: field.name.to_string(),
field_name: field.node.name.node.to_owned(),
object: T::type_name().to_string(),
},
});
@ -128,45 +128,45 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
}))
}
Selection::FragmentSpread(fragment_spread) => {
if ctx.is_skip(&fragment_spread.directives)? {
if ctx.is_skip(&fragment_spread.node.directives)? {
continue;
}
if let Some(fragment) = ctx
.query_env
.document
.fragments()
.get(fragment_spread.fragment_name.as_str())
.fragments
.get(&fragment_spread.node.fragment_name.node)
{
collect_fields(
&ctx.with_selection_set(&fragment.selection_set),
&ctx.with_selection_set(&fragment.node.selection_set),
root,
futures,
)?;
} else {
return Err(Error::Query {
pos: fragment_spread.position(),
pos: fragment_spread.pos,
path: None,
err: QueryError::UnknownFragment {
name: fragment_spread.fragment_name.to_string(),
name: fragment_spread.node.fragment_name.to_string(),
},
});
}
}
Selection::InlineFragment(inline_fragment) => {
if ctx.is_skip(&inline_fragment.directives)? {
if ctx.is_skip(&inline_fragment.node.directives)? {
continue;
}
if let Some(TypeCondition::On(name)) = inline_fragment.type_condition.as_deref() {
if let Some(condition) = &inline_fragment.node.type_condition {
root.collect_inline_fields(
name,
&ctx.with_selection_set(&inline_fragment.selection_set),
&condition.node.on.node,
&ctx.with_selection_set(&inline_fragment.node.selection_set),
futures,
)?;
} else {
collect_fields(
&ctx.with_selection_set(&inline_fragment.selection_set),
&ctx.with_selection_set(&inline_fragment.node.selection_set),
root,
futures,
)?;

View File

@ -4,7 +4,7 @@ use crate::{
Value,
};
use async_graphql_derive::Scalar;
use async_graphql_parser::query::Field;
use crate::parser::types::Field;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;

View File

@ -3,7 +3,7 @@ use crate::{
Result, ScalarType, Type, Value,
};
use async_graphql_derive::Scalar;
use async_graphql_parser::query::Field;
use crate::parser::types::Field;
use std::borrow::Cow;
/// The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.

View File

@ -11,7 +11,7 @@ use crate::{
CacheControl, Error, ObjectType, Pos, QueryEnv, QueryError, QueryResponse, Result,
SubscriptionType, Type, Variables, ID,
};
use async_graphql_parser::query::{Document, OperationType};
use crate::parser::types::{Document, OperationType};
use bytes::Bytes;
use futures::channel::mpsc;
use futures::Stream;
@ -39,31 +39,33 @@ pub struct SchemaBuilder<Query, Mutation, Subscription> {
impl<Query: ObjectType, Mutation: ObjectType, Subscription: SubscriptionType>
SchemaBuilder<Query, Mutation, Subscription>
{
/// You can use this function to register types that are not directly referenced.
/// Manually register a type in the schema.
///
/// You can use this function to register schema types that are not directly referenced.
pub fn register_type<T: Type>(mut self) -> Self {
T::create_type_info(&mut self.registry);
self
}
/// Disable introspection query
/// Disable introspection queries.
pub fn disable_introspection(mut self) -> Self {
self.query.disable_introspection = true;
self
}
/// Set limit complexity, Default no limit.
/// Set the maximum complexity a query can have. By default there is no limit.
pub fn limit_complexity(mut self, complexity: usize) -> Self {
self.complexity = Some(complexity);
self
}
/// Set limit complexity, Default no limit.
/// Set the maximum depth a query can have. By default there is no limit.
pub fn limit_depth(mut self, depth: usize) -> Self {
self.depth = Some(depth);
self
}
/// Add an extension
/// Add an extension to the schema.
pub fn extension<F: Fn() -> E + Send + Sync + 'static, E: Extension>(
mut self,
extension_factory: F,
@ -73,7 +75,7 @@ impl<Query: ObjectType, Mutation: ObjectType, Subscription: SubscriptionType>
self
}
/// Add a global data that can be accessed in the `Schema`, you access it with `Context::data`.
/// Add a global data that can be accessed in the `Schema`. You access it with `Context::data`.
pub fn data<D: Any + Send + Sync>(mut self, data: D) -> Self {
self.data.insert(data);
self
@ -144,7 +146,9 @@ pub struct SchemaInner<Query, Mutation, Subscription> {
pub(crate) env: SchemaEnv,
}
/// GraphQL schema
/// GraphQL schema.
///
/// Cloning a schema is cheap, so it can be easily shared.
pub struct Schema<Query, Mutation, Subscription>(Arc<SchemaInner<Query, Mutation, Subscription>>);
impl<Query, Mutation, Subscription> Clone for Schema<Query, Mutation, Subscription> {
@ -371,10 +375,11 @@ where
variables: Variables,
ctx_data: Option<Arc<Data>>,
) -> Result<impl Stream<Item = Result<serde_json::Value>> + Send> {
let (mut document, _, extensions) = self.prepare_query(source, &variables, &Vec::new())?;
let (document, _, extensions) = self.prepare_query(source, &variables, &Vec::new())?;
if !document.retain_operation(operation_name) {
return if let Some(name) = operation_name {
let document = match document.into_executable(operation_name) {
Some(document) => document,
None => return if let Some(name) = operation_name {
Err(QueryError::UnknownOperationNamed {
name: name.to_string(),
}
@ -382,10 +387,10 @@ where
} else {
Err(QueryError::MissingOperation.into_error(Pos::default()))
}
.log_error(&extensions);
}
.log_error(&extensions),
};
if document.current_operation().ty != OperationType::Subscription {
if document.operation.node.ty != OperationType::Subscription {
return Err(QueryError::NotSupported.into_error(Pos::default())).log_error(&extensions);
}
@ -399,7 +404,7 @@ where
let ctx = env.create_context(
&self.env,
None,
&env.document.current_operation().selection_set,
&env.document.operation.node.selection_set,
&resolve_id,
);
let mut streams = Vec::new();

View File

@ -1,5 +1,5 @@
use crate::context::QueryEnv;
use crate::parser::query::{Selection, TypeCondition};
use crate::parser::types::{Selection, TypeCondition};
use crate::{Context, ContextSelectionSet, ObjectType, Result, Schema, SchemaEnv, Type};
use futures::{Future, Stream};
use std::pin::Pin;
@ -37,12 +37,12 @@ where
Subscription: SubscriptionType + Send + Sync + 'static + Sized,
{
Box::pin(async move {
for (idx, selection) in ctx.items.iter().enumerate() {
for (idx, selection) in ctx.item.node.items.iter().enumerate() {
if ctx.is_skip(selection.node.directives())? {
continue;
}
match &selection.node {
Selection::Field(field) => {
if ctx.is_skip(&field.directives)? {
continue;
}
streams.push(
schema
.subscription
@ -56,38 +56,30 @@ where
)
}
Selection::FragmentSpread(fragment_spread) => {
if ctx.is_skip(&fragment_spread.directives)? {
continue;
}
if let Some(fragment) = ctx
.query_env
.document
.fragments()
.get(fragment_spread.fragment_name.as_str())
.fragments
.get(&fragment_spread.node.fragment_name.node)
{
create_subscription_stream(
schema,
environment.clone(),
&ctx.with_selection_set(&fragment.selection_set),
&ctx.with_selection_set(&fragment.node.selection_set),
streams,
)
.await?;
}
}
Selection::InlineFragment(inline_fragment) => {
if ctx.is_skip(&inline_fragment.directives)? {
continue;
}
if let Some(TypeCondition::On(name)) =
inline_fragment.type_condition.as_ref().map(|v| &v.node)
if let Some(TypeCondition { on: name }) =
inline_fragment.node.type_condition.as_ref().map(|v| &v.node)
{
if name.node == Subscription::type_name() {
create_subscription_stream(
schema,
environment.clone(),
&ctx.with_selection_set(&inline_fragment.selection_set),
&ctx.with_selection_set(&inline_fragment.node.selection_set),
streams,
)
.await?;
@ -96,7 +88,7 @@ where
create_subscription_stream(
schema,
environment.clone(),
&ctx.with_selection_set(&inline_fragment.selection_set),
&ctx.with_selection_set(&inline_fragment.node.selection_set),
streams,
)
.await?;

View File

@ -5,7 +5,7 @@ use crate::{
do_resolve, registry, Context, ContextSelectionSet, FieldResult, ObjectType, OutputValueType,
Positioned, QueryError, Result, Type,
};
use async_graphql_parser::query::Field;
use crate::parser::types::Field;
use futures::{Stream, StreamExt, TryStreamExt};
use indexmap::map::IndexMap;
use std::borrow::Cow;
@ -196,7 +196,7 @@ where
EE: ObjectType + Sync + Send,
{
async fn resolve_field(&self, ctx: &Context<'_>) -> Result<serde_json::Value> {
if ctx.name.node == "pageInfo" {
if ctx.node.name.node == "pageInfo" {
let page_info = PageInfo {
has_previous_page: self.has_previous_page,
has_next_page: self.has_next_page,
@ -206,7 +206,7 @@ where
err: err.to_string(),
extended_error: None,
}
.into_error(ctx.position())
.into_error(ctx.pos)
})?),
None => None,
},
@ -216,15 +216,15 @@ where
err: err.to_string(),
extended_error: None,
}
.into_error(ctx.position())
.into_error(ctx.pos)
})?),
None => None,
},
};
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
let ctx_obj = ctx.with_selection_set(&ctx.node.selection_set);
return OutputValueType::resolve(&page_info, &ctx_obj, ctx.item).await;
} else if ctx.name.node == "edges" {
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
} else if ctx.node.name.node == "edges" {
let ctx_obj = ctx.with_selection_set(&ctx.node.selection_set);
return OutputValueType::resolve(&self.edges, &ctx_obj, ctx.item).await;
}

View File

@ -4,7 +4,7 @@ use crate::{
do_resolve, registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned,
QueryError, Result, Type,
};
use async_graphql_parser::query::Field;
use crate::parser::types::Field;
use indexmap::map::IndexMap;
use std::borrow::Cow;
@ -113,10 +113,10 @@ where
E: ObjectType + Sync + Send,
{
async fn resolve_field(&self, ctx: &Context<'_>) -> Result<serde_json::Value> {
if ctx.name.node == "node" {
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
if ctx.node.name.node == "node" {
let ctx_obj = ctx.with_selection_set(&ctx.node.selection_set);
return OutputValueType::resolve(&self.node, &ctx_obj, ctx.item).await;
} else if ctx.name.node == "cursor" {
} else if ctx.node.name.node == "cursor" {
return Ok(self
.cursor
.encode_cursor()
@ -125,7 +125,7 @@ where
err: err.to_string(),
extended_error: None,
}
.into_error(ctx.position())
.into_error(ctx.pos)
})?
.into());
}

View File

@ -2,7 +2,7 @@ use crate::{
registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, Positioned,
QueryError, Result, Type,
};
use async_graphql_parser::query::Field;
use crate::parser::types::Field;
use std::borrow::Cow;
/// Empty mutation
@ -60,7 +60,7 @@ impl OutputValueType for EmptyMutation {
field: &Positioned<Field>,
) -> Result<serde_json::Value> {
Err(Error::Query {
pos: field.position(),
pos: field.pos,
path: None,
err: QueryError::NotConfiguredMutations,
})

View File

@ -2,7 +2,7 @@ use crate::{
registry, ContextSelectionSet, InputValueResult, InputValueType, OutputValueType, Positioned,
Result, Type, Value,
};
use async_graphql_parser::query::Field;
use crate::parser::types::Field;
use std::borrow::Cow;
impl<T: Type> Type for Vec<T> {

View File

@ -3,7 +3,7 @@ use crate::{
do_resolve, CacheControl, Context, ContextSelectionSet, Error, ObjectType, OutputValueType,
Positioned, QueryEnv, QueryError, Result, SchemaEnv, SubscriptionType, Type,
};
use async_graphql_parser::query::Field;
use crate::parser::types::Field;
use futures::Stream;
use indexmap::IndexMap;
use std::borrow::Cow;

View File

@ -2,7 +2,7 @@ use crate::{
registry, ContextSelectionSet, InputValueResult, InputValueType, OutputValueType, Positioned,
Result, Type, Value,
};
use async_graphql_parser::query::Field;
use crate::parser::types::Field;
use std::borrow::Cow;
impl<T: Type> Type for Option<T> {

View File

@ -5,7 +5,7 @@ use crate::{
Positioned, QueryError, Result, Type,
};
use async_graphql_derive::SimpleObject;
use async_graphql_parser::query::Field;
use crate::parser::types::Field;
use indexmap::map::IndexMap;
use std::borrow::Cow;
@ -81,22 +81,22 @@ impl<T: Type> Type for QueryRoot<T> {
#[async_trait::async_trait]
impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
async fn resolve_field(&self, ctx: &Context<'_>) -> Result<serde_json::Value> {
if ctx.name.node == "__schema" {
if ctx.item.node.name.node == "__schema" {
if self.disable_introspection {
return Err(Error::Query {
pos: ctx.position(),
pos: ctx.pos,
path: ctx
.path_node
.as_ref()
.and_then(|path| serde_json::to_value(path).ok()),
err: QueryError::FieldNotFound {
field_name: ctx.name.to_string(),
field_name: ctx.item.node.name.to_string(),
object: Self::type_name().to_string(),
},
});
}
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
return OutputValueType::resolve(
&__Schema {
registry: &ctx.schema_env.registry,
@ -105,9 +105,9 @@ impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
ctx.item,
)
.await;
} else if ctx.name.node == "__type" {
} else if ctx.item.node.name.node == "__type" {
let type_name: String = ctx.param_value("name", None)?;
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
return OutputValueType::resolve(
&ctx.schema_env
.registry
@ -118,15 +118,15 @@ impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
ctx.item,
)
.await;
} else if ctx.name.node == "_entities" {
} else if ctx.item.node.name.node == "_entities" {
let representations: Vec<Any> = ctx.param_value("representations", None)?;
let mut res = Vec::new();
for item in representations {
res.push(self.inner.find_entity(ctx, &item.0).await?);
}
return Ok(res.into());
} else if ctx.name.node == "_service" {
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
} else if ctx.item.node.name.node == "_service" {
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
return OutputValueType::resolve(
&Service {
sdl: Some(ctx.schema_env.registry.create_federation_sdl()),

View File

@ -1,5 +1,5 @@
use crate::{registry, InputValueError, InputValueResult, InputValueType, Type, Value};
use async_graphql_parser::UploadValue;
use crate::parser::types::UploadValue;
use std::borrow::Cow;
use std::io::Read;
@ -63,7 +63,7 @@ impl Upload {
}
}
impl<'a> Type for Upload {
impl Type for Upload {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("Upload")
}
@ -80,7 +80,7 @@ impl<'a> Type for Upload {
}
}
impl<'a> InputValueType for Upload {
impl InputValueType for Upload {
fn parse(value: Option<Value>) -> InputValueResult<Self> {
let value = value.unwrap_or_default();
if let Value::Upload(upload) = value {

View File

@ -1,13 +1,14 @@
#[cfg(test)]
#[macro_use]
mod test_harness;
mod rules;
mod suggestion;
mod utils;
mod visitor;
mod visitors;
#[cfg(test)]
mod test_harness;
use crate::parser::query::Document;
use crate::parser::types::Document;
use crate::registry::Registry;
use crate::{CacheControl, Error, Result, Variables};
use visitor::{visit, VisitorContext, VisitorNil};

View File

@ -1,5 +1,5 @@
use crate::context::QueryPathNode;
use crate::parser::query::{Directive, Field};
use crate::parser::types::{Directive, Field};
use crate::registry::MetaInputValue;
use crate::validation::utils::is_valid_input_value;
use crate::validation::visitor::{Visitor, VisitorContext};
@ -20,7 +20,7 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
self.current_args = ctx
.registry
.directives
.get(directive.name.as_str())
.get(&directive.node.name.node)
.map(|d| &d.args);
}
@ -40,12 +40,12 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
) {
if let Some(arg) = self
.current_args
.and_then(|args| args.get(name.as_str()).map(|input| input))
.and_then(|args| args.get(&*name.node).map(|input| input))
{
if let Some(validator) = &arg.validator {
let value = match &value.node {
Value::Variable(var_name) => {
ctx.variables.and_then(|variables| variables.get(var_name))
ctx.variables.and_then(|variables| variables.0.get(var_name))
}
_ => Some(&value.node),
};
@ -53,7 +53,7 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
if let Some(value) = value {
if let Err(reason) = validator.is_valid(value) {
ctx.report_error(
vec![name.position()],
vec![name.pos],
format!("Invalid value for argument \"{}\", {}", arg.name, reason),
);
return;
@ -65,14 +65,14 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
ctx.registry,
ctx.variables,
&arg.ty,
value,
&value.node,
QueryPathNode {
parent: None,
segment: QueryPathSegment::Name(arg.name),
},
) {
ctx.report_error(
vec![name.position()],
vec![name.pos],
format!("Invalid value for argument {}", reason),
);
}
@ -82,7 +82,7 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned<Field>) {
self.current_args = ctx
.parent_type()
.and_then(|p| p.field_by_name(&field.name))
.and_then(|p| p.field_by_name(&field.node.name.node))
.map(|f| &f.args);
}
@ -94,7 +94,6 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> ArgumentsOfCorrectType<'a> {
ArgumentsOfCorrectType::default()

View File

@ -1,5 +1,5 @@
use crate::context::QueryPathNode;
use crate::parser::query::{Type, VariableDefinition};
use crate::parser::types::VariableDefinition;
use crate::validation::utils::is_valid_input_value;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Positioned, QueryPathSegment};
@ -12,24 +12,24 @@ impl<'a> Visitor<'a> for DefaultValuesOfCorrectType {
ctx: &mut VisitorContext<'a>,
variable_definition: &'a Positioned<VariableDefinition>,
) {
if let Some(value) = &variable_definition.default_value {
if let Type::NonNull(_) = &variable_definition.var_type.node {
ctx.report_error(vec![variable_definition.position()],format!(
"Argument \"{}\" has type \"{}\" and is not nullable, so it't can't have a default value",
variable_definition.name, variable_definition.var_type,
if let Some(value) = &variable_definition.node.default_value {
if !variable_definition.node.var_type.node.nullable {
ctx.report_error(vec![variable_definition.pos],format!(
"Argument \"{}\" has type \"{}\" and is not nullable, so it can't have a default value",
variable_definition.node.name, variable_definition.node.var_type,
));
} else if let Some(reason) = is_valid_input_value(
ctx.registry,
ctx.variables,
&variable_definition.var_type.to_string(),
value,
&variable_definition.node.var_type.to_string(),
&value.node,
QueryPathNode {
parent: None,
segment: QueryPathSegment::Name(&variable_definition.name),
segment: QueryPathSegment::Name(&variable_definition.node.name.node),
},
) {
ctx.report_error(
vec![variable_definition.position()],
vec![variable_definition.pos],
format!("Invalid default value for argument {}", reason),
)
}
@ -40,7 +40,6 @@ impl<'a> Visitor<'a> for DefaultValuesOfCorrectType {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory() -> DefaultValuesOfCorrectType {
DefaultValuesOfCorrectType

View File

@ -1,4 +1,4 @@
use crate::parser::query::Field;
use crate::parser::types::Field;
use crate::validation::suggestion::make_suggestion;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{registry, Positioned};
@ -12,25 +12,26 @@ impl<'a> Visitor<'a> for FieldsOnCorrectType {
if let Some(registry::MetaType::Union { .. })
| Some(registry::MetaType::Interface { .. }) = ctx.parent_type()
{
if field.name.node == "__typename" {
if field.node.name.node == "__typename" {
return;
}
}
if parent_type
.fields()
.and_then(|fields| fields.get(field.name.as_str()))
.and_then(|fields| fields.get(&field.node.name.node))
.is_none()
&& !field
.node
.directives
.iter()
.any(|directive| directive.node.name.as_str() == "ifdef")
.any(|directive| directive.node.name.node == "ifdef")
{
ctx.report_error(
vec![field.position()],
vec![field.pos],
format!(
"Unknown field \"{}\" on type \"{}\".{}",
field.name,
field.node.name,
parent_type.name(),
make_suggestion(
" Did you mean",
@ -40,7 +41,7 @@ impl<'a> Visitor<'a> for FieldsOnCorrectType {
.map(|fields| fields.keys())
.flatten()
.map(|s| s.as_str()),
&field.name
&field.node.name.node,
)
.unwrap_or_default()
),
@ -53,7 +54,6 @@ impl<'a> Visitor<'a> for FieldsOnCorrectType {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory() -> FieldsOnCorrectType {
FieldsOnCorrectType

View File

@ -1,4 +1,4 @@
use crate::parser::query::{FragmentDefinition, InlineFragment, TypeCondition};
use crate::parser::types::{FragmentDefinition, InlineFragment, TypeCondition};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
@ -13,12 +13,12 @@ impl<'a> Visitor<'a> for FragmentsOnCompositeTypes {
) {
if let Some(current_type) = ctx.current_type() {
if !current_type.is_composite() {
let TypeCondition::On(name) = &fragment_definition.type_condition.node;
let TypeCondition { on: name } = &fragment_definition.node.type_condition.node;
ctx.report_error(
vec![fragment_definition.position()],
vec![fragment_definition.pos],
format!(
"Fragment \"{}\" cannot condition non composite type \"{}\"",
fragment_definition.name, name
fragment_definition.node.name, name
),
);
}
@ -33,7 +33,7 @@ impl<'a> Visitor<'a> for FragmentsOnCompositeTypes {
if let Some(current_type) = ctx.current_type() {
if !current_type.is_composite() {
ctx.report_error(
vec![inline_fragment.position()],
vec![inline_fragment.pos],
format!(
"Fragment cannot condition non composite type \"{}\"",
current_type.name()
@ -47,7 +47,6 @@ impl<'a> Visitor<'a> for FragmentsOnCompositeTypes {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
fn factory() -> FragmentsOnCompositeTypes {
FragmentsOnCompositeTypes

View File

@ -1,4 +1,4 @@
use crate::parser::query::{Directive, Field};
use crate::parser::types::{Directive, Field};
use crate::registry::MetaInputValue;
use crate::validation::suggestion::make_suggestion;
use crate::validation::visitor::{Visitor, VisitorContext};
@ -41,8 +41,8 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
self.current_args = ctx
.registry
.directives
.get(directive.name.as_str())
.map(|d| (&d.args, ArgsType::Directive(&directive.name)));
.get(&directive.node.name.node)
.map(|d| (&d.args, ArgsType::Directive(&directive.node.name.node)));
}
fn exit_directive(
@ -60,31 +60,31 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
_value: &'a Positioned<Value>,
) {
if let Some((args, arg_type)) = &self.current_args {
if !args.contains_key(name.as_str()) {
if !args.contains_key(&*name.node) {
match arg_type {
ArgsType::Field {
field_name,
type_name,
} => {
ctx.report_error(
vec![name.position()],
vec![name.pos],
format!(
"Unknown argument \"{}\" on field \"{}\" of type \"{}\".{}",
name,
field_name,
type_name,
self.get_suggestion(name)
self.get_suggestion(&name.node)
),
);
}
ArgsType::Directive(directive_name) => {
ctx.report_error(
vec![name.position()],
vec![name.pos],
format!(
"Unknown argument \"{}\" on directive \"{}\".{}",
name,
directive_name,
self.get_suggestion(name)
self.get_suggestion(&name.node)
),
);
}
@ -95,11 +95,11 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned<Field>) {
if let Some(parent_type) = ctx.parent_type() {
if let Some(schema_field) = parent_type.field_by_name(&field.name) {
if let Some(schema_field) = parent_type.field_by_name(&field.node.name.node) {
self.current_args = Some((
&schema_field.args,
ArgsType::Field {
field_name: &field.name,
field_name: &field.node.name.node,
type_name: ctx.parent_type().unwrap().name(),
},
));
@ -115,7 +115,6 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> KnownArgumentNames<'a> {
KnownArgumentNames::default()

View File

@ -1,6 +1,6 @@
use crate::model::__DirectiveLocation;
use crate::parser::query::{
Directive, Field, FragmentDefinition, FragmentSpread, InlineFragment, OperationDefinition,
use crate::parser::types::{
Directive, Field, FragmentDefinition, FragmentSpread, InlineFragment, OperationDefinition, OperationType
};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
@ -16,12 +16,10 @@ impl<'a> Visitor<'a> for KnownDirectives {
_ctx: &mut VisitorContext<'a>,
operation_definition: &'a Positioned<OperationDefinition>,
) {
self.location_stack.push(match &operation_definition.node {
OperationDefinition::SelectionSet(_) | OperationDefinition::Query(_) => {
__DirectiveLocation::QUERY
}
OperationDefinition::Mutation(_) => __DirectiveLocation::MUTATION,
OperationDefinition::Subscription(_) => __DirectiveLocation::SUBSCRIPTION,
self.location_stack.push(match &operation_definition.node.ty {
OperationType::Query => __DirectiveLocation::QUERY,
OperationType::Mutation => __DirectiveLocation::MUTATION,
OperationType::Subscription => __DirectiveLocation::SUBSCRIPTION,
});
}
@ -55,22 +53,22 @@ impl<'a> Visitor<'a> for KnownDirectives {
ctx: &mut VisitorContext<'a>,
directive: &'a Positioned<Directive>,
) {
if let Some(schema_directive) = ctx.registry.directives.get(directive.name.as_str()) {
if let Some(schema_directive) = ctx.registry.directives.get(&directive.node.name.node) {
if let Some(current_location) = self.location_stack.last() {
if !schema_directive.locations.contains(current_location) {
ctx.report_error(
vec![directive.position()],
vec![directive.pos],
format!(
"Directive \"{}\" may not be used on \"{:?}\"",
directive.name, current_location
directive.node.name.node, current_location
),
)
}
}
} else {
ctx.report_error(
vec![directive.position()],
format!("Unknown directive \"{}\"", directive.name),
vec![directive.pos],
format!("Unknown directive \"{}\"", directive.node.name.node),
);
}
}
@ -121,7 +119,6 @@ impl<'a> Visitor<'a> for KnownDirectives {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory() -> KnownDirectives {
KnownDirectives::default()

View File

@ -1,4 +1,4 @@
use crate::parser::query::FragmentSpread;
use crate::parser::types::FragmentSpread;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
@ -11,10 +11,10 @@ impl<'a> Visitor<'a> for KnownFragmentNames {
ctx: &mut VisitorContext<'a>,
fragment_spread: &'a Positioned<FragmentSpread>,
) {
if !ctx.is_known_fragment(&fragment_spread.fragment_name) {
if !ctx.is_known_fragment(&fragment_spread.node.fragment_name.node) {
ctx.report_error(
vec![fragment_spread.position()],
format!(r#"Unknown fragment: "{}""#, fragment_spread.fragment_name),
vec![fragment_spread.pos],
format!(r#"Unknown fragment: "{}""#, fragment_spread.node.fragment_name.node),
);
}
}
@ -23,7 +23,6 @@ impl<'a> Visitor<'a> for KnownFragmentNames {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory() -> KnownFragmentNames {
KnownFragmentNames::default()

View File

@ -1,4 +1,4 @@
use crate::parser::query::{FragmentDefinition, InlineFragment, TypeCondition, VariableDefinition};
use crate::parser::types::{FragmentDefinition, InlineFragment, TypeCondition, VariableDefinition};
use crate::registry::MetaTypeName;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Pos, Positioned};
@ -12,8 +12,8 @@ impl<'a> Visitor<'a> for KnownTypeNames {
ctx: &mut VisitorContext<'a>,
fragment_definition: &'a Positioned<FragmentDefinition>,
) {
let TypeCondition::On(name) = &fragment_definition.type_condition.node;
validate_type(ctx, name.as_str(), fragment_definition.position());
let TypeCondition { on: name } = &fragment_definition.node.type_condition.node;
validate_type(ctx, &name.node, fragment_definition.pos);
}
fn enter_variable_definition(
@ -23,8 +23,8 @@ impl<'a> Visitor<'a> for KnownTypeNames {
) {
validate_type(
ctx,
MetaTypeName::concrete_typename(&variable_definition.var_type.to_string()),
variable_definition.position(),
MetaTypeName::concrete_typename(&variable_definition.node.var_type.to_string()),
variable_definition.pos,
);
}
@ -33,10 +33,10 @@ impl<'a> Visitor<'a> for KnownTypeNames {
ctx: &mut VisitorContext<'a>,
inline_fragment: &'a Positioned<InlineFragment>,
) {
if let Some(TypeCondition::On(name)) =
inline_fragment.type_condition.as_ref().map(|c| &c.node)
if let Some(TypeCondition { on: name }) =
inline_fragment.node.type_condition.as_ref().map(|c| &c.node)
{
validate_type(ctx, name.as_str(), inline_fragment.position());
validate_type(ctx, &name.node, inline_fragment.pos);
}
}
}
@ -50,7 +50,6 @@ fn validate_type(ctx: &mut VisitorContext<'_>, type_name: &str, pos: Pos) {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory() -> KnownTypeNames {
KnownTypeNames::default()

View File

@ -1,4 +1,4 @@
use crate::parser::query::{Definition, Document, OperationDefinition};
use crate::parser::types::{Definition, Document, OperationDefinition};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
@ -10,12 +10,9 @@ pub struct LoneAnonymousOperation {
impl<'a> Visitor<'a> for LoneAnonymousOperation {
fn enter_document(&mut self, _ctx: &mut VisitorContext<'a>, doc: &'a Document) {
self.operation_count = Some(
doc.definitions()
doc.definitions
.iter()
.filter(|d| match &d.node {
Definition::Operation(_) => true,
Definition::Fragment(_) => false,
})
.filter(|d| matches!(&d, Definition::Operation(_)))
.count(),
);
}
@ -26,25 +23,9 @@ impl<'a> Visitor<'a> for LoneAnonymousOperation {
operation_definition: &'a Positioned<OperationDefinition>,
) {
if let Some(operation_count) = self.operation_count {
let (err, pos) = match &operation_definition.node {
OperationDefinition::SelectionSet(s) => (operation_count > 1, s.position()),
OperationDefinition::Query(query) if query.name.is_none() => {
(operation_count > 1, query.position())
}
OperationDefinition::Mutation(mutation) if mutation.name.is_none() => {
(operation_count > 1, mutation.position())
}
OperationDefinition::Subscription(subscription) if subscription.name.is_none() => {
(operation_count > 1, subscription.position())
}
_ => {
return;
}
};
if err {
if operation_definition.node.name.is_none() && operation_count > 1 {
ctx.report_error(
vec![pos],
vec![operation_definition.pos],
"This anonymous operation must be the only defined operation",
);
}
@ -55,7 +36,6 @@ impl<'a> Visitor<'a> for LoneAnonymousOperation {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory() -> LoneAnonymousOperation {
LoneAnonymousOperation::default()

View File

@ -1,5 +1,5 @@
use crate::error::RuleError;
use crate::parser::query::{Document, FragmentDefinition, FragmentSpread};
use crate::parser::types::{Document, FragmentDefinition, FragmentSpread};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Pos, Positioned};
use std::collections::{HashMap, HashSet};
@ -77,8 +77,8 @@ impl<'a> Visitor<'a> for NoFragmentCycles<'a> {
_ctx: &mut VisitorContext<'a>,
fragment_definition: &'a Positioned<FragmentDefinition>,
) {
self.current_fragment = Some(&fragment_definition.name);
self.fragment_order.push(&fragment_definition.name);
self.current_fragment = Some(&fragment_definition.node.name.node);
self.fragment_order.push(&fragment_definition.node.name.node);
}
fn exit_fragment_definition(
@ -98,7 +98,7 @@ impl<'a> Visitor<'a> for NoFragmentCycles<'a> {
self.spreads
.entry(current_fragment)
.or_insert_with(Vec::new)
.push((&fragment_spread.fragment_name, fragment_spread.position()));
.push((&fragment_spread.node.fragment_name.node, fragment_spread.pos));
}
}
}
@ -106,7 +106,6 @@ impl<'a> Visitor<'a> for NoFragmentCycles<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> NoFragmentCycles<'a> {
NoFragmentCycles::default()

View File

@ -1,7 +1,7 @@
use crate::parser::query::{
use crate::parser::types::{
Document, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition,
};
use crate::validation::utils::{operation_name, referenced_variables, Scope};
use crate::validation::utils::{referenced_variables, Scope};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Pos, Positioned, Value};
use std::collections::{HashMap, HashSet};
@ -77,10 +77,10 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> {
_ctx: &mut VisitorContext<'a>,
operation_definition: &'a Positioned<OperationDefinition>,
) {
let (op_name, pos) = operation_name(&operation_definition);
self.current_scope = Some(Scope::Operation(op_name));
let name = operation_definition.node.name.as_ref().map(|name| &*name.node);
self.current_scope = Some(Scope::Operation(name));
self.defined_variables
.insert(op_name, (pos, HashSet::new()));
.insert(name, (operation_definition.pos, HashSet::new()));
}
fn enter_fragment_definition(
@ -88,7 +88,7 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> {
_ctx: &mut VisitorContext<'a>,
fragment_definition: &'a Positioned<FragmentDefinition>,
) {
self.current_scope = Some(Scope::Fragment(fragment_definition.name.as_str()));
self.current_scope = Some(Scope::Fragment(&fragment_definition.node.name.node));
}
fn enter_variable_definition(
@ -98,7 +98,7 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> {
) {
if let Some(Scope::Operation(ref name)) = self.current_scope {
if let Some(&mut (_, ref mut vars)) = self.defined_variables.get_mut(name) {
vars.insert(variable_definition.name.as_str());
vars.insert(&variable_definition.node.name.node);
}
}
}
@ -114,9 +114,9 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> {
.entry(scope.clone())
.or_insert_with(HashMap::new)
.extend(
referenced_variables(value)
referenced_variables(&value.node)
.into_iter()
.map(|n| (n, name.position())),
.map(|n| (n, name.pos)),
);
}
}
@ -130,7 +130,7 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> {
self.spreads
.entry(scope.clone())
.or_insert_with(Vec::new)
.push(fragment_spread.fragment_name.as_str());
.push(&fragment_spread.node.fragment_name.node);
}
}
}
@ -138,7 +138,6 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> NoUndefinedVariables<'a> {
NoUndefinedVariables::default()

View File

@ -1,7 +1,7 @@
use crate::parser::query::{
use crate::parser::types::{
Definition, Document, FragmentDefinition, FragmentSpread, OperationDefinition,
};
use crate::validation::utils::{operation_name, Scope};
use crate::validation::utils::Scope;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Pos, Positioned};
use std::collections::{HashMap, HashSet};
@ -35,10 +35,9 @@ impl<'a> Visitor<'a> for NoUnusedFragments<'a> {
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, doc: &'a Document) {
let mut reachable = HashSet::new();
for def in doc.definitions() {
if let Definition::Operation(operation_definition) = &def.node {
let (name, _) = operation_name(operation_definition);
self.find_reachable_fragments(&Scope::Operation(name), &mut reachable);
for def in &doc.definitions {
if let Definition::Operation(operation_definition) = def {
self.find_reachable_fragments(&Scope::Operation(operation_definition.node.name.as_ref().map(|name| &*name.node)), &mut reachable);
}
}
@ -57,8 +56,7 @@ impl<'a> Visitor<'a> for NoUnusedFragments<'a> {
_ctx: &mut VisitorContext<'a>,
operation_definition: &'a Positioned<OperationDefinition>,
) {
let (op_name, _) = operation_name(operation_definition);
self.current_scope = Some(Scope::Operation(op_name));
self.current_scope = Some(Scope::Operation(operation_definition.node.name.as_ref().map(|name| &*name.node)));
}
fn enter_fragment_definition(
@ -66,10 +64,10 @@ impl<'a> Visitor<'a> for NoUnusedFragments<'a> {
_ctx: &mut VisitorContext<'a>,
fragment_definition: &'a Positioned<FragmentDefinition>,
) {
self.current_scope = Some(Scope::Fragment(fragment_definition.name.as_str()));
self.current_scope = Some(Scope::Fragment(&fragment_definition.node.name.node));
self.defined_fragments.insert((
fragment_definition.name.as_str(),
fragment_definition.position(),
&fragment_definition.node.name.node,
fragment_definition.pos,
));
}
@ -82,7 +80,7 @@ impl<'a> Visitor<'a> for NoUnusedFragments<'a> {
self.spreads
.entry(scope.clone())
.or_insert_with(Vec::new)
.push(fragment_spread.fragment_name.as_str());
.push(&fragment_spread.node.fragment_name.node);
}
}
}
@ -90,7 +88,6 @@ impl<'a> Visitor<'a> for NoUnusedFragments<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> NoUnusedFragments<'a> {
NoUnusedFragments::default()

View File

@ -1,7 +1,7 @@
use crate::parser::query::{
use crate::parser::types::{
Document, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition,
};
use crate::validation::utils::{operation_name, referenced_variables, Scope};
use crate::validation::utils::{referenced_variables, Scope};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Pos, Positioned, Value};
use std::collections::{HashMap, HashSet};
@ -77,7 +77,7 @@ impl<'a> Visitor<'a> for NoUnusedVariables<'a> {
_ctx: &mut VisitorContext<'a>,
operation_definition: &'a Positioned<OperationDefinition>,
) {
let (op_name, _) = operation_name(operation_definition);
let op_name = operation_definition.node.name.as_ref().map(|name| &*name.node);
self.current_scope = Some(Scope::Operation(op_name));
self.defined_variables.insert(op_name, HashSet::new());
}
@ -87,7 +87,7 @@ impl<'a> Visitor<'a> for NoUnusedVariables<'a> {
_ctx: &mut VisitorContext<'a>,
fragment_definition: &'a Positioned<FragmentDefinition>,
) {
self.current_scope = Some(Scope::Fragment(fragment_definition.name.as_str()));
self.current_scope = Some(Scope::Fragment(&fragment_definition.node.name.node));
}
fn enter_variable_definition(
@ -98,8 +98,8 @@ impl<'a> Visitor<'a> for NoUnusedVariables<'a> {
if let Some(Scope::Operation(ref name)) = self.current_scope {
if let Some(vars) = self.defined_variables.get_mut(name) {
vars.insert((
variable_definition.name.as_str(),
variable_definition.position(),
&variable_definition.node.name.node,
variable_definition.pos,
));
}
}
@ -115,7 +115,7 @@ impl<'a> Visitor<'a> for NoUnusedVariables<'a> {
self.used_variables
.entry(scope.clone())
.or_insert_with(Vec::new)
.append(&mut referenced_variables(value));
.append(&mut referenced_variables(&value.node));
}
}
@ -128,7 +128,7 @@ impl<'a> Visitor<'a> for NoUnusedVariables<'a> {
self.spreads
.entry(scope.clone())
.or_insert_with(Vec::new)
.push(fragment_spread.fragment_name.as_str());
.push(&fragment_spread.node.fragment_name.node);
}
}
}
@ -136,7 +136,6 @@ impl<'a> Visitor<'a> for NoUnusedVariables<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> NoUnusedVariables<'a> {
NoUnusedVariables::default()

View File

@ -1,4 +1,4 @@
use crate::parser::query::{Field, Selection, SelectionSet};
use crate::parser::types::{Field, Selection, SelectionSet};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
use std::collections::HashMap;
@ -27,22 +27,23 @@ struct FindConflicts<'a, 'ctx> {
impl<'a, 'ctx> FindConflicts<'a, 'ctx> {
pub fn find(&mut self, selection_set: &'a Positioned<SelectionSet>) {
for selection in &selection_set.items {
for selection in &selection_set.node.items {
match &selection.node {
Selection::Field(field) => {
let output_name = field
.node
.alias
.as_ref()
.map(|name| name.as_str())
.unwrap_or_else(|| field.name.as_str());
.map(|name| &name.node)
.unwrap_or_else(|| &field.node.name.node);
self.add_output(&output_name, field);
}
Selection::InlineFragment(inline_fragment) => {
self.find(&inline_fragment.selection_set);
self.find(&inline_fragment.node.selection_set);
}
Selection::FragmentSpread(fragment_spread) => {
if let Some(fragment) = self.ctx.fragment(&fragment_spread.fragment_name) {
self.find(&fragment.selection_set);
if let Some(fragment) = self.ctx.fragment(&fragment_spread.node.fragment_name.node) {
self.find(&fragment.node.selection_set);
}
}
}
@ -51,26 +52,25 @@ impl<'a, 'ctx> FindConflicts<'a, 'ctx> {
fn add_output(&mut self, name: &'a str, field: &'a Positioned<Field>) {
if let Some(prev_field) = self.outputs.get(name) {
if prev_field.name != field.name {
if prev_field.node.name.node != field.node.name.node {
self.ctx.report_error(
vec![prev_field.position(), field.position()],
vec![prev_field.pos, field.pos],
format!("Fields \"{}\" conflict because \"{}\" and \"{}\" are different fields. Use different aliases on the fields to fetch both if this was intentional.",
name, prev_field.name, field.name));
name, prev_field.node.name.node, field.node.name.node));
}
// check arguments
if prev_field.arguments.len() != field.arguments.len() {
if prev_field.node.arguments.len() != field.node.arguments.len() {
self.ctx.report_error(
vec![prev_field.position(), field.position()],
vec![prev_field.pos, field.pos],
format!("Fields \"{}\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.", name));
}
for (name, value) in &prev_field.arguments {
match field.get_argument(name.as_str())
{
for (name, value) in &prev_field.node.arguments {
match field.node.get_argument(&name.node) {
Some(other_value) if value == other_value => {}
_=> self.ctx.report_error(
vec![prev_field.position(), field.position()],
vec![prev_field.pos, field.pos],
format!("Fields \"{}\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.", name)),
}
}

View File

@ -1,4 +1,4 @@
use crate::parser::query::{Definition, Document, FragmentSpread, InlineFragment, TypeCondition};
use crate::parser::types::{Definition, Document, FragmentSpread, InlineFragment, TypeCondition};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
use std::collections::HashMap;
@ -10,11 +10,11 @@ pub struct PossibleFragmentSpreads<'a> {
impl<'a> Visitor<'a> for PossibleFragmentSpreads<'a> {
fn enter_document(&mut self, _ctx: &mut VisitorContext<'a>, doc: &'a Document) {
for d in doc.definitions() {
if let Definition::Fragment(fragment) = &d.node {
let TypeCondition::On(type_name) = &fragment.type_condition.node;
for d in &doc.definitions {
if let Definition::Fragment(fragment) = &d {
let TypeCondition { on: type_name } = &fragment.node.type_condition.node;
self.fragment_types
.insert(fragment.name.as_str(), type_name);
.insert(&fragment.node.name.node, &type_name.node);
}
}
}
@ -26,16 +26,16 @@ impl<'a> Visitor<'a> for PossibleFragmentSpreads<'a> {
) {
if let Some(fragment_type) = self
.fragment_types
.get(fragment_spread.fragment_name.as_str())
.get(&*fragment_spread.node.fragment_name.node)
{
if let Some(current_type) = ctx.current_type() {
if let Some(on_type) = ctx.registry.types.get(*fragment_type) {
if !current_type.type_overlap(on_type) {
ctx.report_error(
vec![fragment_spread.position()],
vec![fragment_spread.pos],
format!(
"Fragment \"{}\" cannot be spread here as objects of type \"{}\" can never be of type \"{}\"",
&fragment_spread.fragment_name, current_type.name(), fragment_type
fragment_spread.node.fragment_name.node, current_type.name(), fragment_type
),
);
}
@ -50,13 +50,13 @@ impl<'a> Visitor<'a> for PossibleFragmentSpreads<'a> {
inline_fragment: &'a Positioned<InlineFragment>,
) {
if let Some(parent_type) = ctx.parent_type() {
if let Some(TypeCondition::On(fragment_type)) =
&inline_fragment.type_condition.as_ref().map(|c| &c.node)
if let Some(TypeCondition { on: fragment_type }) =
&inline_fragment.node.type_condition.as_ref().map(|c| &c.node)
{
if let Some(on_type) = ctx.registry.types.get(fragment_type.as_str()) {
if let Some(on_type) = ctx.registry.types.get(&fragment_type.node) {
if !parent_type.type_overlap(&on_type) {
ctx.report_error(
vec![inline_fragment.position()],
vec![inline_fragment.pos],
format!(
"Fragment cannot be spread here as objects of type \"{}\" \
can never be of type \"{}\"",
@ -74,7 +74,6 @@ impl<'a> Visitor<'a> for PossibleFragmentSpreads<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> PossibleFragmentSpreads<'a> {
PossibleFragmentSpreads::default()

View File

@ -1,4 +1,4 @@
use crate::parser::query::{Directive, Field};
use crate::parser::types::{Directive, Field};
use crate::registry::MetaTypeName;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
@ -12,20 +12,21 @@ impl<'a> Visitor<'a> for ProvidedNonNullArguments {
ctx: &mut VisitorContext<'a>,
directive: &'a Positioned<Directive>,
) {
if let Some(schema_directive) = ctx.registry.directives.get(directive.name.as_str()) {
if let Some(schema_directive) = ctx.registry.directives.get(&directive.node.name.node) {
for arg in schema_directive.args.values() {
if MetaTypeName::create(&arg.ty).is_non_null()
&& arg.default_value.is_none()
&& directive
.node
.arguments
.iter()
.find(|(name, _)| name.node == arg.name)
.is_none()
{
ctx.report_error(vec![directive.position()],
ctx.report_error(vec![directive.pos],
format!(
"Directive \"@{}\" argument \"{}\" of type \"{}\" is required but not provided",
directive.name, arg.name, arg.ty
directive.node.name, arg.name, arg.ty
));
}
}
@ -34,20 +35,21 @@ impl<'a> Visitor<'a> for ProvidedNonNullArguments {
fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned<Field>) {
if let Some(parent_type) = ctx.parent_type() {
if let Some(schema_field) = parent_type.field_by_name(&field.name) {
if let Some(schema_field) = parent_type.field_by_name(&field.node.name.node) {
for arg in schema_field.args.values() {
if MetaTypeName::create(&arg.ty).is_non_null()
&& arg.default_value.is_none()
&& field
.node
.arguments
.iter()
.find(|(name, _)| name.node == arg.name)
.is_none()
{
ctx.report_error(vec![field.position()],
ctx.report_error(vec![field.pos],
format!(
r#"Field "{}" argument "{}" of type "{}" is required but not provided"#,
field.name, arg.name, parent_type.name()
field.node.name, arg.name, parent_type.name()
));
}
}
@ -59,7 +61,6 @@ impl<'a> Visitor<'a> for ProvidedNonNullArguments {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory() -> ProvidedNonNullArguments {
ProvidedNonNullArguments

View File

@ -1,4 +1,4 @@
use crate::parser::query::Field;
use crate::parser::types::Field;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
@ -8,19 +8,19 @@ pub struct ScalarLeafs;
impl<'a> Visitor<'a> for ScalarLeafs {
fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned<Field>) {
if let Some(ty) = ctx.parent_type() {
if let Some(schema_field) = ty.field_by_name(&field.name) {
if let Some(schema_field) = ty.field_by_name(&field.node.name.node) {
if let Some(ty) = ctx.registry.concrete_type_by_name(&schema_field.ty) {
if ty.is_leaf() && !field.selection_set.items.is_empty() {
ctx.report_error(vec![field.position()], format!(
if ty.is_leaf() && !field.node.selection_set.node.items.is_empty() {
ctx.report_error(vec![field.pos], format!(
"Field \"{}\" must not have a selection since type \"{}\" has no subfields",
field.name, ty.name()
field.node.name, ty.name()
))
} else if !ty.is_leaf() && field.selection_set.items.is_empty() {
} else if !ty.is_leaf() && field.node.selection_set.node.items.is_empty() {
ctx.report_error(
vec![field.position()],
vec![field.pos],
format!(
"Field \"{}\" of type \"{}\" must have a selection of subfields",
field.name,
field.node.name,
ty.name()
),
)
@ -34,7 +34,6 @@ impl<'a> Visitor<'a> for ScalarLeafs {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory() -> ScalarLeafs {
ScalarLeafs

View File

@ -1,4 +1,4 @@
use crate::parser::query::{Directive, Field};
use crate::parser::types::{Directive, Field};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Positioned, Value};
use std::collections::HashSet;
@ -23,9 +23,9 @@ impl<'a> Visitor<'a> for UniqueArgumentNames<'a> {
name: &'a Positioned<String>,
_value: &'a Positioned<Value>,
) {
if !self.names.insert(name) {
if !self.names.insert(&name.node) {
ctx.report_error(
vec![name.position()],
vec![name.pos],
format!("There can only be one argument named \"{}\"", name),
)
}
@ -39,7 +39,6 @@ impl<'a> Visitor<'a> for UniqueArgumentNames<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> UniqueArgumentNames<'a> {
UniqueArgumentNames::default()

View File

@ -1,4 +1,4 @@
use crate::parser::query::FragmentDefinition;
use crate::parser::types::FragmentDefinition;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
use std::collections::HashSet;
@ -14,12 +14,12 @@ impl<'a> Visitor<'a> for UniqueFragmentNames<'a> {
ctx: &mut VisitorContext<'a>,
fragment_definition: &'a Positioned<FragmentDefinition>,
) {
if !self.names.insert(&fragment_definition.name) {
if !self.names.insert(&fragment_definition.node.name.node) {
ctx.report_error(
vec![fragment_definition.position()],
vec![fragment_definition.pos],
format!(
"There can only be one fragment named \"{}\"",
fragment_definition.name
fragment_definition.node.name
),
)
}
@ -29,7 +29,6 @@ impl<'a> Visitor<'a> for UniqueFragmentNames<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> UniqueFragmentNames<'a> {
UniqueFragmentNames::default()

View File

@ -1,4 +1,4 @@
use crate::parser::query::{Mutation, OperationDefinition, Query, Subscription};
use crate::parser::types::OperationDefinition;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
use std::collections::HashSet;
@ -14,26 +14,10 @@ impl<'a> Visitor<'a> for UniqueOperationNames<'a> {
ctx: &mut VisitorContext<'a>,
operation_definition: &'a Positioned<OperationDefinition>,
) {
let name = match &operation_definition.node {
OperationDefinition::Query(Positioned {
node: Query { name, .. },
..
}) => name.as_ref(),
OperationDefinition::Mutation(Positioned {
node: Mutation { name, .. },
..
}) => name.as_ref(),
OperationDefinition::Subscription(Positioned {
node: Subscription { name, .. },
..
}) => name.as_ref(),
OperationDefinition::SelectionSet(_) => None,
};
if let Some(name) = name {
if !self.names.insert(name.as_str()) {
if let Some(name) = &operation_definition.node.name {
if !self.names.insert(&name.node) {
ctx.report_error(
vec![name.position()],
vec![name.pos],
format!("There can only be one operation named \"{}\"", name),
)
}
@ -44,7 +28,6 @@ impl<'a> Visitor<'a> for UniqueOperationNames<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> UniqueOperationNames<'a> {
UniqueOperationNames::default()

View File

@ -1,4 +1,4 @@
use crate::parser::query::{OperationDefinition, VariableDefinition};
use crate::parser::types::{OperationDefinition, VariableDefinition};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
use std::collections::HashSet;
@ -22,12 +22,12 @@ impl<'a> Visitor<'a> for UniqueVariableNames<'a> {
ctx: &mut VisitorContext<'a>,
variable_definition: &'a Positioned<VariableDefinition>,
) {
if !self.names.insert(variable_definition.name.as_str()) {
if !self.names.insert(&variable_definition.node.name.node) {
ctx.report_error(
vec![variable_definition.position()],
vec![variable_definition.pos],
format!(
"There can only be one variable named \"${}\"",
variable_definition.name
variable_definition.node.name.node
),
);
}
@ -37,7 +37,6 @@ impl<'a> Visitor<'a> for UniqueVariableNames<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> UniqueVariableNames<'a> {
UniqueVariableNames::default()

View File

@ -1,4 +1,4 @@
use crate::parser::query::OperationDefinition;
use crate::parser::types::OperationDefinition;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
@ -11,26 +11,13 @@ impl<'a> Visitor<'a> for UploadFile {
ctx: &mut VisitorContext<'a>,
operation_definition: &'a Positioned<OperationDefinition>,
) {
if let OperationDefinition::Query(query) = &operation_definition.node {
for var in &query.variable_definitions {
if let Some(ty) = ctx.registry.concrete_type_by_parsed_type(&var.var_type) {
if ty.name() == "Upload" {
ctx.report_error(
vec![var.position()],
"The Upload type is only allowed to be defined on a mutation",
);
}
}
}
} else if let OperationDefinition::Subscription(subscription) = &operation_definition.node {
for var in &subscription.variable_definitions {
if let Some(ty) = ctx.registry.concrete_type_by_parsed_type(&var.var_type) {
if ty.name() == "Upload" {
ctx.report_error(
vec![var.position()],
"The Upload type is only allowed to be defined on a mutation",
);
}
for var in &operation_definition.node.variable_definitions {
if let Some(ty) = ctx.registry.concrete_type_by_parsed_type(&var.node.var_type.node) {
if ty.name() == "Upload" {
ctx.report_error(
vec![var.pos],
"The Upload type is only allowed to be defined on a mutation",
);
}
}
}

View File

@ -1,4 +1,4 @@
use crate::parser::query::VariableDefinition;
use crate::parser::types::VariableDefinition;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;
@ -13,14 +13,14 @@ impl<'a> Visitor<'a> for VariablesAreInputTypes {
) {
if let Some(ty) = ctx
.registry
.concrete_type_by_parsed_type(&variable_definition.var_type)
.concrete_type_by_parsed_type(&variable_definition.node.var_type.node)
{
if !ty.is_input() {
ctx.report_error(
vec![variable_definition.position()],
vec![variable_definition.pos],
format!(
"Variable \"{}\" cannot be of non-input type \"{}\"",
&variable_definition.name,
variable_definition.node.name.node,
ty.name()
),
);
@ -32,7 +32,6 @@ impl<'a> Visitor<'a> for VariablesAreInputTypes {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory() -> VariablesAreInputTypes {
VariablesAreInputTypes

View File

@ -1,8 +1,8 @@
use crate::parser::query::{
Document, FragmentDefinition, FragmentSpread, OperationDefinition, Type, VariableDefinition,
use crate::parser::types::{
Document, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition,
};
use crate::registry::MetaTypeName;
use crate::validation::utils::{operation_name, Scope};
use crate::validation::utils::Scope;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Pos, Positioned, Value};
use std::collections::{HashMap, HashSet};
@ -31,20 +31,21 @@ impl<'a> VariableInAllowedPosition<'a> {
if let Some(usages) = self.variable_usages.get(from) {
for (var_name, usage_pos, var_type) in usages {
if let Some(def) = var_defs.iter().find(|def| def.name.node == *var_name) {
let expected_type = match (&def.default_value, &def.var_type.node) {
(Some(_), Type::List(_)) => def.var_type.to_string() + "!",
(Some(_), Type::Named(_)) => def.var_type.to_string() + "!",
(_, _) => def.var_type.to_string(),
if let Some(def) = var_defs.iter().find(|def| def.node.name.node == *var_name) {
let expected_type = if def.node.var_type.node.nullable && def.node.default_value.is_some() {
// A nullable type with a default value functions as a non-nullable
format!("{}!", def.node.var_type.node)
} else {
def.node.var_type.node.to_string()
};
if !var_type.is_subtype(&MetaTypeName::create(&expected_type)) {
ctx.report_error(
vec![def.position(), *usage_pos],
vec![def.pos, *usage_pos],
format!(
"Variable \"{}\" of type \"{}\" used in position expecting type \"{}\"",
var_name, var_type, expected_type
),
"Variable \"{}\" of type \"{}\" used in position expecting type \"{}\"",
var_name, var_type, expected_type
),
);
}
}
@ -71,8 +72,7 @@ impl<'a> Visitor<'a> for VariableInAllowedPosition<'a> {
_ctx: &mut VisitorContext<'a>,
operation_definition: &'a Positioned<OperationDefinition>,
) {
let (op_name, _) = operation_name(operation_definition);
self.current_scope = Some(Scope::Operation(op_name));
self.current_scope = Some(Scope::Operation(operation_definition.node.name.as_ref().map(|name| &*name.node)));
}
fn enter_fragment_definition(
@ -80,7 +80,7 @@ impl<'a> Visitor<'a> for VariableInAllowedPosition<'a> {
_ctx: &mut VisitorContext<'a>,
fragment_definition: &'a Positioned<FragmentDefinition>,
) {
self.current_scope = Some(Scope::Fragment(fragment_definition.name.as_str()));
self.current_scope = Some(Scope::Fragment(&fragment_definition.node.name.node));
}
fn enter_variable_definition(
@ -105,7 +105,7 @@ impl<'a> Visitor<'a> for VariableInAllowedPosition<'a> {
self.spreads
.entry(scope.clone())
.or_insert_with(HashSet::new)
.insert(fragment_spread.fragment_name.as_str());
.insert(&fragment_spread.node.fragment_name.node);
}
}
@ -132,7 +132,6 @@ impl<'a> Visitor<'a> for VariableInAllowedPosition<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{expect_fails_rule, expect_passes_rule};
pub fn factory<'a>() -> VariableInAllowedPosition<'a> {
VariableInAllowedPosition::default()

View File

@ -4,7 +4,7 @@
use crate::validation::visitor::{visit, Visitor, VisitorContext};
use crate::*;
use async_graphql_parser::query::Document;
use crate::parser::types::Document;
use once_cell::sync::Lazy;
#[InputObject(internal)]
@ -367,10 +367,8 @@ where
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! expect_passes_rule {
($factory:expr, $query_source:literal $(,)*) => {
($factory:expr, $query_source:literal $(,)?) => {
let doc = crate::parser::parse_query($query_source).expect("Parse error");
crate::validation::test_harness::expect_passes_rule_(&doc, $factory);
};
@ -386,10 +384,8 @@ where
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! expect_fails_rule {
($factory:expr, $query_source:literal $(,)*) => {
($factory:expr, $query_source:literal $(,)?) => {
let doc = crate::parser::parse_query($query_source).expect("Parse error");
crate::validation::test_harness::expect_fails_rule_(&doc, $factory);
};

View File

@ -1,6 +1,5 @@
use crate::context::QueryPathNode;
use crate::parser::query::OperationDefinition;
use crate::{registry, Pos, QueryPathSegment, Value, Variables};
use crate::{registry, QueryPathSegment, Value, Variables};
use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -34,23 +33,6 @@ fn referenced_variables_to_vec<'a>(value: &'a Value, vars: &mut Vec<&'a str>) {
}
}
pub fn operation_name(operation_definition: &OperationDefinition) -> (Option<&str>, Pos) {
match operation_definition {
OperationDefinition::SelectionSet(selection_set) => (None, selection_set.position()),
OperationDefinition::Query(query) => {
(query.name.as_ref().map(|n| n.as_str()), query.position())
}
OperationDefinition::Mutation(mutation) => (
mutation.name.as_ref().map(|n| n.as_str()),
mutation.position(),
),
OperationDefinition::Subscription(subscription) => (
subscription.name.as_ref().map(|n| n.as_str()),
subscription.position(),
),
}
}
pub fn is_valid_input_value(
registry: &registry::Registry,
variables: Option<&Variables>,
@ -100,7 +82,7 @@ pub fn is_valid_input_value(
registry::MetaType::Scalar { is_valid, .. } => {
let value = match value {
Value::Variable(var_name) => {
variables.and_then(|variables| variables.get(var_name))
variables.and_then(|variables| variables.0.get(var_name))
}
_ => Some(value),
};
@ -150,7 +132,7 @@ pub fn is_valid_input_value(
if let Some(validator) = &field.validator {
let value = match value {
Value::Variable(var_name) => variables
.and_then(|variables| variables.get(var_name)),
.and_then(|variables| variables.0.get(var_name)),
_ => Some(value),
};

View File

@ -1,7 +1,7 @@
use crate::error::RuleError;
use crate::parser::query::{
use crate::parser::types::{
Definition, Directive, Document, Field, FragmentDefinition, FragmentSpread, InlineFragment,
OperationDefinition, Selection, SelectionSet, TypeCondition, VariableDefinition,
OperationDefinition, OperationType, Selection, SelectionSet, TypeCondition, VariableDefinition,
};
use crate::registry::{self, MetaType, MetaTypeName};
use crate::{Pos, Positioned, Value, Variables};
@ -29,10 +29,10 @@ impl<'a> VisitorContext<'a> {
type_stack: Default::default(),
input_type: Default::default(),
fragments: doc
.definitions()
.definitions
.iter()
.filter_map(|d| match &d.node {
Definition::Fragment(fragment) => Some((fragment.name.as_str(), fragment)),
.filter_map(|d| match &d {
Definition::Fragment(fragment) => Some((&*fragment.node.name.node, fragment)),
_ => None,
})
.collect(),
@ -457,14 +457,14 @@ fn visit_definitions<'a, V: Visitor<'a>>(
ctx: &mut VisitorContext<'a>,
doc: &'a Document,
) {
for d in doc.definitions() {
match &d.node {
for d in &doc.definitions {
match d {
Definition::Operation(operation) => {
visit_operation_definition(v, ctx, operation);
}
Definition::Fragment(fragment) => {
let TypeCondition::On(name) = &fragment.type_condition.node;
ctx.with_type(ctx.registry.types.get(name.as_str()), |ctx| {
let TypeCondition { on: name } = &fragment.node.type_condition.node;
ctx.with_type(ctx.registry.types.get(&name.node), |ctx| {
visit_fragment_definition(v, ctx, fragment)
});
}
@ -478,47 +478,23 @@ fn visit_operation_definition<'a, V: Visitor<'a>>(
operation: &'a Positioned<OperationDefinition>,
) {
v.enter_operation_definition(ctx, operation);
match &operation.node {
OperationDefinition::SelectionSet(selection_set) => {
ctx.with_type(Some(&ctx.registry.types[&ctx.registry.query_type]), |ctx| {
visit_selection_set(v, ctx, selection_set)
});
}
OperationDefinition::Query(query) => {
ctx.with_type(Some(&ctx.registry.types[&ctx.registry.query_type]), |ctx| {
visit_variable_definitions(v, ctx, &query.variable_definitions);
visit_directives(v, ctx, &query.directives);
visit_selection_set(v, ctx, &query.selection_set);
});
}
OperationDefinition::Mutation(mutation) => {
if let Some(mutation_type) = &ctx.registry.mutation_type {
ctx.with_type(Some(&ctx.registry.types[mutation_type]), |ctx| {
visit_variable_definitions(v, ctx, &mutation.variable_definitions);
visit_directives(v, ctx, &mutation.directives);
visit_selection_set(v, ctx, &mutation.selection_set);
});
} else {
ctx.report_error(
vec![mutation.position()],
"Schema is not configured for mutations.",
);
}
}
OperationDefinition::Subscription(subscription) => {
if let Some(subscription_type) = &ctx.registry.subscription_type {
ctx.with_type(Some(&ctx.registry.types[subscription_type]), |ctx| {
visit_variable_definitions(v, ctx, &subscription.variable_definitions);
visit_directives(v, ctx, &subscription.directives);
visit_selection_set(v, ctx, &subscription.selection_set);
});
} else {
ctx.report_error(
vec![subscription.position()],
"Schema is not configured for subscriptions.",
);
}
}
let root_name = match &operation.node.ty {
OperationType::Query => Some(&*ctx.registry.query_type),
OperationType::Mutation => ctx.registry.mutation_type.as_deref(),
OperationType::Subscription => ctx.registry.subscription_type.as_deref(),
};
if let Some(root_name) = root_name {
ctx.with_type(Some(&ctx.registry.types[&*root_name]), |ctx| {
visit_variable_definitions(v, ctx, &operation.node.variable_definitions);
visit_directives(v, ctx, &operation.node.directives);
visit_selection_set(v, ctx, &operation.node.selection_set);
});
} else {
ctx.report_error(
vec![operation.pos],
// The only one with an irregular plural, "query", is always present
format!("Schema is not configured for {}s.", operation.node.ty),
);
}
v.exit_operation_definition(ctx, operation);
}
@ -528,9 +504,9 @@ fn visit_selection_set<'a, V: Visitor<'a>>(
ctx: &mut VisitorContext<'a>,
selection_set: &'a Positioned<SelectionSet>,
) {
if !selection_set.items.is_empty() {
if !selection_set.node.items.is_empty() {
v.enter_selection_set(ctx, selection_set);
for selection in &selection_set.items {
for selection in &selection_set.node.items {
visit_selection(v, ctx, selection);
}
v.exit_selection_set(ctx, selection_set);
@ -545,10 +521,10 @@ fn visit_selection<'a, V: Visitor<'a>>(
v.enter_selection(ctx, selection);
match &selection.node {
Selection::Field(field) => {
if field.name.node != "__typename" {
if field.node.name.node != "__typename" {
ctx.with_type(
ctx.current_type()
.and_then(|ty| ty.field_by_name(&field.name))
.and_then(|ty| ty.field_by_name(&field.node.name.node))
.and_then(|schema_field| {
ctx.registry.concrete_type_by_name(&schema_field.ty)
}),
@ -562,10 +538,10 @@ fn visit_selection<'a, V: Visitor<'a>>(
visit_fragment_spread(v, ctx, fragment_spread)
}
Selection::InlineFragment(inline_fragment) => {
if let Some(TypeCondition::On(name)) =
&inline_fragment.type_condition.as_ref().map(|c| &c.node)
if let Some(TypeCondition { on: name }) =
&inline_fragment.node.type_condition.as_ref().map(|c| &c.node)
{
ctx.with_type(ctx.registry.types.get(name.as_str()), |ctx| {
ctx.with_type(ctx.registry.types.get(&name.node), |ctx| {
visit_inline_fragment(v, ctx, inline_fragment)
});
}
@ -581,21 +557,21 @@ fn visit_field<'a, V: Visitor<'a>>(
) {
v.enter_field(ctx, field);
for (name, value) in &field.arguments {
for (name, value) in &field.node.arguments {
v.enter_argument(ctx, name, value);
let expected_ty = ctx
.parent_type()
.and_then(|ty| ty.field_by_name(&field.name))
.and_then(|schema_field| schema_field.args.get(name.as_str()))
.and_then(|ty| ty.field_by_name(&field.node.name.node))
.and_then(|schema_field| schema_field.args.get(&*name.node))
.map(|input_ty| MetaTypeName::create(&input_ty.ty));
ctx.with_input_type(expected_ty, |ctx| {
visit_input_value(v, ctx, field.position(), expected_ty, value)
visit_input_value(v, ctx, field.pos, expected_ty, &value.node)
});
v.exit_argument(ctx, name, value);
}
visit_directives(v, ctx, &field.directives);
visit_selection_set(v, ctx, &field.selection_set);
visit_directives(v, ctx, &field.node.directives);
visit_selection_set(v, ctx, &field.node.selection_set);
v.exit_field(ctx, field);
}
@ -676,15 +652,15 @@ fn visit_directives<'a, V: Visitor<'a>>(
for d in directives {
v.enter_directive(ctx, d);
let schema_directive = ctx.registry.directives.get(d.name.as_str());
let schema_directive = ctx.registry.directives.get(&d.node.name.node);
for (name, value) in &d.arguments {
for (name, value) in &d.node.arguments {
v.enter_argument(ctx, name, value);
let expected_ty = schema_directive
.and_then(|schema_directive| schema_directive.args.get(name.as_str()))
.and_then(|schema_directive| schema_directive.args.get(&*name.node))
.map(|input_ty| MetaTypeName::create(&input_ty.ty));
ctx.with_input_type(expected_ty, |ctx| {
visit_input_value(v, ctx, d.position(), expected_ty, value)
visit_input_value(v, ctx, d.pos, expected_ty, &value.node)
});
v.exit_argument(ctx, name, value);
}
@ -699,8 +675,8 @@ fn visit_fragment_definition<'a, V: Visitor<'a>>(
fragment: &'a Positioned<FragmentDefinition>,
) {
v.enter_fragment_definition(ctx, fragment);
visit_directives(v, ctx, &fragment.directives);
visit_selection_set(v, ctx, &fragment.selection_set);
visit_directives(v, ctx, &fragment.node.directives);
visit_selection_set(v, ctx, &fragment.node.selection_set);
v.exit_fragment_definition(ctx, fragment);
}
@ -710,7 +686,7 @@ fn visit_fragment_spread<'a, V: Visitor<'a>>(
fragment_spread: &'a Positioned<FragmentSpread>,
) {
v.enter_fragment_spread(ctx, fragment_spread);
visit_directives(v, ctx, &fragment_spread.directives);
visit_directives(v, ctx, &fragment_spread.node.directives);
v.exit_fragment_spread(ctx, fragment_spread);
}
@ -720,7 +696,7 @@ fn visit_inline_fragment<'a, V: Visitor<'a>>(
inline_fragment: &'a Positioned<InlineFragment>,
) {
v.enter_inline_fragment(ctx, inline_fragment);
visit_directives(v, ctx, &inline_fragment.directives);
visit_selection_set(v, ctx, &inline_fragment.selection_set);
visit_directives(v, ctx, &inline_fragment.node.directives);
visit_selection_set(v, ctx, &inline_fragment.node.selection_set);
v.exit_inline_fragment(ctx, inline_fragment);
}

View File

@ -1,4 +1,4 @@
use crate::parser::query::{Field, SelectionSet};
use crate::parser::types::{Field, SelectionSet};
use crate::registry::MetaType;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{CacheControl, Positioned};
@ -23,7 +23,7 @@ impl<'ctx, 'a> Visitor<'ctx> for CacheControlCalculate<'a> {
fn enter_field(&mut self, ctx: &mut VisitorContext<'_>, field: &Positioned<Field>) {
if let Some(registry_field) = ctx
.parent_type()
.and_then(|parent| parent.field_by_name(&field.name))
.and_then(|parent| parent.field_by_name(&field.node.name.node))
{
self.cache_control.merge(&registry_field.cache_control);
}

View File

@ -1,4 +1,4 @@
use crate::parser::query::Field;
use crate::parser::types::Field;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;

View File

@ -1,4 +1,4 @@
use crate::parser::query::{FragmentSpread, InlineFragment, SelectionSet};
use crate::parser::types::{FragmentSpread, InlineFragment, SelectionSet};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::Positioned;

View File

@ -1625,7 +1625,7 @@ pub async fn test_input_validator_variable() {
let validator_length = 6;
for case in &test_cases {
let mut variables = Variables::default();
variables.insert("id".to_string(), Value::String(case.to_string()));
variables.0.insert("id".to_string(), Value::String(case.to_string()));
let field_query = "query($id: String!) {fieldParameter(id: $id)}";
let object_query = "query($id: String!) {inputObject(input: {id: $id})}";