From 81d85c2535c710373c1ce9497fde46d15b08fc89 Mon Sep 17 00:00:00 2001 From: Koxiaet <38139193+Koxiaet@users.noreply.github.com> Date: Sun, 6 Sep 2020 06:38:31 +0100 Subject: [PATCH] Rewrite async-graphql-parser --- async-graphql-derive/src/enum.rs | 2 +- async-graphql-derive/src/interface.rs | 8 +- async-graphql-derive/src/merged_object.rs | 2 +- async-graphql-derive/src/object.rs | 10 +- async-graphql-derive/src/scalar.rs | 2 +- async-graphql-derive/src/simple_object.rs | 8 +- async-graphql-derive/src/subscription.rs | 8 +- async-graphql-derive/src/union.rs | 4 +- async-graphql-derive/src/utils.rs | 5 +- async-graphql-parser/Cargo.toml | 7 +- async-graphql-parser/src/error.rs | 35 - async-graphql-parser/src/graphql.pest | 69 ++ async-graphql-parser/src/lib.rs | 68 +- async-graphql-parser/src/parser.rs | 479 +++++++++++ async-graphql-parser/src/pos.rs | 64 +- async-graphql-parser/src/query.pest | 51 -- async-graphql-parser/src/query.rs | 243 ------ async-graphql-parser/src/query_parser.rs | 530 ------------ async-graphql-parser/src/schema.pest | 72 -- async-graphql-parser/src/schema.rs | 172 ---- async-graphql-parser/src/schema_parser.rs | 766 ------------------ async-graphql-parser/src/types.rs | 542 +++++++++++++ async-graphql-parser/src/utils.rs | 193 ++--- async-graphql-parser/src/value.rs | 221 ----- src/base.rs | 10 +- src/context.rs | 328 +++----- src/error.rs | 5 +- src/extensions/logger.rs | 32 +- src/extensions/mod.rs | 2 +- src/lib.rs | 2 +- src/look_ahead.rs | 20 +- src/mutation_resolver.rs | 50 +- src/query.rs | 28 +- src/registry.rs | 52 +- src/resolver.rs | 50 +- src/scalars/json.rs | 2 +- src/scalars/string.rs | 2 +- src/schema.rs | 35 +- src/subscription/subscription_type.rs | 32 +- src/types/connection/connection_type.rs | 14 +- src/types/connection/edge.rs | 10 +- src/types/empty_mutation.rs | 4 +- src/types/list.rs | 2 +- src/types/merged_object.rs | 2 +- src/types/optional.rs | 2 +- src/types/query_root.rs | 20 +- src/types/upload.rs | 6 +- src/validation/mod.rs | 9 +- .../rules/arguments_of_correct_type.rs | 17 +- .../rules/default_values_of_correct_type.rs | 21 +- .../rules/fields_on_correct_type.rs | 16 +- .../rules/fragments_on_composite_types.rs | 11 +- src/validation/rules/known_argument_names.rs | 21 +- src/validation/rules/known_directives.rs | 25 +- src/validation/rules/known_fragment_names.rs | 9 +- src/validation/rules/known_type_names.rs | 17 +- .../rules/lone_anonymous_operation.rs | 30 +- src/validation/rules/no_fragment_cycles.rs | 9 +- .../rules/no_undefined_variables.rs | 21 +- src/validation/rules/no_unused_fragments.rs | 23 +- src/validation/rules/no_unused_variables.rs | 17 +- .../rules/overlapping_fields_can_be_merged.rs | 32 +- .../rules/possible_fragment_spreads.rs | 25 +- .../rules/provided_non_null_arguments.rs | 17 +- src/validation/rules/scalar_leafs.rs | 17 +- src/validation/rules/unique_argument_names.rs | 7 +- src/validation/rules/unique_fragment_names.rs | 9 +- .../rules/unique_operation_names.rs | 25 +- src/validation/rules/unique_variable_names.rs | 9 +- src/validation/rules/upload_file.rs | 29 +- .../rules/variables_are_input_types.rs | 9 +- .../rules/variables_in_allowed_position.rs | 33 +- src/validation/test_harness.rs | 10 +- src/validation/utils.rs | 24 +- src/validation/visitor.rs | 120 ++- src/validation/visitors/cache_control.rs | 4 +- src/validation/visitors/complexity.rs | 2 +- src/validation/visitors/depth.rs | 2 +- tests/input_validators.rs | 2 +- 79 files changed, 1817 insertions(+), 3076 deletions(-) delete mode 100644 async-graphql-parser/src/error.rs create mode 100644 async-graphql-parser/src/graphql.pest create mode 100644 async-graphql-parser/src/parser.rs delete mode 100644 async-graphql-parser/src/query.pest delete mode 100644 async-graphql-parser/src/query.rs delete mode 100644 async-graphql-parser/src/query_parser.rs delete mode 100644 async-graphql-parser/src/schema.pest delete mode 100644 async-graphql-parser/src/schema.rs delete mode 100644 async-graphql-parser/src/schema_parser.rs create mode 100644 async-graphql-parser/src/types.rs delete mode 100644 async-graphql-parser/src/value.rs diff --git a/async-graphql-derive/src/enum.rs b/async-graphql-derive/src/enum.rs index 884e041a..8bb76139 100644 --- a/async-graphql-derive/src/enum.rs +++ b/async-graphql-derive/src/enum.rs @@ -117,7 +117,7 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result, _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()) } } diff --git a/async-graphql-derive/src/interface.rs b/async-graphql-derive/src/interface.rs index fa8f2a50..717ea5fd 100644 --- a/async-graphql-derive/src/interface.rs +++ b/async-graphql-derive/src/interface.rs @@ -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 } } diff --git a/async-graphql-derive/src/merged_object.rs b/async-graphql-derive/src/merged_object.rs index 3dfb8c0c..b7c92be7 100644 --- a/async-graphql-derive/src/merged_object.rs +++ b/async-graphql-derive/src/merged_object.rs @@ -96,7 +96,7 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result, _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 } } diff --git a/async-graphql-derive/src/object.rs b/async-graphql-derive/src/object.rs index be451d5c..f276b919 100644 --- a/async-graphql-derive/src/object.rs +++ b/async-graphql-derive/src/object.rs @@ -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 } } diff --git a/async-graphql-derive/src/scalar.rs b/async-graphql-derive/src/scalar.rs index 5bcced2d..a83c1f83 100644 --- a/async-graphql-derive/src/scalar.rs +++ b/async-graphql-derive/src/scalar.rs @@ -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()) } diff --git a/async-graphql-derive/src/simple_object.rs b/async-graphql-derive/src/simple_object.rs index fbbdf75e..a9e15a0b 100644 --- a/async-graphql-derive/src/simple_object.rs +++ b/async-graphql-derive/src/simple_object.rs @@ -135,10 +135,10 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result Result) -> #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, _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 } } diff --git a/async-graphql-derive/src/subscription.rs b/async-graphql-derive/src/subscription.rs index 987920f6..4a4c04ee 100644 --- a/async-graphql-derive/src/subscription.rs +++ b/async-graphql-derive/src/subscription.rs @@ -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> + 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())) } diff --git a/async-graphql-derive/src/union.rs b/async-graphql-derive/src/union.rs index 3fb419d4..c9ac6315 100644 --- a/async-graphql-derive/src/union.rs +++ b/async-graphql-derive/src/union.rs @@ -132,7 +132,7 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result) -> #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, _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 } } diff --git a/async-graphql-derive/src/utils.rs b/async-graphql-derive/src/utils.rs index 417e64cb..d723abd2 100644 --- a/async-graphql-derive/src/utils.rs +++ b/async-graphql-derive/src/utils.rs @@ -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() } } diff --git a/async-graphql-parser/Cargo.toml b/async-graphql-parser/Cargo.toml index 8a4c06f4..e5c61890 100644 --- a/async-graphql-parser/Cargo.toml +++ b/async-graphql-parser/Cargo.toml @@ -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"] } diff --git a/async-graphql-parser/src/error.rs b/async-graphql-parser/src/error.rs deleted file mode 100644 index 158e8bf0..00000000 --- a/async-graphql-parser/src/error.rs +++ /dev/null @@ -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 From> for Error { - fn from(err: pest::error::Error) -> Self { - Error { - pos: { - let (line, column) = match err.line_col { - LineColLocation::Pos((line, column)) => (line, column), - LineColLocation::Span((line, column), _) => (line, column), - }; - Pos { line, column } - }, - message: err.to_string(), - } - } -} - -/// Parser result -pub type Result = std::result::Result; diff --git a/async-graphql-parser/src/graphql.pest b/async-graphql-parser/src/graphql.pest new file mode 100644 index 00000000..a4d87de7 --- /dev/null +++ b/async-graphql-parser/src/graphql.pest @@ -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 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 | "_")* } diff --git a/async-graphql-parser/src/lib.rs b/async-graphql-parser/src/lib.rs index bc7a76dd..3393677c 100644 --- a/async-graphql-parser/src/lib.rs +++ b/async-graphql-parser/src/lib.rs @@ -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 From> for Error { + fn from(err: pest::error::Error) -> 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`. +pub type Result = std::result::Result; diff --git a/async-graphql-parser/src/parser.rs b/async-graphql-parser/src/parser.rs new file mode 100644 index 00000000..74ed1b09 --- /dev/null +++ b/async-graphql-parser/src/parser.rs @@ -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>(input: T) -> Result { + 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, 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, + 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, + pc: &mut PositionCalculator, +) -> Positioned { + 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, + 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, pc: &mut PositionCalculator) -> Positioned { + 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, + pc: &mut PositionCalculator, +) -> Vec> { + 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, + pc: &mut PositionCalculator, +) -> Positioned { + 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, pc: &mut PositionCalculator) -> Positioned { + debug_assert_eq!(pair.as_rule(), Rule::variable); + + parse_name(&exactly_one(pair.into_inner()), pc) +} + +fn parse_default_value(pair: Pair, pc: &mut PositionCalculator) -> Positioned { + debug_assert_eq!(pair.as_rule(), Rule::default_value); + + parse_value(exactly_one(pair.into_inner()), pc) +} + +fn parse_type(pair: &Pair, pc: &mut PositionCalculator) -> Positioned { + debug_assert_eq!(pair.as_rule(), Rule::type_); + + Positioned::new(Type::new(pair.as_str()), pc.step(&pair)) +} + +fn parse_value(pair: Pair, pc: &mut PositionCalculator) -> Positioned { + 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, + pc: &mut PositionCalculator, +) -> (Positioned, Positioned) { + 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, + pc: &mut PositionCalculator, +) -> Positioned { + 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, pc: &mut PositionCalculator) -> Positioned { + 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, pc: &mut PositionCalculator) -> Positioned { + 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, pc: &mut PositionCalculator) -> Positioned { + debug_assert_eq!(pair.as_rule(), Rule::alias); + parse_name(&exactly_one(pair.into_inner()), pc) +} + +fn parse_arguments( + pair: Pair, + pc: &mut PositionCalculator, +) -> Vec<(Positioned, Positioned)> { + 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, + pc: &mut PositionCalculator, +) -> Positioned { + 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, + pc: &mut PositionCalculator, +) -> Positioned { + 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, + pc: &mut PositionCalculator, +) -> Positioned { + 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, + pc: &mut PositionCalculator, +) -> Positioned { + 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, + pc: &mut PositionCalculator, +) -> Vec> { + debug_assert_eq!(pair.as_rule(), Rule::directives); + + pair.into_inner().map(|pair| parse_directive(pair, pc)).collect() +} + +fn parse_directive(pair: Pair, pc: &mut PositionCalculator) -> Positioned { + 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, pc: &mut PositionCalculator) -> Positioned { + 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> { + if pairs.peek().map_or(false, |pair| pair.as_rule() == rule) { + Some(pairs.next().unwrap()) + } else { + None + } +} +fn exactly_one(iter: impl IntoIterator) -> 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()); + } +} diff --git a/async-graphql-parser/src/pos.rs b/async-graphql-parser/src/pos.rs index 714825df..8afcfa5c 100644 --- a/async-graphql-parser/src/pos.rs +++ b/async-graphql-parser/src/pos.rs @@ -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 { + /// The position of the node. pub pos: Pos, + /// The node itself. pub node: T, } @@ -40,49 +40,22 @@ impl fmt::Display for Positioned { self.node.fmt(f) } } - -impl Positioned { - #[inline] - #[allow(missing_docs)] - pub fn clone_inner(&self) -> T { - self.node.clone() - } -} - impl PartialEq for Positioned { fn eq(&self, other: &Self) -> bool { - self.node.eq(&other.node) + self.node == other.node } } - +impl Eq for Positioned {} impl PartialOrd for Positioned { fn partial_cmp(&self, other: &Self) -> Option { self.node.partial_cmp(&other.node) } } - impl Ord for Positioned { fn cmp(&self, other: &Self) -> Ordering { self.node.cmp(&other.node) } } - -impl Eq for Positioned {} - -impl Deref for Positioned { - type Target = T; - - fn deref(&self) -> &T { - &self.node - } -} - -impl DerefMut for Positioned { - fn deref_mut(&mut self) -> &mut T { - &mut self.node - } -} - impl Hash for Positioned { fn hash(&self, state: &mut H) { self.node.hash(state) @@ -102,26 +75,15 @@ impl BorrowMut for Positioned { } impl Positioned { - pub(crate) fn new(node: T, pos: Pos) -> Positioned { + /// Create a new positioned node from the node and its position. + #[must_use] + pub const fn new(node: T, pos: Pos) -> Positioned { 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 R, R>(self, f: F) -> Positioned { - Positioned { - pos: self.pos, - node: f(self), - } - } } diff --git a/async-graphql-parser/src/query.pest b/async-graphql-parser/src/query.pest deleted file mode 100644 index 16fa6962..00000000 --- a/async-graphql-parser/src/query.pest +++ /dev/null @@ -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} diff --git a/async-graphql-parser/src/query.rs b/async-graphql-parser/src/query.rs deleted file mode 100644 index 3c06b221..00000000 --- a/async-graphql-parser/src/query.rs +++ /dev/null @@ -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), - NonNull(Box), -} - -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, - pub arguments: Vec<(Positioned, Positioned)>, -} - -impl Directive { - pub fn get_argument(&self, name: &str) -> Option<&Positioned> { - self.arguments - .iter() - .find(|item| item.0.node == name) - .map(|item| &item.1) - } -} - -pub type FragmentsMap = HashMap>; - -/// 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>, - pub selection_set: Positioned, -} - -#[derive(Debug, Clone)] -pub struct Document { - pub(crate) definitions: Vec>, - pub(crate) fragments: FragmentsMap, - pub(crate) current_operation: Option, -} - -impl Document { - #[inline] - pub fn definitions(&self) -> &[Positioned] { - &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), - Fragment(Positioned), -} - -#[derive(Debug, Clone)] -pub enum TypeCondition { - On(Positioned), -} - -#[derive(Debug, Clone)] -pub struct FragmentDefinition { - pub name: Positioned, - pub type_condition: Positioned, - pub directives: Vec>, - pub selection_set: Positioned, -} - -#[derive(Debug, Clone)] -pub enum OperationDefinition { - SelectionSet(Positioned), - Query(Positioned), - Mutation(Positioned), - Subscription(Positioned), -} - -#[derive(Debug, Clone)] -pub struct Query { - pub name: Option>, - pub variable_definitions: Vec>, - pub directives: Vec>, - pub selection_set: Positioned, -} - -#[derive(Debug, Clone)] -pub struct Mutation { - pub name: Option>, - pub variable_definitions: Vec>, - pub directives: Vec>, - pub selection_set: Positioned, -} - -#[derive(Debug, Clone)] -pub struct Subscription { - pub name: Option>, - pub variable_definitions: Vec>, - pub directives: Vec>, - pub selection_set: Positioned, -} - -#[derive(Debug, Default, Clone)] -pub struct SelectionSet { - pub items: Vec>, -} - -#[derive(Debug, Clone)] -pub struct VariableDefinition { - pub name: Positioned, - pub var_type: Positioned, - pub default_value: Option>, -} - -#[derive(Debug, Clone)] -pub enum Selection { - Field(Positioned), - FragmentSpread(Positioned), - InlineFragment(Positioned), -} - -#[derive(Debug, Clone)] -pub struct Field { - pub alias: Option>, - pub name: Positioned, - pub arguments: Vec<(Positioned, Positioned)>, - pub directives: Vec>, - pub selection_set: Positioned, -} - -impl Field { - pub fn get_argument(&self, name: &str) -> Option<&Positioned> { - self.arguments - .iter() - .find(|item| item.0.node == name) - .map(|item| &item.1) - } -} - -#[derive(Debug, Clone)] -pub struct FragmentSpread { - pub fragment_name: Positioned, - pub directives: Vec>, -} - -#[derive(Debug, Clone)] -pub struct InlineFragment { - pub type_condition: Option>, - pub directives: Vec>, - pub selection_set: Positioned, -} diff --git a/async-graphql-parser/src/query_parser.rs b/async-graphql-parser/src/query_parser.rs deleted file mode 100644 index 25f97840..00000000 --- a/async-graphql-parser/src/query_parser.rs +++ /dev/null @@ -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>(input: T) -> Result { - let document_pair: Pair = 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, - pc: &mut PositionCalculator, -) -> Result> { - 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, pc: &mut PositionCalculator) -> Result { - 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, pc: &mut PositionCalculator) -> Result { - let pair = pair.into_inner().next().unwrap(); - match pair.as_rule() { - Rule::nonnull_type => Ok(Type::NonNull(Box::new(parse_type(pair, pc)?))), - Rule::list_type => Ok(Type::List(Box::new(parse_type(pair, pc)?))), - Rule::name => Ok(Type::Named(pair.as_str().to_string())), - Rule::type_ => parse_type(pair, pc), - _ => unreachable!(), - } -} - -fn parse_variable_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - 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, - pc: &mut PositionCalculator, -) -> Result>> { - 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, pc: &mut PositionCalculator) -> Result> { - let pos = pc.step(&pair); - let mut name = None; - let mut arguments = None; - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::name => { - let pos = pc.step(&pair); - name = Some(Positioned::new(pair.as_str().to_string(), pos)) - } - Rule::arguments => arguments = Some(parse_arguments(pair, pc)?), - _ => unreachable!(), - } - } - Ok(Positioned::new( - Directive { - name: name.unwrap(), - arguments: arguments.unwrap_or_default(), - }, - pos, - )) -} - -fn parse_directives( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result>> { - let mut directives = Vec::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::directive => directives.push(parse_directive(pair, pc)?), - _ => unreachable!(), - } - } - Ok(directives) -} - -fn parse_variable(pair: Pair, pc: &mut PositionCalculator) -> Result> { - 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, pc: &mut PositionCalculator) -> Result { - let pair = pair.into_inner().next().unwrap(); - Ok(match pair.as_rule() { - Rule::object => parse_object_value(pair, pc)?, - Rule::array => parse_array_value(pair, pc)?, - Rule::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, 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, pc: &mut PositionCalculator) -> Result { - let mut map = BTreeMap::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::pair => map.extend(std::iter::once(parse_object_pair(pair, pc)?)), - _ => unreachable!(), - } - } - Ok(Value::Object(map)) -} - -fn parse_array_value(pair: Pair, pc: &mut PositionCalculator) -> Result { - let mut array = Vec::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::value => array.push(parse_value2(pair, pc)?), - _ => unreachable!(), - } - } - Ok(Value::List(array)) -} - -fn parse_pair( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result<(Positioned, Positioned)> { - let mut name = None; - let mut value = None; - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), - Rule::value => { - value = { - let pos = pc.step(&pair); - Some(Positioned::new(parse_value2(pair, pc)?, pos)) - } - } - _ => unreachable!(), - } - } - Ok((name.unwrap(), value.unwrap())) -} - -fn parse_arguments( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result, Positioned)>> { - let mut arguments = Vec::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::pair => arguments.extend(std::iter::once(parse_pair(pair, pc)?)), - _ => unreachable!(), - } - } - Ok(arguments) -} - -fn parse_alias(pair: Pair, pc: &mut PositionCalculator) -> Result> { - 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, pc: &mut PositionCalculator) -> Result> { - 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, - pc: &mut PositionCalculator, -) -> Result> { - 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, - pc: &mut PositionCalculator, -) -> Result> { - 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, - pc: &mut PositionCalculator, -) -> Result> { - 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, - pc: &mut PositionCalculator, -) -> Result> { - 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, - pc: &mut PositionCalculator, -) -> Result> { - 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()); - } -} diff --git a/async-graphql-parser/src/schema.pest b/async-graphql-parser/src/schema.pest deleted file mode 100644 index dee6be5c..00000000 --- a/async-graphql-parser/src/schema.pest +++ /dev/null @@ -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} diff --git a/async-graphql-parser/src/schema.rs b/async-graphql-parser/src/schema.rs deleted file mode 100644 index f867ba63..00000000 --- a/async-graphql-parser/src/schema.rs +++ /dev/null @@ -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), - Object(BTreeMap), -} - -#[derive(Debug, PartialEq)] -pub enum Type { - Named(String), - List(Box), - NonNull(Box), -} - -#[derive(Debug)] -pub struct Document { - pub definitions: Vec>, -} - -#[derive(Debug)] -pub enum Definition { - SchemaDefinition(Positioned), - TypeDefinition(Positioned), - DirectiveDefinition(Positioned), -} - -#[derive(Debug)] -pub struct SchemaDefinition { - pub extend: bool, - pub directives: Vec>, - pub query: Option>, - pub mutation: Option>, - pub subscription: Option>, -} - -#[derive(Debug)] -pub enum TypeDefinition { - Scalar(Positioned), - Object(Positioned), - Interface(Positioned), - Union(Positioned), - Enum(Positioned), - InputObject(Positioned), -} - -#[derive(Debug)] -pub struct ScalarType { - pub extend: bool, - pub description: Option>, - pub name: Positioned, - pub directives: Vec>, -} - -#[derive(Debug)] -pub struct ObjectType { - pub extend: bool, - pub description: Option>, - pub name: Positioned, - pub implements_interfaces: Vec>, - pub directives: Vec>, - pub fields: Vec>, -} - -#[derive(Debug)] -pub struct Field { - pub description: Option>, - pub name: Positioned, - pub arguments: Vec>, - pub ty: Positioned, - pub directives: Vec>, -} - -#[derive(Debug)] -pub struct InputValue { - pub description: Option>, - pub name: Positioned, - pub ty: Positioned, - pub default_value: Option>, - pub directives: Vec>, -} - -#[derive(Debug)] -pub struct InterfaceType { - pub extend: bool, - pub description: Option>, - pub name: Positioned, - pub implements_interfaces: Vec>, - pub directives: Vec>, - pub fields: Vec>, -} - -#[derive(Debug)] -pub struct UnionType { - pub extend: bool, - pub description: Option>, - pub name: Positioned, - pub directives: Vec>, - pub members: Vec>, -} - -#[derive(Debug)] -pub struct EnumType { - pub extend: bool, - pub description: Option>, - pub name: Positioned, - pub directives: Vec>, - pub values: Vec>, -} - -#[derive(Debug)] -pub struct EnumValue { - pub description: Option>, - pub name: Positioned, - pub directives: Vec>, -} - -#[derive(Debug)] -pub struct InputObjectType { - pub extend: bool, - pub description: Option>, - pub name: Positioned, - pub directives: Vec>, - pub fields: Vec>, -} - -#[derive(Debug)] -pub enum DirectiveLocation { - // executable - Query, - Mutation, - Subscription, - Field, - FragmentDefinition, - FragmentSpread, - InlineFragment, - - // type_system - Schema, - Scalar, - Object, - FieldDefinition, - ArgumentDefinition, - Interface, - Union, - Enum, - EnumValue, - InputObject, - InputFieldDefinition, -} - -#[derive(Debug)] -pub struct DirectiveDefinition { - pub description: Option>, - pub name: Positioned, - pub arguments: Vec>, - pub locations: Vec>, -} - -#[derive(Debug)] -pub struct Directive { - pub name: Positioned, - pub arguments: Vec<(Positioned, Positioned)>, -} diff --git a/async-graphql-parser/src/schema_parser.rs b/async-graphql-parser/src/schema_parser.rs deleted file mode 100644 index 948d8226..00000000 --- a/async-graphql-parser/src/schema_parser.rs +++ /dev/null @@ -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>(input: T) -> Result { - let document_pair: Pair = SchemaParser::parse(Rule::document, input.as_ref())? - .next() - .unwrap(); - let mut definitions = Vec::new(); - let mut pc = PositionCalculator::new(input.as_ref()); - - for pair in document_pair.into_inner() { - match pair.as_rule() { - Rule::definition => { - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::schema_definition => { - definitions.push( - parse_schema_definition(pair, &mut pc)? - .pack(Definition::SchemaDefinition), - ); - } - Rule::sclar_type_definition => definitions.push( - parse_scalar_type(pair, &mut pc)? - .pack(TypeDefinition::Scalar) - .pack(Definition::TypeDefinition), - ), - Rule::object_type_definition => definitions.push( - parse_object_type(pair, &mut pc)? - .pack(TypeDefinition::Object) - .pack(Definition::TypeDefinition), - ), - Rule::interface_type_definition => definitions.push( - parse_interface_type(pair, &mut pc)? - .pack(TypeDefinition::Interface) - .pack(Definition::TypeDefinition), - ), - Rule::union_type_definition => definitions.push( - parse_union_type(pair, &mut pc)? - .pack(TypeDefinition::Union) - .pack(Definition::TypeDefinition), - ), - Rule::enum_type_definition => definitions.push( - parse_enum_type(pair, &mut pc)? - .pack(TypeDefinition::Enum) - .pack(Definition::TypeDefinition), - ), - Rule::input_type_definition => definitions.push( - parse_input_type(pair, &mut pc)? - .pack(TypeDefinition::InputObject) - .pack(Definition::TypeDefinition), - ), - Rule::directive_definition => definitions.push( - parse_directive_definition(pair, &mut pc)? - .pack(Definition::DirectiveDefinition), - ), - _ => unreachable!(), - } - } - } - Rule::EOI => {} - _ => unreachable!(), - } - } - - Ok(Document { definitions }) -} - -fn parse_schema_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - let pos = pc.step(&pair); - let mut extend = false; - let mut directives = None; - let mut query = None; - let mut mutation = None; - let mut subscription = None; - - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::extend => extend = true, - Rule::directives => directives = Some(parse_directives(pair, pc)?), - Rule::root_operation_type_definition => { - let mut op_name = None; - let mut ty_name = None; - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::operation_type => op_name = Some(pair.as_str().to_string()), - Rule::name => { - ty_name = - Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) - } - _ => unreachable!(), - } - } - match op_name.as_deref() { - Some("query") => query = ty_name, - Some("mutation") => mutation = ty_name, - Some("subscription") => subscription = ty_name, - _ => unreachable!(), - } - } - _ => unreachable!(), - } - } - - Ok(Positioned::new( - SchemaDefinition { - extend, - directives: directives.unwrap_or_default(), - query, - mutation, - subscription, - }, - pos, - )) -} - -fn parse_directive(pair: Pair, pc: &mut PositionCalculator) -> Result> { - let pos = pc.step(&pair); - let mut name = None; - let mut arguments = None; - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::name => { - let pos = pc.step(&pair); - name = Some(Positioned::new(pair.as_str().to_string(), pos)) - } - Rule::arguments => arguments = Some(parse_arguments(pair, pc)?), - _ => unreachable!(), - } - } - Ok(Positioned::new( - Directive { - name: name.unwrap(), - arguments: arguments.unwrap_or_default(), - }, - pos, - )) -} - -fn parse_directives( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result>> { - let mut directives = Vec::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::directive => directives.push(parse_directive(pair, pc)?), - _ => unreachable!(), - } - } - Ok(directives) -} - -fn parse_arguments( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result, Positioned)>> { - let mut arguments = Vec::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::pair => arguments.extend(std::iter::once(parse_pair(pair, pc)?)), - _ => unreachable!(), - } - } - Ok(arguments) -} - -fn parse_pair( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result<(Positioned, Positioned)> { - let mut name = None; - let mut value = None; - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), - Rule::value => { - value = { - let pos = pc.step(&pair); - Some(Positioned::new(parse_value(pair, pc)?, pos)) - } - } - _ => unreachable!(), - } - } - Ok((name.unwrap(), value.unwrap())) -} - -fn parse_value(pair: Pair, pc: &mut PositionCalculator) -> Result { - let pair = pair.into_inner().next().unwrap(); - Ok(match pair.as_rule() { - Rule::object => parse_object_value(pair, pc)?, - Rule::array => parse_array_value(pair, pc)?, - Rule::float => Value::Float(pair.as_str().parse().unwrap()), - Rule::int => Value::Int(pair.as_str().parse().unwrap()), - Rule::string => Value::String({ - let pos = pc.step(&pair); - unquote_string(pair.as_str(), pos)? - }), - Rule::name => Value::Enum(pair.to_string()), - Rule::boolean => Value::Boolean(match pair.as_str() { - "true" => true, - "false" => false, - _ => unreachable!(), - }), - Rule::null => Value::Null, - _ => unreachable!(), - }) -} - -fn parse_object_pair(pair: Pair, pc: &mut PositionCalculator) -> Result<(String, Value)> { - let mut name = None; - let mut value = None; - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::name => name = Some(pair.as_str().to_string()), - Rule::value => value = Some(parse_value(pair, pc)?), - _ => unreachable!(), - } - } - Ok((name.unwrap(), value.unwrap())) -} - -fn parse_object_value(pair: Pair, pc: &mut PositionCalculator) -> Result { - let mut map = BTreeMap::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::pair => map.extend(std::iter::once(parse_object_pair(pair, pc)?)), - _ => unreachable!(), - } - } - Ok(Value::Object(map)) -} - -fn parse_array_value(pair: Pair, pc: &mut PositionCalculator) -> Result { - let mut array = Vec::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::value => array.push(parse_value(pair, pc)?), - _ => unreachable!(), - } - } - Ok(Value::List(array)) -} - -fn parse_scalar_type( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - let pos = pc.step(&pair); - let mut description = None; - let mut extend = false; - let mut name = None; - let mut directives = None; - - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::string => { - description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) - } - Rule::extend => extend = true, - Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), - Rule::directives => directives = Some(parse_directives(pair, pc)?), - _ => unreachable!(), - } - } - - Ok(Positioned::new( - ScalarType { - extend, - description, - name: name.unwrap(), - directives: directives.unwrap_or_default(), - }, - pos, - )) -} - -fn parse_implements_interfaces( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result>> { - let mut interfaces = Vec::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::name => { - interfaces.push(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) - } - _ => unreachable!(), - } - } - Ok(interfaces) -} - -fn parse_type(pair: Pair, pc: &mut PositionCalculator) -> Result { - let pair = pair.into_inner().next().unwrap(); - match pair.as_rule() { - Rule::nonnull_type => Ok(Type::NonNull(Box::new(parse_type(pair, pc)?))), - Rule::list_type => Ok(Type::List(Box::new(parse_type(pair, pc)?))), - Rule::name => Ok(Type::Named(pair.as_str().to_string())), - Rule::type_ => parse_type(pair, pc), - _ => unreachable!(), - } -} - -fn parse_input_value_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - let pos = pc.step(&pair); - let mut description = None; - let mut name = None; - let mut type_ = None; - let mut default_value = None; - let mut directives = None; - - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::string => { - description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) - } - Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), - Rule::type_ => { - let pos = pc.step(&pair); - type_ = Some(Positioned::new(parse_type(pair, pc)?, pos)); - } - Rule::default_value => { - let pos = pc.step(&pair); - default_value = Some(Positioned::new( - parse_value(pair.into_inner().next().unwrap(), pc)?, - pos, - )); - } - Rule::directives => directives = Some(parse_directives(pair, pc)?), - _ => unreachable!(), - } - } - - Ok(Positioned::new( - InputValue { - description, - name: name.unwrap(), - default_value, - ty: type_.unwrap(), - directives: directives.unwrap_or_default(), - }, - pos, - )) -} - -fn parse_arguments_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result>> { - let mut arguments = Vec::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::input_value_definition => arguments.push(parse_input_value_definition(pair, pc)?), - _ => unreachable!(), - } - } - Ok(arguments) -} - -fn parse_field_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - let pos = pc.step(&pair); - let mut description = None; - let mut name = None; - let mut arguments = None; - let mut type_ = None; - let mut directives = None; - - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::string => { - description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) - } - Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), - Rule::arguments_definition => arguments = Some(parse_arguments_definition(pair, pc)?), - Rule::type_ => { - let pos = pc.step(&pair); - type_ = Some(Positioned::new(parse_type(pair, pc)?, pos)); - } - Rule::directives => directives = Some(parse_directives(pair, pc)?), - _ => unreachable!(), - } - } - - Ok(Positioned::new( - Field { - description, - name: name.unwrap(), - arguments: arguments.unwrap_or_default(), - ty: type_.unwrap(), - directives: directives.unwrap_or_default(), - }, - pos, - )) -} - -fn parse_fields_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result>> { - let mut fields = Vec::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::field_definition => fields.push(parse_field_definition(pair, pc)?), - _ => unreachable!(), - } - } - Ok(fields) -} - -fn parse_object_type( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - let pos = pc.step(&pair); - let mut description = None; - let mut extend = false; - let mut name = None; - let mut implements_interfaces = None; - let mut directives = None; - let mut fields = None; - - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::string => { - description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) - } - Rule::extend => extend = true, - Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), - Rule::implements_interfaces => { - implements_interfaces = Some(parse_implements_interfaces(pair, pc)?) - } - Rule::directives => directives = Some(parse_directives(pair, pc)?), - Rule::fields_definition => fields = Some(parse_fields_definition(pair, pc)?), - _ => unreachable!(), - } - } - - Ok(Positioned::new( - ObjectType { - extend, - description, - name: name.unwrap(), - implements_interfaces: implements_interfaces.unwrap_or_default(), - directives: directives.unwrap_or_default(), - fields: fields.unwrap_or_default(), - }, - pos, - )) -} - -fn parse_interface_type( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - let pos = pc.step(&pair); - let mut description = None; - let mut extend = false; - let mut name = None; - let mut 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, - pc: &mut PositionCalculator, -) -> Result>> { - let mut members = Vec::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::name => members.push(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), - _ => unreachable!(), - } - } - Ok(members) -} - -fn parse_union_type( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - let pos = pc.step(&pair); - let mut description = None; - let mut extend = false; - let mut name = None; - let mut directives = None; - let mut members = None; - - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::string => { - description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) - } - Rule::extend => extend = true, - Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), - Rule::directives => directives = Some(parse_directives(pair, pc)?), - Rule::union_member_types => members = Some(parse_union_members(pair, pc)?), - _ => unreachable!(), - } - } - - Ok(Positioned::new( - UnionType { - extend, - description, - name: name.unwrap(), - directives: directives.unwrap_or_default(), - members: members.unwrap_or_default(), - }, - pos, - )) -} - -fn parse_enum_value( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - let pos = pc.step(&pair); - let mut description = None; - let mut name = None; - let mut directives = None; - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::string => { - description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) - } - Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), - Rule::directives => directives = Some(parse_directives(pair, pc)?), - _ => unreachable!(), - } - } - Ok(Positioned::new( - EnumValue { - description, - name: name.unwrap(), - directives: directives.unwrap_or_default(), - }, - pos, - )) -} - -fn parse_enum_values( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result>> { - let mut values = Vec::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::enum_value_definition => values.push(parse_enum_value(pair, pc)?), - _ => unreachable!(), - } - } - Ok(values) -} - -fn parse_enum_type(pair: Pair, pc: &mut PositionCalculator) -> Result> { - let pos = pc.step(&pair); - let mut description = None; - let mut extend = false; - let mut name = None; - let mut directives = None; - let mut values = None; - - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::string => { - description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) - } - Rule::extend => extend = true, - Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), - Rule::directives => directives = Some(parse_directives(pair, pc)?), - Rule::enum_values_definition => values = Some(parse_enum_values(pair, pc)?), - _ => unreachable!(), - } - } - - Ok(Positioned::new( - EnumType { - extend, - description, - name: name.unwrap(), - directives: directives.unwrap_or_default(), - values: values.unwrap_or_default(), - }, - pos, - )) -} - -fn parse_input_type( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - let pos = pc.step(&pair); - let mut description = None; - let mut extend = false; - let mut name = None; - let mut directives = None; - let mut fields = None; - - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::string => { - description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) - } - Rule::extend => extend = true, - Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), - Rule::directives => directives = Some(parse_directives(pair, pc)?), - Rule::input_fields_definition => fields = Some(parse_arguments_definition(pair, pc)?), - _ => unreachable!(), - } - } - - Ok(Positioned::new( - InputObjectType { - extend, - description, - name: name.unwrap(), - directives: directives.unwrap_or_default(), - fields: fields.unwrap_or_default(), - }, - pos, - )) -} - -fn parse_directive_locations( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result>> { - let mut locations = Vec::new(); - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::directive_location => { - let pos = pc.step(&pair); - let loc = match pair.as_str() { - "QUERY" => DirectiveLocation::Query, - "MUTATION" => DirectiveLocation::Mutation, - "SUBSCRIPTION" => DirectiveLocation::Subscription, - "FIELD" => DirectiveLocation::Field, - "FRAGMENT_DEFINITION" => DirectiveLocation::FragmentDefinition, - "FRAGMENT_SPREAD" => DirectiveLocation::FragmentSpread, - "INLINE_FRAGMENT" => DirectiveLocation::InlineFragment, - "SCHEMA" => DirectiveLocation::Schema, - "SCALAR" => DirectiveLocation::Scalar, - "OBJECT" => DirectiveLocation::Object, - "FIELD_DEFINITION" => DirectiveLocation::FieldDefinition, - "ARGUMENT_DEFINITION" => DirectiveLocation::ArgumentDefinition, - "INTERFACE" => DirectiveLocation::Interface, - "UNION" => DirectiveLocation::Union, - "ENUM" => DirectiveLocation::Enum, - "ENUM_VALUE" => DirectiveLocation::EnumValue, - "INPUT_OBJECT" => DirectiveLocation::InputObject, - "INPUT_FIELD_DEFINITION" => DirectiveLocation::InputFieldDefinition, - _ => unreachable!(), - }; - locations.push(Positioned::new(loc, pos)); - } - _ => unreachable!(), - } - } - Ok(locations) -} - -fn parse_directive_definition( - pair: Pair, - pc: &mut PositionCalculator, -) -> Result> { - let pos = pc.step(&pair); - let mut description = None; - let mut name = None; - let mut arguments = None; - let mut locations = None; - - for pair in pair.into_inner() { - match pair.as_rule() { - Rule::string => { - description = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))) - } - Rule::name => name = Some(Positioned::new(pair.as_str().to_string(), pc.step(&pair))), - Rule::arguments_definition => arguments = Some(parse_arguments_definition(pair, pc)?), - Rule::directive_locations => locations = Some(parse_directive_locations(pair, pc)?), - _ => unreachable!(), - } - } - - Ok(Positioned::new( - DirectiveDefinition { - description, - name: name.unwrap(), - arguments: arguments.unwrap(), - locations: locations.unwrap(), - }, - pos, - )) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - - #[test] - fn test_parser() { - for entry in fs::read_dir("tests/schemas").unwrap() { - if let Ok(entry) = entry { - SchemaParser::parse(Rule::document, &fs::read_to_string(entry.path()).unwrap()) - .unwrap(); - } - } - } - - #[test] - fn test_parser_ast() { - for entry in fs::read_dir("tests/schemas").unwrap() { - if let Ok(entry) = entry { - parse_schema(fs::read_to_string(entry.path()).unwrap()).unwrap(); - } - } - } -} diff --git a/async-graphql-parser/src/types.rs b/async-graphql-parser/src/types.rs new file mode 100644 index 00000000..3b9b48b8 --- /dev/null +++ b/async-graphql-parser/src/types.rs @@ -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, +} + +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 { + 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, + /// The fragments of the document. + pub fragments: HashMap>, +} + +/// 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), + /// The definition of a fragment. + Fragment(Positioned), +} + +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> { + 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> { + 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>, + /// The variable definitions. + pub variable_definitions: Vec>, + /// The operation's directives. + pub directives: Vec>, + /// The operation's selection set. + pub selection_set: Positioned, +} + +/// 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, + /// The type of the variable. + pub var_type: Positioned, + /// The optional default value of the variable. + pub default_value: Option>, +} + +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), +} + +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), + /// An object. This is a map of keys to values. + Object(BTreeMap), + /// An uploaded file. + #[serde(serialize_with = "serialize_upload")] + Upload(UploadValue), +} +fn serialize_variable(name: &str, serializer: S) -> Result { + serializer.serialize_str(&format!("${}", name)) +} +fn serialize_upload(_: &UploadValue, serializer: S) -> Result { + 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 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::>() + .into(), + Value::Object(obj) => serde_json::Value::Object( + obj.into_iter() + .map(|(name, value)| (name, value.into())) + .collect(), + ), + } + } +} + +impl From 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, + /// 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 { + 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>, +} + + +/// 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), + /// Select using a fragment. + FragmentSpread(Positioned), + /// Select using an inline fragment. + InlineFragment(Positioned), +} + +impl Selection { + /// Get a reference to the directives of the selection. + #[must_use] + pub fn directives(&self) -> &Vec> { + 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> { + 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>, + /// The name of the field. + pub name: Positioned, + /// The arguments to the field, empty if no arguments are provided. + pub arguments: Vec<(Positioned, Positioned)>, + /// The directives in the field selector. + pub directives: Vec>, + /// The subfields being selected in this field, if it is an object. Empty if no fields are + /// being selected. + pub selection_set: Positioned, +} + +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 { + 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> { + 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, + /// The directives in the fragment selector. + pub directives: Vec>, +} + +/// 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>, + /// The directives in the inline fragment. + pub directives: Vec>, + /// The selected fields of the fragment. + pub selection_set: Positioned, +} + +/// 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, + /// The type this fragment operates on. + pub type_condition: Positioned, + /// Directives in the fragment. + pub directives: Vec>, + /// The fragment's selection set. + pub selection_set: Positioned, +} + +/// 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, +} + +/// 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, + /// The arguments to the directive. + pub arguments: Vec<(Positioned, Positioned)>, +} + +impl Directive { + /// Get the argument with the given name. + #[must_use] + pub fn get_argument(&self, name: &str) -> Option<&Positioned> { + self.arguments + .iter() + .find(|item| item.0.node == name) + .map(|item| &item.1) + } +} diff --git a/async-graphql-parser/src/utils.rs b/async-graphql-parser/src/utils.rs index ef00d42e..d8e52cbb 100644 --- a/async-graphql-parser/src/utils.rs +++ b/async-graphql-parser/src/utils.rs @@ -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>, + 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 { - 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("\\\"\\\\"), "\"\\"); } diff --git a/async-graphql-parser/src/value.rs b/async-graphql-parser/src/value.rs deleted file mode 100644 index 3a58be66..00000000 --- a/async-graphql-parser/src/value.rs +++ /dev/null @@ -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, - 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), - Object(BTreeMap), - Upload(UploadValue), -} - -impl serde::Serialize for Value { - fn serialize(&self, serializer: S) -> Result<::Ok, ::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 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::>() - .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 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(), - ), - } - } -} diff --git a/src/base.rs b/src/base.rs index 72062d07..0ac66a8d 100644 --- a/src/base.rs +++ b/src/base.rs @@ -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) -> InputValueResult; - /// 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 { - Err(QueryError::EntityNotFound.into_error(ctx.position())) + Err(QueryError::EntityNotFound.into_error(ctx.pos)) } } @@ -243,7 +243,7 @@ impl OutputValueType for FieldResult { 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())), } } } diff --git a/src/context.rs b/src/context.rs index fcce3e7f..651a45b9 100644 --- a/src/context.rs +++ b/src/context.rs @@ -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; - - 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); 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 { - 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, - 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::() { - 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::() + .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>); 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, pub variables: Variables, - pub document: Document, + pub document: ExecutableDocument, pub ctx_data: Arc, } @@ -298,7 +235,7 @@ impl QueryEnv { pub fn new( extensions: spin::Mutex, variables: Variables, - document: Document, + document: ExecutableDocument, ctx_data: Arc, ) -> 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(&self) -> FieldResult<&D> { self.data_opt::() .ok_or_else(|| format!("Data `{}` does not exist.", std::any::type_name::()).into()) @@ -399,7 +332,7 @@ impl<'a, T> ContextBase<'a, T> { .unwrap_or_else(|| panic!("Data `{}` does not exist.", std::any::type_name::())) } - /// 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(&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 { - 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) -> Result { + 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]) -> 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]) -> Result { 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 != ::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]) -> 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]) -> 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> { name: &str, default: Option T>, ) -> Result { - 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. diff --git a/src/error.rs b/src/error.rs index 5b4035b4..966d548f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -81,9 +81,12 @@ impl From 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(self, cb: C) -> FieldError where C: FnOnce(&Self) -> serde_json::Value, diff --git a/src/extensions/logger.rs b/src/extensions/logger.rs index edb2f2b8..4d5f6cdf 100644 --- a/src/extensions/logger.rs +++ b/src/extensions/logger.rs @@ -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; diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs index e1598dc8..f9bf6e6b 100644 --- a/src/extensions/mod.rs +++ b/src/extensions/mod.rs @@ -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; diff --git a/src/lib.rs b/src/lib.rs index 8957fa7a..1afebf05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; diff --git a/src/look_ahead.rs b/src/look_ahead.rs index c17a4435..6eeb28cb 100644 --- a/src/look_ahead.rs +++ b/src/look_ahead.rs @@ -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); } } diff --git a/src/mutation_resolver.rs b/src/mutation_resolver.rs index d293fc6e..988116fb 100644 --- a/src/mutation_resolver.rs +++ b/src/mutation_resolver.rs @@ -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, ) -> 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, ) diff --git a/src/query.rs b/src/query.rs index cd101392..4463ffcb 100644 --- a/src/query.rs +++ b/src/query.rs @@ -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, 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 => { diff --git a/src/registry.rs b/src/registry.rs index b110a7da..18b7a54c 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -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 { 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>(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; } diff --git a/src/resolver.rs b/src/resolver.rs index c39fad66..465e121e 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -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 { - 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>, ) -> 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::( 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, )?; diff --git a/src/scalars/json.rs b/src/scalars/json.rs index e5f50dfc..b38e161f 100644 --- a/src/scalars/json.rs +++ b/src/scalars/json.rs @@ -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; diff --git a/src/scalars/string.rs b/src/scalars/string.rs index be578e50..e8e04c55 100644 --- a/src/scalars/string.rs +++ b/src/scalars/string.rs @@ -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. diff --git a/src/schema.rs b/src/schema.rs index d4fce291..a10cda68 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -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 { impl SchemaBuilder { - /// 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(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 E + Send + Sync + 'static, E: Extension>( mut self, extension_factory: F, @@ -73,7 +75,7 @@ impl 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(mut self, data: D) -> Self { self.data.insert(data); self @@ -144,7 +146,9 @@ pub struct SchemaInner { pub(crate) env: SchemaEnv, } -/// GraphQL schema +/// GraphQL schema. +/// +/// Cloning a schema is cheap, so it can be easily shared. pub struct Schema(Arc>); impl Clone for Schema { @@ -371,10 +375,11 @@ where variables: Variables, ctx_data: Option>, ) -> Result> + 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(); diff --git a/src/subscription/subscription_type.rs b/src/subscription/subscription_type.rs index 2732c7a2..89e24293 100644 --- a/src/subscription/subscription_type.rs +++ b/src/subscription/subscription_type.rs @@ -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?; diff --git a/src/types/connection/connection_type.rs b/src/types/connection/connection_type.rs index 58b205b8..44d5949d 100644 --- a/src/types/connection/connection_type.rs +++ b/src/types/connection/connection_type.rs @@ -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 { - 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; } diff --git a/src/types/connection/edge.rs b/src/types/connection/edge.rs index 646a273b..faa7de62 100644 --- a/src/types/connection/edge.rs +++ b/src/types/connection/edge.rs @@ -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 { - 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()); } diff --git a/src/types/empty_mutation.rs b/src/types/empty_mutation.rs index 83e257cc..87f14b82 100644 --- a/src/types/empty_mutation.rs +++ b/src/types/empty_mutation.rs @@ -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, ) -> Result { Err(Error::Query { - pos: field.position(), + pos: field.pos, path: None, err: QueryError::NotConfiguredMutations, }) diff --git a/src/types/list.rs b/src/types/list.rs index b3232982..f7f13a51 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -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 Type for Vec { diff --git a/src/types/merged_object.rs b/src/types/merged_object.rs index 0b50754f..d58df705 100644 --- a/src/types/merged_object.rs +++ b/src/types/merged_object.rs @@ -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; diff --git a/src/types/optional.rs b/src/types/optional.rs index 2416e349..fba445a1 100644 --- a/src/types/optional.rs +++ b/src/types/optional.rs @@ -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 Type for Option { diff --git a/src/types/query_root.rs b/src/types/query_root.rs index ce21c824..9297b162 100644 --- a/src/types/query_root.rs +++ b/src/types/query_root.rs @@ -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 Type for QueryRoot { #[async_trait::async_trait] impl ObjectType for QueryRoot { async fn resolve_field(&self, ctx: &Context<'_>) -> Result { - 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 ObjectType for QueryRoot { 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 ObjectType for QueryRoot { ctx.item, ) .await; - } else if ctx.name.node == "_entities" { + } else if ctx.item.node.name.node == "_entities" { let representations: Vec = 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()), diff --git a/src/types/upload.rs b/src/types/upload.rs index ab3bda2f..ad4db887 100644 --- a/src/types/upload.rs +++ b/src/types/upload.rs @@ -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) -> InputValueResult { let value = value.unwrap_or_default(); if let Value::Upload(upload) = value { diff --git a/src/validation/mod.rs b/src/validation/mod.rs index 0998f67c..421cc7a1 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -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}; diff --git a/src/validation/rules/arguments_of_correct_type.rs b/src/validation/rules/arguments_of_correct_type.rs index 81887c6a..0fe5f4a1 100644 --- a/src/validation/rules/arguments_of_correct_type.rs +++ b/src/validation/rules/arguments_of_correct_type.rs @@ -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) { 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() diff --git a/src/validation/rules/default_values_of_correct_type.rs b/src/validation/rules/default_values_of_correct_type.rs index 7bd8df38..cf94125b 100644 --- a/src/validation/rules/default_values_of_correct_type.rs +++ b/src/validation/rules/default_values_of_correct_type.rs @@ -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, ) { - 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 diff --git a/src/validation/rules/fields_on_correct_type.rs b/src/validation/rules/fields_on_correct_type.rs index 8ad841b7..bf95b026 100644 --- a/src/validation/rules/fields_on_correct_type.rs +++ b/src/validation/rules/fields_on_correct_type.rs @@ -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 diff --git a/src/validation/rules/fragments_on_composite_types.rs b/src/validation/rules/fragments_on_composite_types.rs index 40159d1a..387d5379 100644 --- a/src/validation/rules/fragments_on_composite_types.rs +++ b/src/validation/rules/fragments_on_composite_types.rs @@ -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 diff --git a/src/validation/rules/known_argument_names.rs b/src/validation/rules/known_argument_names.rs index af2d4897..e7d1b265 100644 --- a/src/validation/rules/known_argument_names.rs +++ b/src/validation/rules/known_argument_names.rs @@ -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, ) { 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) { 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() diff --git a/src/validation/rules/known_directives.rs b/src/validation/rules/known_directives.rs index dff8c2e9..a00573d0 100644 --- a/src/validation/rules/known_directives.rs +++ b/src/validation/rules/known_directives.rs @@ -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, ) { - 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, ) { - 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() diff --git a/src/validation/rules/known_fragment_names.rs b/src/validation/rules/known_fragment_names.rs index d64ff1b3..93f39fa2 100644 --- a/src/validation/rules/known_fragment_names.rs +++ b/src/validation/rules/known_fragment_names.rs @@ -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, ) { - 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() diff --git a/src/validation/rules/known_type_names.rs b/src/validation/rules/known_type_names.rs index 99a2e81b..0f51d4b6 100644 --- a/src/validation/rules/known_type_names.rs +++ b/src/validation/rules/known_type_names.rs @@ -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, ) { - 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, ) { - 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() diff --git a/src/validation/rules/lone_anonymous_operation.rs b/src/validation/rules/lone_anonymous_operation.rs index c8b57fc4..084d304f 100644 --- a/src/validation/rules/lone_anonymous_operation.rs +++ b/src/validation/rules/lone_anonymous_operation.rs @@ -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, ) { 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() diff --git a/src/validation/rules/no_fragment_cycles.rs b/src/validation/rules/no_fragment_cycles.rs index 191bfb57..05616d9f 100644 --- a/src/validation/rules/no_fragment_cycles.rs +++ b/src/validation/rules/no_fragment_cycles.rs @@ -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, ) { - 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() diff --git a/src/validation/rules/no_undefined_variables.rs b/src/validation/rules/no_undefined_variables.rs index f9805cac..cde608d2 100644 --- a/src/validation/rules/no_undefined_variables.rs +++ b/src/validation/rules/no_undefined_variables.rs @@ -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, ) { - 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, ) { - 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() diff --git a/src/validation/rules/no_unused_fragments.rs b/src/validation/rules/no_unused_fragments.rs index 71065904..7db4e2c3 100644 --- a/src/validation/rules/no_unused_fragments.rs +++ b/src/validation/rules/no_unused_fragments.rs @@ -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, ) { - 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, ) { - 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() diff --git a/src/validation/rules/no_unused_variables.rs b/src/validation/rules/no_unused_variables.rs index 9a87416a..a3a4a02f 100644 --- a/src/validation/rules/no_unused_variables.rs +++ b/src/validation/rules/no_unused_variables.rs @@ -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, ) { - 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, ) { - 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() diff --git a/src/validation/rules/overlapping_fields_can_be_merged.rs b/src/validation/rules/overlapping_fields_can_be_merged.rs index cf4da10f..1c5d1550 100644 --- a/src/validation/rules/overlapping_fields_can_be_merged.rs +++ b/src/validation/rules/overlapping_fields_can_be_merged.rs @@ -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) { - 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) { 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)), } } diff --git a/src/validation/rules/possible_fragment_spreads.rs b/src/validation/rules/possible_fragment_spreads.rs index 4107205e..a2f9ffeb 100644 --- a/src/validation/rules/possible_fragment_spreads.rs +++ b/src/validation/rules/possible_fragment_spreads.rs @@ -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, ) { 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() diff --git a/src/validation/rules/provided_non_null_arguments.rs b/src/validation/rules/provided_non_null_arguments.rs index 2b9299fa..d92f04e1 100644 --- a/src/validation/rules/provided_non_null_arguments.rs +++ b/src/validation/rules/provided_non_null_arguments.rs @@ -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, ) { - 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) { 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 diff --git a/src/validation/rules/scalar_leafs.rs b/src/validation/rules/scalar_leafs.rs index a3dc676c..382b8991 100644 --- a/src/validation/rules/scalar_leafs.rs +++ b/src/validation/rules/scalar_leafs.rs @@ -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) { 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 diff --git a/src/validation/rules/unique_argument_names.rs b/src/validation/rules/unique_argument_names.rs index c02e6227..b169ac6a 100644 --- a/src/validation/rules/unique_argument_names.rs +++ b/src/validation/rules/unique_argument_names.rs @@ -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, _value: &'a Positioned, ) { - 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() diff --git a/src/validation/rules/unique_fragment_names.rs b/src/validation/rules/unique_fragment_names.rs index 134deb10..f0dcdc98 100644 --- a/src/validation/rules/unique_fragment_names.rs +++ b/src/validation/rules/unique_fragment_names.rs @@ -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, ) { - 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() diff --git a/src/validation/rules/unique_operation_names.rs b/src/validation/rules/unique_operation_names.rs index 412c1dcf..ec598afa 100644 --- a/src/validation/rules/unique_operation_names.rs +++ b/src/validation/rules/unique_operation_names.rs @@ -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, ) { - 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() diff --git a/src/validation/rules/unique_variable_names.rs b/src/validation/rules/unique_variable_names.rs index 587b245e..33dd5bbb 100644 --- a/src/validation/rules/unique_variable_names.rs +++ b/src/validation/rules/unique_variable_names.rs @@ -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, ) { - 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() diff --git a/src/validation/rules/upload_file.rs b/src/validation/rules/upload_file.rs index 3c7ae865..af46cd8b 100644 --- a/src/validation/rules/upload_file.rs +++ b/src/validation/rules/upload_file.rs @@ -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, ) { - 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", + ); } } } diff --git a/src/validation/rules/variables_are_input_types.rs b/src/validation/rules/variables_are_input_types.rs index bfbefff9..b3a2d067 100644 --- a/src/validation/rules/variables_are_input_types.rs +++ b/src/validation/rules/variables_are_input_types.rs @@ -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 diff --git a/src/validation/rules/variables_in_allowed_position.rs b/src/validation/rules/variables_in_allowed_position.rs index 99f21504..3146569c 100644 --- a/src/validation/rules/variables_in_allowed_position.rs +++ b/src/validation/rules/variables_in_allowed_position.rs @@ -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, ) { - 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, ) { - 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() diff --git a/src/validation/test_harness.rs b/src/validation/test_harness.rs index 6c56915f..c8d05e4c 100644 --- a/src/validation/test_harness.rs +++ b/src/validation/test_harness.rs @@ -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); }; diff --git a/src/validation/utils.rs b/src/validation/utils.rs index 0a84beff..83d1c312 100644 --- a/src/validation/utils.rs +++ b/src/validation/utils.rs @@ -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: ®istry::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), }; diff --git a/src/validation/visitor.rs b/src/validation/visitor.rs index d5a89458..941da344 100644 --- a/src/validation/visitor.rs +++ b/src/validation/visitor.rs @@ -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, ) { 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, ) { - 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, ) { 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, ) { 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, ) { 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); } diff --git a/src/validation/visitors/cache_control.rs b/src/validation/visitors/cache_control.rs index f0c9796e..1f1a04e9 100644 --- a/src/validation/visitors/cache_control.rs +++ b/src/validation/visitors/cache_control.rs @@ -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) { 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(®istry_field.cache_control); } diff --git a/src/validation/visitors/complexity.rs b/src/validation/visitors/complexity.rs index 37faca0a..e86934de 100644 --- a/src/validation/visitors/complexity.rs +++ b/src/validation/visitors/complexity.rs @@ -1,4 +1,4 @@ -use crate::parser::query::Field; +use crate::parser::types::Field; use crate::validation::visitor::{Visitor, VisitorContext}; use crate::Positioned; diff --git a/src/validation/visitors/depth.rs b/src/validation/visitors/depth.rs index 341a8642..b75a901d 100644 --- a/src/validation/visitors/depth.rs +++ b/src/validation/visitors/depth.rs @@ -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; diff --git a/tests/input_validators.rs b/tests/input_validators.rs index 1aeec9fc..63ad33bb 100644 --- a/tests/input_validators.rs +++ b/tests/input_validators.rs @@ -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})}";