diff --git a/Cargo.toml b/Cargo.toml index c08531fd..e666a126 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ default = ["bson", "uuid", "url", "chrono-tz"] [dependencies] async-graphql-derive = { path = "async-graphql-derive", version = "1.10.12" } -graphql-parser = "=0.2.3" anyhow = "1.0.26" thiserror = "1.0.11" async-trait = "0.1.24" @@ -42,6 +41,8 @@ http = "0.2.1" fnv = "1.0.6" regex = "1.3.5" tracing = "0.1.13" +pest = "2.1.3" +pest_derive = "2.1.0" bson = { version = "0.14.1", optional = true } uuid = { version = "0.8.1", optional = true } url = { version = "2.1.1", optional = true } diff --git a/async-graphql-derive/src/interface.rs b/async-graphql-derive/src/interface.rs index f78fce0d..d0a3bb21 100644 --- a/async-graphql-derive/src/interface.rs +++ b/async-graphql-derive/src/interface.rs @@ -67,7 +67,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result collect_inline_fields.push(quote! { if let #ident::#enum_name(obj) = self { - return obj.collect_inline_fields(name, pos, ctx, futures); + return obj.collect_inline_fields(name, ctx, futures); } }); @@ -134,7 +134,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result None => quote! { || #crate_name::Value::Null }, }; get_params.push(quote! { - let #ident: #ty = ctx.param_value(#name, ctx.position, #param_default)?; + let #ident: #ty = ctx.param_value(#name, #param_default)?; }); let desc = desc @@ -210,14 +210,14 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result let resolve_obj = quote! { self.#method_name(#(#use_params),*).await. - map_err(|err| err.into_error_with_path(ctx.position, ctx.path_node.as_ref().unwrap().to_json()))? + map_err(|err| err.into_error_with_path(ctx.position(), ctx.path_node.as_ref().unwrap().to_json()))? }; resolvers.push(quote! { if ctx.name.as_str() == #name { #(#get_params)* let ctx_obj = ctx.with_selection_set(&ctx.selection_set); - return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj, ctx.position).await; + return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj, ctx.position()).await; } }); } @@ -280,15 +280,14 @@ 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.clone(), + field_name: ctx.name.clone_inner(), object: #gql_typename.to_string(), - }.into_error(ctx.position)) + }.into_error(ctx.position())) } fn collect_inline_fields<'a>( &'a self, - name: &str, - pos: #crate_name::Pos, + name: &#crate_name::Spanned, ctx: &#crate_name::ContextSelectionSet<'a>, futures: &mut Vec<#crate_name::BoxFieldFuture<'a>>, ) -> #crate_name::Result<()> { diff --git a/async-graphql-derive/src/object.rs b/async-graphql-derive/src/object.rs index cb2f63a3..44e9e2bd 100644 --- a/async-graphql-derive/src/object.rs +++ b/async-graphql-derive/src/object.rs @@ -152,10 +152,10 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< ) .expect("invalid result type"); } - let do_find = quote! { self.#field_ident(ctx, #(#use_keys),*).await.map_err(|err| err.into_error(pos))? }; + let do_find = quote! { self.#field_ident(ctx, #(#use_keys),*).await.map_err(|err| err.into_error(ctx.position()))? }; let guard = entity.guard.map( - |guard| quote! { #guard.check(ctx).await.map_err(|err| err.into_error(pos))?; }, + |guard| quote! { #guard.check(ctx).await.map_err(|err| err.into_error(ctx.position()))?; }, ); find_entities.push(( @@ -165,7 +165,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< if let (#(#key_pat),*) = (#(#key_getter),*) { #guard let ctx_obj = ctx.with_selection_set(&ctx.selection_set); - return #crate_name::OutputValueType::resolve(&#do_find, &ctx_obj, pos).await; + return #crate_name::OutputValueType::resolve(&#do_find, &ctx_obj, ctx.position()).await; } } }, @@ -325,11 +325,13 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< let repr = build_value_repr(&crate_name, &default); quote! {|| #repr } } - None => quote! { || #crate_name::Value::Null }, + None => { + quote! { || #crate_name::Value::Null } + } }; get_params.push(quote! { - let #ident: #ty = ctx.param_value(#name, ctx.position, #default)?; + let #ident: #ty = ctx.param_value(#name, #default)?; }); } @@ -371,7 +373,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< let resolve_obj = quote! { { let res = self.#field_ident(ctx, #(#use_params),*).await; - res.map_err(|err| err.into_error_with_path(ctx.position, ctx.path_node.as_ref().unwrap().to_json()))? + res.map_err(|err| err.into_error_with_path(ctx.position(), ctx.path_node.as_ref().unwrap().to_json()))? } }; @@ -379,7 +381,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< .guard .map(|guard| quote! { #guard.check(ctx).await - .map_err(|err| err.into_error_with_path(ctx.position, ctx.path_node.as_ref().unwrap().to_json()))?; + .map_err(|err| err.into_error_with_path(ctx.position(), ctx.path_node.as_ref().unwrap().to_json()))?; }); resolvers.push(quote! { @@ -388,7 +390,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< #guard #(#get_params)* let ctx_obj = ctx.with_selection_set(&ctx.selection_set); - return OutputValueType::resolve(&#resolve_obj, &ctx_obj, ctx.position).await; + return OutputValueType::resolve(&#resolve_obj, &ctx_obj, ctx.position()).await; } }); @@ -457,23 +459,23 @@ 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.clone(), + field_name: ctx.name.clone_inner(), object: #gql_typename.to_string(), - }.into_error(ctx.position)) + }.into_error(ctx.position())) } - async fn find_entity(&self, ctx: &#crate_name::Context<'_>, pos: #crate_name::Pos, params: &#crate_name::Value) -> #crate_name::Result<#crate_name::serde_json::Value> { + async fn find_entity(&self, ctx: &#crate_name::Context<'_>, params: &#crate_name::Value) -> #crate_name::Result<#crate_name::serde_json::Value> { let params = match params { #crate_name::Value::Object(params) => params, - _ => return Err(#crate_name::QueryError::EntityNotFound.into_error(pos)), + _ => return Err(#crate_name::QueryError::EntityNotFound.into_error(ctx.position())), }; let typename = if let Some(#crate_name::Value::String(typename)) = params.get("__typename") { typename } else { - return Err(#crate_name::QueryError::TypeNameNotExists.into_error(pos)); + return Err(#crate_name::QueryError::TypeNameNotExists.into_error(ctx.position())); }; #(#find_entities_iter)* - Err(#crate_name::QueryError::EntityNotFound.into_error(pos)) + Err(#crate_name::QueryError::EntityNotFound.into_error(ctx.position())) } } diff --git a/async-graphql-derive/src/simple_object.rs b/async-graphql-derive/src/simple_object.rs index 12c49dc9..0369511c 100644 --- a/async-graphql-derive/src/simple_object.rs +++ b/async-graphql-derive/src/simple_object.rs @@ -93,7 +93,7 @@ pub fn generate(object_args: &args::Object, input: &mut DeriveInput) -> Result Result Result) -> #crate_name::Result<#crate_name::serde_json::Value> { #(#resolvers)* Err(#crate_name::QueryError::FieldNotFound { - field_name: ctx.name.clone(), + field_name: ctx.name.clone_inner(), object: #gql_typename.to_string(), - }.into_error(ctx.position)) + }.into_error(ctx.position())) } } diff --git a/async-graphql-derive/src/subscription.rs b/async-graphql-derive/src/subscription.rs index f5bc53c5..d4ab6550 100644 --- a/async-graphql-derive/src/subscription.rs +++ b/async-graphql-derive/src/subscription.rs @@ -181,7 +181,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< }; get_params.push(quote! { - let #ident: #ty = ctx.param_value(#name, ctx.position, #default)?; + let #ident: #ty = ctx.param_value(#name, #default)?; }); } @@ -227,11 +227,11 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< let create_field_stream = quote! { #crate_name::futures::stream::StreamExt::fuse(self.#ident(ctx, #(#use_params),*).await. - map_err(|err| err.into_error_with_path(ctx.position, ctx.path_node.as_ref().unwrap().to_json()))?) + map_err(|err| err.into_error_with_path(ctx.position(), ctx.path_node.as_ref().unwrap().to_json()))?) }; let guard = field.guard.map(|guard| quote! { - #guard.check(ctx).await.map_err(|err| err.into_error_with_path(ctx.position, ctx.path_node.as_ref().unwrap().to_json()))?; + #guard.check(ctx).await.map_err(|err| err.into_error_with_path(ctx.position(), ctx.path_node.as_ref().unwrap().to_json()))?; }); create_stream.push(quote! { @@ -244,7 +244,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< #(#get_params)* let field_selection_set = std::sync::Arc::new(ctx.selection_set.clone()); let schema = schema.clone(); - let pos = ctx.position; + let pos = ctx.position(); let environment = environment.clone(); let stream = #create_field_stream.then({ let field_name = field_name.clone(); @@ -338,9 +338,9 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< #(#create_stream)* Err(#crate_name::QueryError::FieldNotFound { - field_name: ctx.name.clone(), + field_name: ctx.name.clone_inner(), object: #gql_typename.to_string(), - }.into_error(ctx.position)) + }.into_error(ctx.position())) } } }; diff --git a/async-graphql-derive/src/union.rs b/async-graphql-derive/src/union.rs index 8c9a7b53..a015d14d 100644 --- a/async-graphql-derive/src/union.rs +++ b/async-graphql-derive/src/union.rs @@ -57,7 +57,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result }); collect_inline_fields.push(quote! { if let #ident::#enum_name(obj) = self { - return obj.collect_inline_fields(name, pos, ctx, futures); + return obj.collect_inline_fields(name, ctx, futures); } }); get_introspection_typename.push(quote! { @@ -106,15 +106,14 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result impl #generics #crate_name::ObjectType for #ident #generics { async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> { Err(#crate_name::QueryError::FieldNotFound { - field_name: ctx.name.clone(), + field_name: ctx.name.clone_inner(), object: #gql_typename.to_string(), - }.into_error(ctx.position)) + }.into_error(ctx.position())) } fn collect_inline_fields<'a>( &'a self, - name: &str, - pos: #crate_name::Pos, + name: &#crate_name::Spanned, ctx: &#crate_name::ContextSelectionSet<'a>, futures: &mut Vec<#crate_name::BoxFieldFuture<'a>>, ) -> #crate_name::Result<()> { diff --git a/src/base.rs b/src/base.rs index fec8f690..2fe4e7b1 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,7 +1,8 @@ +use crate::parser::Pos; use crate::registry::Registry; -use crate::{registry, Context, ContextSelectionSet, FieldResult, QueryError, Result, ID}; -use graphql_parser::query::Value; -use graphql_parser::Pos; +use crate::{ + registry, Context, ContextSelectionSet, FieldResult, QueryError, Result, Spanned, Value, ID, +}; use std::borrow::Cow; use std::future::Future; use std::pin::Pin; @@ -79,20 +80,19 @@ pub trait ObjectType: OutputValueType { /// Collect the fields with the `name` inline object fn collect_inline_fields<'a>( &'a self, - name: &str, - _pos: Pos, + name: &Spanned, ctx: &ContextSelectionSet<'a>, futures: &mut Vec>, ) -> Result<()> where Self: Send + Sync + Sized, { - if name == Self::type_name().as_ref() + if name.as_str() == Self::type_name().as_ref() || ctx .registry .implements .get(Self::type_name().as_ref()) - .map(|ty| ty.contains(name)) + .map(|ty| ty.contains(name.as_str())) .unwrap_or_default() { crate::collect_fields(ctx, self, futures) @@ -102,13 +102,8 @@ pub trait ObjectType: OutputValueType { } /// Query entities with params - async fn find_entity( - &self, - _ctx: &Context<'_>, - pos: Pos, - _params: &Value, - ) -> Result { - Err(QueryError::EntityNotFound.into_error(pos)) + async fn find_entity(&self, ctx: &Context<'_>, _params: &Value) -> Result { + Err(QueryError::EntityNotFound.into_error(ctx.position())) } } @@ -134,7 +129,7 @@ pub trait InputObjectType: InputValueType {} /// /// fn parse(value: &Value) -> Option { /// if let Value::Int(n) = value { -/// Some(MyInt(n.as_i64().unwrap() as i32)) +/// Some(MyInt(*n as i32)) /// } else { /// None /// } diff --git a/src/context.rs b/src/context.rs index d332fc87..5c2a4c1d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,10 +1,9 @@ use crate::extensions::BoxExtension; +use crate::parser::ast::{Directive, Field, FragmentDefinition, SelectionSet, VariableDefinition}; use crate::registry::Registry; -use crate::{InputValueType, Pos, QueryError, Result, Schema, Type}; +use crate::{InputValueType, QueryError, Result, Schema, Type}; +use crate::{Pos, Spanned, Value}; use fnv::FnvHashMap; -use graphql_parser::query::{ - Directive, Field, FragmentDefinition, SelectionSet, Value, VariableDefinition, -}; use std::any::{Any, TypeId}; use std::collections::{BTreeMap, HashMap}; use std::ops::{Deref, DerefMut}; @@ -47,9 +46,8 @@ impl DerefMut for Variables { impl Variables { /// Parse variables from JSON object. pub fn parse_from_json(value: serde_json::Value) -> Result { - let gql_value = json_value_to_gql_value(value); - if let Value::Object(_) = gql_value { - Ok(Variables(gql_value)) + if let Value::Object(obj) = json_value_to_gql_value(value) { + Ok(Variables(Value::Object(obj))) } else { Ok(Default::default()) } @@ -116,7 +114,7 @@ fn json_value_to_gql_value(value: serde_json::Value) -> Value { serde_json::Value::Null => Value::Null, serde_json::Value::Bool(n) => Value::Boolean(n), serde_json::Value::Number(n) if n.is_f64() => Value::Float(n.as_f64().unwrap()), - serde_json::Value::Number(n) => Value::Int((n.as_i64().unwrap() as i32).into()), + serde_json::Value::Number(n) => Value::Int(n.as_i64().unwrap()), serde_json::Value::String(s) => Value::String(s), serde_json::Value::Array(ls) => { Value::List(ls.into_iter().map(json_value_to_gql_value).collect()) @@ -141,10 +139,10 @@ impl Data { } /// Context for `SelectionSet` -pub type ContextSelectionSet<'a> = ContextBase<'a, &'a SelectionSet>; +pub type ContextSelectionSet<'a> = ContextBase<'a, &'a Spanned>; /// Context object for resolve field -pub type Context<'a> = ContextBase<'a, &'a Field>; +pub type Context<'a> = ContextBase<'a, &'a Spanned>; /// The query path segment #[derive(Clone)] @@ -261,7 +259,7 @@ pub struct ContextBase<'a, T> { pub(crate) extensions: &'a [BoxExtension], pub(crate) item: T, pub(crate) variables: &'a Variables, - pub(crate) variable_definitions: &'a [VariableDefinition], + pub(crate) variable_definitions: &'a [Spanned], pub(crate) registry: &'a Registry, pub(crate) data: &'a Data, pub(crate) ctx_data: Option<&'a Data>, @@ -279,7 +277,7 @@ impl<'a, T> Deref for ContextBase<'a, T> { #[doc(hidden)] pub struct Environment { pub variables: Variables, - pub variable_definitions: Vec, + pub variable_definitions: Vec>, pub fragments: HashMap, pub ctx_data: Arc, } @@ -322,14 +320,15 @@ impl<'a, T> ContextBase<'a, T> { } #[doc(hidden)] - pub fn with_field(&'a self, field: &'a Field) -> ContextBase<'a, &'a Field> { + pub fn with_field(&'a self, field: &'a Spanned) -> ContextBase<'a, &'a Spanned> { ContextBase { path_node: Some(QueryPathNode { parent: self.path_node.as_ref(), segment: QueryPathSegment::Name( field .alias - .as_deref() + .as_ref() + .map(|alias| alias.as_str()) .unwrap_or_else(|| field.name.as_str()), ), }), @@ -349,8 +348,8 @@ impl<'a, T> ContextBase<'a, T> { #[doc(hidden)] pub fn with_selection_set( &self, - selection_set: &'a SelectionSet, - ) -> ContextBase<'a, &'a SelectionSet> { + selection_set: &'a Spanned, + ) -> ContextBase<'a, &'a Spanned> { ContextBase { path_node: self.path_node.clone(), extensions: self.extensions, @@ -384,12 +383,12 @@ impl<'a, T> ContextBase<'a, T> { let def = self .variable_definitions .iter() - .find(|def| def.name == name); + .find(|def| def.name.as_str() == name); if let Some(def) = def { - if let Some(var_value) = self.variables.get(&def.name) { + if let Some(var_value) = self.variables.get(def.name.as_str()) { return Ok(var_value.clone()); } else if let Some(default) = &def.default_value { - return Ok(default.clone()); + return Ok(default.clone_inner()); } } Err(QueryError::VarNotDefined { @@ -422,22 +421,17 @@ impl<'a, T> ContextBase<'a, T> { } #[doc(hidden)] - pub fn is_skip(&self, directives: &[Directive]) -> Result { + pub fn is_skip(&self, directives: &[Spanned]) -> Result { for directive in directives { - if directive.name == "skip" { - if let Some(value) = directive - .arguments - .iter() - .find(|(name, _)| name == "if") - .map(|(_, value)| value) - { - let value = self.resolve_input_value(value.clone(), directive.position)?; + if directive.name.as_str() == "skip" { + if let Some(value) = directive.get_argument("if") { + let value = self.resolve_input_value(value.clone_inner(), value.position())?; let res: bool = InputValueType::parse(&value).ok_or_else(|| { QueryError::ExpectedType { expect: bool::qualified_type_name(), actual: value, } - .into_error(directive.position) + .into_error(directive.position()) })?; if res { return Ok(true); @@ -448,22 +442,17 @@ impl<'a, T> ContextBase<'a, T> { arg_name: "if", arg_type: "Boolean!", } - .into_error(directive.position)); + .into_error(directive.position())); } - } else if directive.name == "include" { - if let Some(value) = directive - .arguments - .iter() - .find(|(name, _)| name == "if") - .map(|(_, value)| value) - { - let value = self.resolve_input_value(value.clone(), directive.position)?; + } else if directive.name.as_str() == "include" { + if let Some(value) = directive.get_argument("if") { + let value = self.resolve_input_value(value.clone_inner(), value.position())?; let res: bool = InputValueType::parse(&value).ok_or_else(|| { QueryError::ExpectedType { expect: bool::qualified_type_name(), actual: value, } - .into_error(directive.position) + .into_error(directive.position()) })?; if !res { return Ok(true); @@ -474,13 +463,13 @@ impl<'a, T> ContextBase<'a, T> { arg_name: "if", arg_type: "Boolean!", } - .into_error(directive.position)); + .into_error(directive.position())); } } else { return Err(QueryError::UnknownDirective { - name: directive.name.clone(), + name: directive.name.clone_inner(), } - .into_error(directive.position)); + .into_error(directive.position())); } } @@ -488,9 +477,9 @@ impl<'a, T> ContextBase<'a, T> { } } -impl<'a> ContextBase<'a, &'a SelectionSet> { +impl<'a> ContextBase<'a, &'a Spanned> { #[doc(hidden)] - pub fn with_index(&'a self, idx: usize) -> ContextBase<'a, &'a SelectionSet> { + pub fn with_index(&'a self, idx: usize) -> ContextBase<'a, &'a Spanned> { ContextBase { path_node: Some(QueryPathNode { parent: self.path_node.as_ref(), @@ -510,23 +499,17 @@ impl<'a> ContextBase<'a, &'a SelectionSet> { } } -impl<'a> ContextBase<'a, &'a Field> { +impl<'a> ContextBase<'a, &'a Spanned> { #[doc(hidden)] pub fn param_value Value>( &self, name: &str, - pos: Pos, default: F, ) -> Result { - match self - .arguments - .iter() - .find(|(n, _)| n == name) - .map(|(_, v)| v) - .cloned() - { + match self.get_argument(name).cloned() { Some(value) => { - let value = self.resolve_input_value(value, pos)?; + let pos = value.position(); + let value = self.resolve_input_value(value.into_inner(), pos)?; let res = InputValueType::parse(&value).ok_or_else(|| { QueryError::ExpectedType { expect: T::qualified_type_name(), @@ -543,7 +526,7 @@ impl<'a> ContextBase<'a, &'a Field> { expect: T::qualified_type_name(), actual: value.clone(), } - .into_error(pos) + .into_error(self.position()) })?; Ok(res) } @@ -554,7 +537,8 @@ impl<'a> ContextBase<'a, &'a Field> { pub fn result_name(&self) -> &str { self.item .alias - .as_deref() + .as_ref() + .map(|alias| alias.as_str()) .unwrap_or_else(|| self.item.name.as_str()) } } diff --git a/src/error.rs b/src/error.rs index e75c73aa..4b715f96 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,6 @@ -use graphql_parser::query::{ParseError, Value}; -use graphql_parser::Pos; +use crate::parser::ParseError; +use crate::{Pos, Value}; +use pest::error::LineColLocation; use std::fmt::Debug; /// FieldError type @@ -256,22 +257,14 @@ pub struct RuleError { impl From for Error { fn from(err: ParseError) -> Self { - let msg = err.to_string(); - let mut s = msg.splitn(2, '\n'); - let first = s.next().unwrap(); - let ln = &first[first.rfind(' ').unwrap() + 1..]; - let (line, column) = { - let mut s = ln.splitn(2, ':'); - ( - s.next().unwrap().parse().unwrap(), - s.next().unwrap().parse().unwrap(), - ) + let (line, column) = match err.line_col { + LineColLocation::Pos((line, column)) => (line, column), + LineColLocation::Span((line, column), _) => (line, column), }; - let tail = s.next().unwrap(); Error::Parse { line, column, - message: tail.to_string(), + message: err.to_string(), } } } diff --git a/src/http/mod.rs b/src/http/mod.rs index 91cd6c5f..759b4f2a 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -14,8 +14,9 @@ pub use playground_source::playground_source; pub use stream_body::StreamBody; use crate::query::{IntoQueryBuilder, IntoQueryBuilderOpts}; -use crate::{Error, ParseRequestError, QueryBuilder, QueryError, QueryResponse, Result, Variables}; -use graphql_parser::Pos; +use crate::{ + Error, ParseRequestError, Pos, QueryBuilder, QueryError, QueryResponse, Result, Variables, +}; use serde::ser::{SerializeMap, SerializeSeq}; use serde::{Serialize, Serializer}; @@ -162,7 +163,7 @@ impl<'a> Serialize for GQLErrorPos<'a> { #[cfg(test)] mod tests { use super::*; - use graphql_parser::Pos; + use crate::Pos; use serde_json::json; #[test] diff --git a/src/lib.rs b/src/lib.rs index 95d01086..ca67f953 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,12 +76,15 @@ extern crate thiserror; #[macro_use] extern crate serde_derive; +#[macro_use] +extern crate pest_derive; mod base; mod context; mod error; mod model; mod mutation_resolver; +mod parser; mod query; mod resolver; mod scalars; @@ -101,8 +104,6 @@ pub use async_trait; #[doc(hidden)] pub use futures; #[doc(hidden)] -pub use graphql_parser; -#[doc(hidden)] pub use serde_json; pub mod http; @@ -114,8 +115,7 @@ pub use context::{ pub use error::{ Error, ErrorExtensions, FieldError, FieldResult, ParseRequestError, QueryError, ResultExt, }; -pub use graphql_parser::query::Value; -pub use graphql_parser::Pos; +pub use parser::{Pos, Spanned, Value}; pub use query::{IntoQueryBuilder, IntoQueryBuilderOpts, QueryBuilder, QueryResponse}; pub use registry::CacheControl; pub use scalars::{Any, Json, ID}; @@ -130,7 +130,7 @@ pub use types::{ }; pub use validation::ValidationMode; -/// Result type, are actually `anyhow::Result` +/// Result type pub type Result = std::result::Result; // internal types diff --git a/src/mutation_resolver.rs b/src/mutation_resolver.rs index ebade649..314e7ebc 100644 --- a/src/mutation_resolver.rs +++ b/src/mutation_resolver.rs @@ -1,6 +1,6 @@ use crate::extensions::ResolveInfo; +use crate::parser::ast::{Selection, TypeCondition}; use crate::{ContextSelectionSet, Error, ObjectType, QueryError, Result}; -use graphql_parser::query::{Selection, TypeCondition}; use std::future::Future; use std::pin::Pin; @@ -24,7 +24,7 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>( Box::pin(async move { if ctx.items.is_empty() { return Err(Error::Query { - pos: ctx.span.0, + pos: ctx.position(), path: None, err: QueryError::MustHaveSubFields { object: T::type_name().to_string(), @@ -33,7 +33,7 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>( } for selection in &ctx.item.items { - match selection { + match &selection.node { Selection::Field(field) => { if ctx.is_skip(&field.directives)? { continue; @@ -65,10 +65,10 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>( Some(ty) => &ty, None => { return Err(Error::Query { - pos: field.position, + pos: field.position(), path: None, err: QueryError::FieldNotFound { - field_name: field.name.clone(), + field_name: field.name.clone_inner(), object: T::type_name().to_string(), }, }); @@ -108,10 +108,10 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>( .await?; } else { return Err(Error::Query { - pos: fragment_spread.position, + pos: fragment_spread.position(), path: None, err: QueryError::UnknownFragment { - name: fragment_spread.fragment_name.clone(), + name: fragment_spread.fragment_name.clone_inner(), }, }); } @@ -121,11 +121,12 @@ fn do_resolve<'a, T: ObjectType + Send + Sync>( continue; } - if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition { + if let Some(TypeCondition::On(name)) = + inline_fragment.type_condition.as_ref().map(|v| &v.node) + { let mut futures = Vec::new(); root.collect_inline_fields( name, - inline_fragment.position, &ctx.with_selection_set(&inline_fragment.selection_set), &mut futures, )?; diff --git a/src/parser/ast.rs b/src/parser/ast.rs new file mode 100644 index 00000000..8a76d2c2 --- /dev/null +++ b/src/parser/ast.rs @@ -0,0 +1,141 @@ +use crate::parser::span::Spanned; +use crate::parser::value::Value; +use std::fmt; + +#[derive(Clone, Debug, PartialEq)] +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(Clone, Debug)] +pub struct Directive { + pub name: Spanned, + pub arguments: Vec<(Spanned, Spanned)>, +} + +impl Directive { + pub fn get_argument(&self, name: &str) -> Option<&Spanned> { + self.arguments + .iter() + .find(|item| item.0.as_str() == name) + .map(|item| &item.1) + } +} + +#[derive(Clone, Debug)] +pub struct Document { + pub definitions: Vec>, +} + +#[derive(Clone, Debug)] +pub enum Definition { + Operation(Spanned), + Fragment(Spanned), +} + +#[derive(Clone, Debug)] +pub enum TypeCondition { + On(Spanned), +} + +#[derive(Clone, Debug)] +pub struct FragmentDefinition { + pub name: Spanned, + pub type_condition: Spanned, + pub directives: Vec>, + pub selection_set: Spanned, +} + +#[derive(Clone, Debug)] +pub enum OperationDefinition { + SelectionSet(Spanned), + Query(Spanned), + Mutation(Spanned), + Subscription(Spanned), +} + +#[derive(Clone, Debug)] +pub struct Query { + pub name: Option>, + pub variable_definitions: Vec>, + pub directives: Vec>, + pub selection_set: Spanned, +} + +#[derive(Clone, Debug)] +pub struct Mutation { + pub name: Option>, + pub variable_definitions: Vec>, + pub directives: Vec>, + pub selection_set: Spanned, +} + +#[derive(Clone, Debug)] +pub struct Subscription { + pub name: Option>, + pub variable_definitions: Vec>, + pub directives: Vec>, + pub selection_set: Spanned, +} + +#[derive(Clone, Debug, Default)] +pub struct SelectionSet { + pub items: Vec>, +} + +#[derive(Clone, Debug)] +pub struct VariableDefinition { + pub name: Spanned, + pub var_type: Spanned, + pub default_value: Option>, +} + +#[derive(Clone, Debug)] +pub enum Selection { + Field(Spanned), + FragmentSpread(Spanned), + InlineFragment(Spanned), +} + +#[derive(Clone, Debug)] +pub struct Field { + pub alias: Option>, + pub name: Spanned, + pub arguments: Vec<(Spanned, Spanned)>, + pub directives: Vec>, + pub selection_set: Spanned, +} + +impl Field { + pub fn get_argument(&self, name: &str) -> Option<&Spanned> { + self.arguments + .iter() + .find(|item| item.0.as_str() == name) + .map(|item| &item.1) + } +} + +#[derive(Clone, Debug)] +pub struct FragmentSpread { + pub fragment_name: Spanned, + pub directives: Vec>, +} + +#[derive(Clone, Debug)] +pub struct InlineFragment { + pub type_condition: Option>, + pub directives: Vec>, + pub selection_set: Spanned, +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 00000000..973a1da7 --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,8 @@ +pub mod ast; +mod query_parser; +mod span; +mod value; + +pub use query_parser::{parse_query, ParseError}; +pub use span::{Pos, Span, Spanned}; +pub use value::Value; diff --git a/src/parser/query.pest b/src/parser/query.pest new file mode 100644 index 00000000..468d6bb9 --- /dev/null +++ b/src/parser/query.pest @@ -0,0 +1,49 @@ +WHITESPACE = _{ " " | "," | "\t" | "\u{feff}" | NEWLINE } +COMMENT = _{ "#" ~ (!"\n" ~ ANY)* } + +// value +int = @{ "-"? ~ "0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* } +float = @{ "-"? ~ int ~ "." ~ ASCII_DIGIT+ ~ exp? } +exp = @{ ("E" | "e") ~ ("+" | "-")? ~ ASCII_DIGIT+ } + +string = @{ "\"" ~ string_inner ~ "\"" } +string_inner = @{ (!("\"" | "\\") ~ ANY)* ~ (escape ~ string_inner)? } +escape = @{ "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t" | unicode) } +unicode = @{ "u" ~ ASCII_HEX_DIGIT{4} } + +boolean = { "true" | "false" } +null = { "null" } +name = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHA | ASCII_DIGIT | "_")* } +variable = { "$" ~ name } + +array = { "[" ~ value* ~ "]" } + +pair = { name ~ ":" ~ value } +object = { "{" ~ pair* ~ "}" } + +value = { object | array | variable | float | int | string | null | boolean | name } + +// type +list_type = { "[" ~ type_ ~ "]" } +nonnull_type = { (list_type | name) ~ "!" } +type_ = { nonnull_type | list_type | name } + +// 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/src/parser/query_parser.rs b/src/parser/query_parser.rs new file mode 100644 index 00000000..21d4a1c1 --- /dev/null +++ b/src/parser/query_parser.rs @@ -0,0 +1,536 @@ +use crate::parser::ast::*; +use crate::parser::span::Spanned; +use crate::parser::value::Value; +use crate::{Pos, Result}; +use pest::iterators::Pair; +use pest::{error::Error, Parser}; +use std::collections::BTreeMap; + +#[derive(Parser)] +#[grammar = "parser/query.pest"] +struct QueryParser; + +pub type ParseError = Error; + +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(); + + for pair in document_pair.into_inner() { + match pair.as_rule() { + Rule::named_operation_definition => definitions + .push(parse_named_operation_definition(pair)?.pack(Definition::Operation)), + Rule::selection_set => definitions.push( + parse_selection_set(pair)? + .pack(OperationDefinition::SelectionSet) + .pack(Definition::Operation), + ), + Rule::fragment_definition => { + definitions.push(parse_fragment_definition(pair)?.pack(Definition::Fragment)) + } + Rule::EOI => {} + _ => unreachable!(), + } + } + Ok(Document { definitions }) +} + +fn parse_named_operation_definition(pair: Pair) -> Result> { + enum OperationType { + Query, + Mutation, + Subscription, + } + + let span = pair.as_span(); + 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(Spanned::new(pair.as_str().to_string(), pair.as_span())); + } + Rule::variable_definitions => { + variable_definitions = Some(parse_variable_definitions(pair)?); + } + Rule::directives => { + directives = Some(parse_directives(pair)?); + } + Rule::selection_set => { + selection_set = Some(parse_selection_set(pair)?); + } + _ => unreachable!(), + } + } + + Ok(match operation_type { + OperationType::Query => Spanned::new( + Query { + name, + variable_definitions: variable_definitions.unwrap_or_default(), + directives: directives.unwrap_or_default(), + selection_set: selection_set.unwrap(), + }, + span, + ) + .pack(OperationDefinition::Query), + OperationType::Mutation => Spanned::new( + Mutation { + name, + variable_definitions: variable_definitions.unwrap_or_default(), + directives: directives.unwrap_or_default(), + selection_set: selection_set.unwrap(), + }, + span, + ) + .pack(OperationDefinition::Mutation), + OperationType::Subscription => Spanned::new( + Subscription { + name, + variable_definitions: variable_definitions.unwrap_or_default(), + directives: directives.unwrap_or_default(), + selection_set: selection_set.unwrap(), + }, + span, + ) + .pack(OperationDefinition::Subscription), + }) +} + +fn parse_default_value(pair: Pair) -> Result { + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::value => return Ok(parse_value(pair)?), + _ => unreachable!(), + } + } + unreachable!() +} + +fn parse_type(pair: Pair) -> Result { + let pair = pair.into_inner().next().unwrap(); + match pair.as_rule() { + Rule::nonnull_type => Ok(Type::NonNull(Box::new(parse_type(pair)?))), + Rule::list_type => Ok(Type::List(Box::new(parse_type(pair)?))), + Rule::name => Ok(Type::Named(pair.as_str().to_string())), + Rule::type_ => parse_type(pair), + _ => unreachable!(), + } +} + +fn parse_variable_definition(pair: Pair) -> Result> { + let span = pair.as_span(); + let mut variable = None; + let mut ty = None; + let mut default_value = None; + + for pair in pair.into_inner() { + let span = pair.as_span(); + match pair.as_rule() { + Rule::variable => variable = Some(parse_variable(pair)?), + Rule::type_ => ty = Some(Spanned::new(parse_type(pair)?, span)), + Rule::default_value => { + default_value = Some(Spanned::new(parse_default_value(pair)?, span)) + } + _ => unreachable!(), + } + } + Ok(Spanned::new( + VariableDefinition { + name: variable.unwrap(), + var_type: ty.unwrap(), + default_value, + }, + span, + )) +} + +fn parse_variable_definitions(pair: Pair) -> 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)?), + _ => unreachable!(), + } + } + Ok(vars) +} + +fn parse_directive(pair: Pair) -> Result> { + let span = pair.as_span(); + let mut name = None; + let mut arguments = None; + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::name => name = Some(Spanned::new(pair.as_str().to_string(), pair.as_span())), + Rule::arguments => arguments = Some(parse_arguments(pair)?), + _ => unreachable!(), + } + } + Ok(Spanned::new( + Directive { + name: name.unwrap(), + arguments: arguments.unwrap_or_default(), + }, + span, + )) +} + +fn parse_directives(pair: Pair) -> Result>> { + let mut directives = Vec::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::directive => directives.push(parse_directive(pair)?), + _ => unreachable!(), + } + } + Ok(directives) +} + +fn parse_variable(pair: Pair) -> Result> { + for pair in pair.into_inner() { + if let Rule::name = pair.as_rule() { + return Ok(Spanned::new(pair.as_str().to_string(), pair.as_span())); + } + } + unreachable!() +} + +fn parse_value(pair: Pair) -> Result { + let pair = pair.into_inner().next().unwrap(); + Ok(match pair.as_rule() { + Rule::object => parse_object_value(pair)?, + Rule::array => parse_array_value(pair)?, + Rule::variable => Value::Variable(parse_variable(pair)?.into_inner()), + Rule::float => Value::Float(pair.as_str().parse().unwrap()), + Rule::int => Value::Int(pair.as_str().parse().unwrap()), + Rule::string => Value::String({ + let start_pos = pair.as_span().start_pos().line_col(); + unquote_string( + pair.as_str(), + Pos { + line: start_pos.0, + column: start_pos.1, + }, + )? + }), + 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) -> 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)?), + _ => unreachable!(), + } + } + Ok((name.unwrap(), value.unwrap())) +} + +fn parse_object_value(pair: Pair) -> 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)?)); + } + _ => unreachable!(), + } + } + Ok(Value::Object(map)) +} + +fn parse_array_value(pair: Pair) -> Result { + let mut array = Vec::new(); + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::value => { + array.push(parse_value(pair)?); + } + _ => unreachable!(), + } + } + Ok(Value::List(array)) +} + +fn parse_pair(pair: Pair) -> Result<(Spanned, Spanned)> { + let mut name = None; + let mut value = None; + for pair in pair.into_inner() { + let span = pair.as_span(); + match pair.as_rule() { + Rule::name => name = Some(Spanned::new(pair.as_str().to_string(), span)), + Rule::value => value = Some(Spanned::new(parse_value(pair)?, span)), + _ => unreachable!(), + } + } + Ok((name.unwrap(), value.unwrap())) +} + +fn parse_arguments(pair: Pair) -> Result, Spanned)>> { + 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)?)), + _ => unreachable!(), + } + } + Ok(arguments) +} + +fn parse_alias(pair: Pair) -> Result> { + for pair in pair.into_inner() { + if let Rule::name = pair.as_rule() { + return Ok(Spanned::new(pair.as_str().to_string(), pair.as_span())); + } + } + unreachable!() +} + +fn parse_field(pair: Pair) -> Result> { + let span = pair.as_span(); + 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)?), + Rule::name => name = Some(Spanned::new(pair.as_str().to_string(), pair.as_span())), + Rule::arguments => arguments = Some(parse_arguments(pair)?), + Rule::directives => directives = Some(parse_directives(pair)?), + Rule::selection_set => selection_set = Some(parse_selection_set(pair)?), + _ => unreachable!(), + } + } + + Ok(Spanned::new( + Field { + alias, + name: name.unwrap(), + arguments: arguments.unwrap_or_default(), + directives: directives.unwrap_or_default(), + selection_set: selection_set.unwrap_or_default(), + }, + span, + )) +} + +fn parse_fragment_spread(pair: Pair) -> Result> { + let span = pair.as_span(); + let mut name = None; + let mut directives = None; + for pair in pair.into_inner() { + match pair.as_rule() { + Rule::name => name = Some(Spanned::new(pair.as_str().to_string(), pair.as_span())), + Rule::directives => directives = Some(parse_directives(pair)?), + _ => unreachable!(), + } + } + Ok(Spanned::new( + FragmentSpread { + fragment_name: name.unwrap(), + directives: directives.unwrap_or_default(), + }, + span, + )) +} + +fn parse_type_condition(pair: Pair) -> Result> { + for pair in pair.into_inner() { + if let Rule::name = pair.as_rule() { + return Ok(Spanned::new( + TypeCondition::On(Spanned::new(pair.as_str().to_string(), pair.as_span())), + pair.as_span(), + )); + } + } + unreachable!() +} + +fn parse_inline_fragment(pair: Pair) -> Result> { + let span = pair.as_span(); + 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)?), + Rule::directives => directives = Some(parse_directives(pair)?), + Rule::selection_set => selection_set = Some(parse_selection_set(pair)?), + _ => unreachable!(), + } + } + + Ok(Spanned::new( + InlineFragment { + type_condition, + directives: directives.unwrap_or_default(), + selection_set: selection_set.unwrap(), + }, + span, + )) +} + +fn parse_selection_set(pair: Pair) -> Result> { + let span = pair.as_span(); + 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)?.pack(Selection::Field)), + Rule::fragment_spread => { + items.push(parse_fragment_spread(pair)?.pack(Selection::FragmentSpread)) + } + Rule::inline_fragment => { + items.push(parse_inline_fragment(pair)?.pack(Selection::InlineFragment)) + } + _ => unreachable!(), + } + } + Ok(Spanned::new(SelectionSet { items }, span)) +} + +fn parse_fragment_definition(pair: Pair) -> Result> { + let span = pair.as_span(); + 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(Spanned::new(pair.as_str().to_string(), pair.as_span())), + Rule::type_condition => type_condition = Some(parse_type_condition(pair)?), + Rule::directives => directives = Some(parse_directives(pair)?), + Rule::selection_set => selection_set = Some(parse_selection_set(pair)?), + _ => unreachable!(), + } + } + + Ok(Spanned::new( + FragmentDefinition { + name: name.unwrap(), + type_condition: type_condition.unwrap(), + directives: directives.unwrap_or_default(), + selection_set: selection_set.unwrap(), + }, + span, + )) +} + +fn unquote_string(s: &str, pos: Pos) -> Result { + let mut res = String::with_capacity(s.len()); + debug_assert!(s.starts_with('"') && s.ends_with('"')); + let mut chars = s[1..s.len() - 1].chars(); + let mut temp_code_point = String::with_capacity(4); + 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) => temp_code_point.push(inner_c), + None => { + return Err(crate::Error::Parse { + line: pos.line, + column: pos.column, + message: format!( + "\\u must have 4 characters after it, only found '{}'", + temp_code_point + ), + }); + } + } + } + + // convert our hex string into a u32, then convert that into a char + match u32::from_str_radix(&temp_code_point, 16).map(std::char::from_u32) { + Ok(Some(unicode_char)) => res.push(unicode_char), + _ => { + return Err(crate::Error::Parse { + line: pos.line, + column: pos.column, + message: format!( + "{} is not a valid unicode code point", + temp_code_point + ), + }); + } + } + } + c => { + return Err(crate::Error::Parse { + line: pos.line, + column: pos.column, + message: format!("bad escaped char {:?}", c), + }); + } + } + } + c => res.push(c), + } + } + + Ok(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 { + 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(); + } + } + } +} diff --git a/src/parser/span.rs b/src/parser/span.rs new file mode 100644 index 00000000..2f042837 --- /dev/null +++ b/src/parser/span.rs @@ -0,0 +1,148 @@ +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 +#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Default, Hash)] +pub struct Pos { + /// One-based line number + pub line: usize, + + /// One-based column number + pub column: usize, +} + +impl fmt::Debug for Pos { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Pos({}:{})", self.line, self.column) + } +} + +impl fmt::Display for Pos { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}:{}", self.line, self.column) + } +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct Span { + pub start: Pos, + pub end: Pos, +} + +/// Represents the location of a AST node +#[derive(Clone, Debug, Copy, Default)] +#[allow(missing_docs)] +pub struct Spanned { + pub span: Span, + pub node: T, +} + +impl fmt::Display for Spanned { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.node.fmt(f) + } +} + +impl Spanned { + #[inline] + #[allow(missing_docs)] + pub fn clone_inner(&self) -> T { + self.node.clone() + } +} + +impl PartialEq for Spanned { + fn eq(&self, other: &Self) -> bool { + self.node.eq(&other.node) + } +} + +impl PartialOrd for Spanned { + fn partial_cmp(&self, other: &Self) -> Option { + self.node.partial_cmp(&other.node) + } +} + +impl Ord for Spanned { + fn cmp(&self, other: &Self) -> Ordering { + self.node.cmp(&other.node) + } +} + +impl Eq for Spanned {} + +impl Deref for Spanned { + type Target = T; + + fn deref(&self) -> &T { + &self.node + } +} + +impl DerefMut for Spanned { + fn deref_mut(&mut self) -> &mut T { + &mut self.node + } +} + +impl Hash for Spanned { + fn hash(&self, state: &mut H) { + self.node.hash(state) + } +} + +impl Borrow for Spanned { + fn borrow(&self) -> &str { + self.node.as_str() + } +} + +impl BorrowMut for Spanned { + fn borrow_mut(&mut self) -> &mut str { + self.node.as_mut_str() + } +} + +impl Spanned { + pub(crate) fn new(node: T, pair_span: pest::Span<'_>) -> Spanned { + let ((start_line, start_column), (end_line, end_column)) = ( + pair_span.start_pos().line_col(), + pair_span.end_pos().line_col(), + ); + Spanned { + node, + span: Span { + start: Pos { + line: start_line, + column: start_column, + }, + end: Pos { + line: end_line, + column: end_column, + }, + }, + } + } + + #[inline] + pub(crate) fn into_inner(self) -> T { + self.node + } + + /// Get start position + #[inline] + pub fn position(&self) -> Pos { + self.span.start + } + + #[inline] + pub(crate) fn pack R, R>(self, f: F) -> Spanned { + Spanned { + span: self.span, + node: f(self), + } + } +} diff --git a/src/parser/value.rs b/src/parser/value.rs new file mode 100644 index 00000000..0c7c41ab --- /dev/null +++ b/src/parser/value.rs @@ -0,0 +1,117 @@ +use std::collections::BTreeMap; +use std::fmt; + +/// Represents a GraphQL value +#[derive(Clone, Debug)] +#[allow(missing_docs)] +pub enum Value { + Null, + Variable(String), + Int(i64), + Float(f64), + String(String), + Boolean(bool), + Enum(String), + List(Vec), + Object(BTreeMap), +} + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + use Value::*; + + match (self, other) { + (Variable(a), Variable(b)) => a.eq(b), + (Int(a), Int(b)) => a.eq(b), + (Float(a), Float(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 + } + _ => 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::Int(num) => write!(f, "{}", *num), + Value::Float(val) => write!(f, "{}", *val), + 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, "}}") + } + } + } +} diff --git a/src/query.rs b/src/query.rs index b19929bd..b580a1db 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,14 +1,15 @@ use crate::context::{Data, ResolveId}; use crate::error::ParseRequestError; use crate::mutation_resolver::do_mutation_resolve; -use crate::registry::CacheControl; -use crate::validation::{check_rules, CheckResult}; -use crate::{do_resolve, ContextBase, Error, Result, Schema}; -use crate::{ObjectType, QueryError, Variables}; -use graphql_parser::query::{ +use crate::parser::ast::{ Definition, Document, OperationDefinition, SelectionSet, VariableDefinition, }; -use graphql_parser::{parse_query, Pos}; +use crate::parser::parse_query; +use crate::registry::CacheControl; +use crate::validation::{check_rules, CheckResult}; +use crate::{ + do_resolve, ContextBase, Error, ObjectType, Pos, QueryError, Result, Schema, Spanned, Variables, +}; use itertools::Itertools; use std::any::Any; use std::collections::HashMap; @@ -181,8 +182,8 @@ impl QueryBuilder { })?; for definition in &document.definitions { - if let Definition::Fragment(fragment) = &definition { - fragments.insert(fragment.name.clone(), fragment.clone()); + if let Definition::Fragment(fragment) = &definition.node { + fragments.insert(fragment.name.clone_inner(), fragment.clone_inner()); } } @@ -232,27 +233,34 @@ impl QueryBuilder { } } +#[allow(clippy::type_complexity)] fn current_operation<'a>( document: &'a Document, operation_name: Option<&str>, -) -> Option<(&'a SelectionSet, &'a [VariableDefinition], bool)> { +) -> Option<( + &'a Spanned, + &'a [Spanned], + bool, +)> { for definition in &document.definitions { - match definition { - Definition::Operation(operation_definition) => match operation_definition { + match &definition.node { + Definition::Operation(operation_definition) => match &operation_definition.node { OperationDefinition::SelectionSet(s) => { return Some((s, &[], true)); } OperationDefinition::Query(query) if query.name.is_none() || operation_name.is_none() - || query.name.as_deref() == operation_name.as_deref() => + || query.name.as_ref().map(|name| name.as_str()) + == operation_name.as_deref() => { return Some((&query.selection_set, &query.variable_definitions, true)); } OperationDefinition::Mutation(mutation) if mutation.name.is_none() || operation_name.is_none() - || mutation.name.as_deref() == operation_name.as_deref() => + || mutation.name.as_ref().map(|name| name.as_str()) + == operation_name.as_deref() => { return Some(( &mutation.selection_set, @@ -263,7 +271,8 @@ fn current_operation<'a>( OperationDefinition::Subscription(subscription) if subscription.name.is_none() || operation_name.is_none() - || subscription.name.as_deref() == operation_name.as_deref() => + || subscription.name.as_ref().map(|name| name.as_str()) + == operation_name.as_deref() => { return None; } diff --git a/src/registry.rs b/src/registry.rs index 3d5eec8d..d3dd75ab 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -1,6 +1,6 @@ +use crate::parser::ast::Type as ParsedType; use crate::validators::InputValueValidator; use crate::{model, Any, Type as _, Value}; -use graphql_parser::query::Type as ParsedType; use std::collections::{HashMap, HashSet}; use std::fmt::Write; use std::sync::Arc; @@ -402,9 +402,9 @@ impl Registry { pub fn concrete_type_by_parsed_type(&self, query_type: &ParsedType) -> Option<&Type> { match query_type { - ParsedType::NonNullType(ty) => self.concrete_type_by_parsed_type(ty), - ParsedType::ListType(ty) => self.concrete_type_by_parsed_type(ty), - ParsedType::NamedType(name) => self.types.get(name.as_str()), + 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()), } } diff --git a/src/resolver.rs b/src/resolver.rs index 133f849e..9fb46800 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -1,8 +1,8 @@ use crate::base::BoxFieldFuture; use crate::extensions::ResolveInfo; +use crate::parser::ast::{Selection, TypeCondition}; use crate::{ContextSelectionSet, Error, ObjectType, QueryError, Result}; use futures::{future, TryFutureExt}; -use graphql_parser::query::{Selection, TypeCondition}; use std::iter::FromIterator; #[allow(missing_docs)] @@ -25,7 +25,7 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>( ) -> Result<()> { if ctx.items.is_empty() { return Err(Error::Query { - pos: ctx.span.0, + pos: ctx.position(), path: None, err: QueryError::MustHaveSubFields { object: T::type_name().to_string(), @@ -34,7 +34,7 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>( } for selection in &ctx.item.items { - match selection { + match &selection.node { Selection::Field(field) => { if ctx.is_skip(&field.directives)? { continue; @@ -74,10 +74,10 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>( Some(ty) => &ty, None => { return Err(Error::Query { - pos: field.position, + pos: field.position(), path: None, err: QueryError::FieldNotFound { - field_name: field.name.clone(), + field_name: field.name.clone_inner(), object: T::type_name().to_string(), }, }); @@ -120,10 +120,10 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>( )?; } else { return Err(Error::Query { - pos: fragment_spread.position, + pos: fragment_spread.position(), path: None, err: QueryError::UnknownFragment { - name: fragment_spread.fragment_name.clone(), + name: fragment_spread.fragment_name.clone_inner(), }, }); } @@ -133,10 +133,9 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>( continue; } - if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition { + if let Some(TypeCondition::On(name)) = inline_fragment.type_condition.as_deref() { root.collect_inline_fields( name, - inline_fragment.position, &ctx.with_selection_set(&inline_fragment.selection_set), futures, )?; diff --git a/src/scalars/any.rs b/src/scalars/any.rs index 64c361d7..36526561 100644 --- a/src/scalars/any.rs +++ b/src/scalars/any.rs @@ -43,7 +43,7 @@ pub(crate) fn gql_value_to_json_value(value: Value) -> serde_json::Value { match value { Value::Null => serde_json::Value::Null, Value::Variable(name) => name.into(), - Value::Int(n) => n.as_i64().unwrap().into(), + Value::Int(n) => n.into(), Value::Float(n) => n.into(), Value::String(s) => s.into(), Value::Boolean(v) => v.into(), diff --git a/src/scalars/floats.rs b/src/scalars/floats.rs index d243e758..002e68e3 100644 --- a/src/scalars/floats.rs +++ b/src/scalars/floats.rs @@ -16,7 +16,7 @@ macro_rules! impl_float_scalars { fn parse(value: &Value) -> Option { match value { - Value::Int(n) => Some(n.as_i64().unwrap() as Self), + Value::Int(n) => Some(*n as Self), Value::Float(n) => Some(*n as Self), _ => None } diff --git a/src/scalars/id.rs b/src/scalars/id.rs index 02802645..d7ac58b4 100644 --- a/src/scalars/id.rs +++ b/src/scalars/id.rs @@ -106,7 +106,7 @@ impl ScalarType for ID { fn parse(value: &Value) -> Option { match value { - Value::Int(n) => Some(ID(n.as_i64().unwrap().to_string())), + Value::Int(n) => Some(ID(n.to_string())), Value::String(s) => Some(ID(s.clone())), _ => None, } diff --git a/src/scalars/integers.rs b/src/scalars/integers.rs index 3c81c9ca..83959abc 100644 --- a/src/scalars/integers.rs +++ b/src/scalars/integers.rs @@ -16,7 +16,7 @@ macro_rules! impl_integer_scalars { fn parse(value: &Value) -> Option { match value { - Value::Int(n) => Some(n.as_i64().unwrap() as Self), + Value::Int(n) => Some(*n as Self), _ => None } } @@ -46,7 +46,7 @@ macro_rules! impl_int64_scalars { fn parse(value: &Value) -> Option { match value { - Value::Int(n) => Some(n.as_i64().unwrap() as Self), + Value::Int(n) => Some(*n as Self), Value::String(s) => s.parse().ok(), _ => None } diff --git a/src/scalars/string.rs b/src/scalars/string.rs index 5a523bd8..e19758a7 100644 --- a/src/scalars/string.rs +++ b/src/scalars/string.rs @@ -1,4 +1,5 @@ -use crate::{registry, ContextSelectionSet, OutputValueType, Pos, Result, ScalarType, Type, Value}; +use crate::parser::Pos; +use crate::{registry, ContextSelectionSet, OutputValueType, Result, ScalarType, Type, Value}; use async_graphql_derive::Scalar; use std::borrow::Cow; diff --git a/src/schema.rs b/src/schema.rs index ed0754b0..016e1b78 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,6 +1,8 @@ use crate::context::Data; use crate::extensions::{BoxExtension, Extension}; use crate::model::__DirectiveLocation; +use crate::parser::ast::{Definition, OperationDefinition}; +use crate::parser::parse_query; use crate::query::QueryBuilder; use crate::registry::{Directive, InputValue, Registry}; use crate::subscription::{create_connection, create_subscription_stream, SubscriptionTransport}; @@ -13,8 +15,6 @@ use crate::{ use bytes::Bytes; use futures::channel::mpsc; use futures::Stream; -use graphql_parser::parse_query; -use graphql_parser::query::{Definition, OperationDefinition}; use std::any::Any; use std::collections::HashMap; use std::sync::atomic::AtomicUsize; @@ -249,29 +249,33 @@ where let mut subscription = None; for definition in document.definitions { - match definition { - Definition::Operation(OperationDefinition::Subscription(s)) => { - if subscription.is_none() - && (s.name.as_deref() == operation_name || operation_name.is_none()) - { - subscription = Some(s); + match definition.node { + Definition::Operation(operation) => { + if let OperationDefinition::Subscription(s) = operation.node { + if subscription.is_none() + && (s.name.as_ref().map(|v| v.as_str()) == operation_name + || operation_name.is_none()) + { + subscription = Some(s); + } } } Definition::Fragment(fragment) => { - fragments.insert(fragment.name.clone(), fragment); + fragments.insert(fragment.name.clone_inner(), fragment.into_inner()); } - _ => {} } } - let subscription = subscription.ok_or(if let Some(name) = operation_name { - QueryError::UnknownOperationNamed { - name: name.to_string(), - } - .into_error(Pos::default()) - } else { - QueryError::MissingOperation.into_error(Pos::default()) - })?; + let subscription = subscription + .ok_or(if let Some(name) = operation_name { + QueryError::UnknownOperationNamed { + name: name.to_string(), + } + .into_error(Pos::default()) + } else { + QueryError::MissingOperation.into_error(Pos::default()) + })? + .into_inner(); let resolve_id = AtomicUsize::default(); let environment = Arc::new(Environment { diff --git a/src/subscription/subscription_type.rs b/src/subscription/subscription_type.rs index a492277c..d2833463 100644 --- a/src/subscription/subscription_type.rs +++ b/src/subscription/subscription_type.rs @@ -1,7 +1,7 @@ use crate::context::Environment; +use crate::parser::ast::{Selection, TypeCondition}; use crate::{Context, ContextSelectionSet, ObjectType, Result, Schema, Type}; use futures::{Future, Stream}; -use graphql_parser::query::{Selection, TypeCondition}; use std::pin::Pin; use std::sync::Arc; @@ -42,7 +42,7 @@ where { Box::pin(async move { for selection in &ctx.items { - match selection { + match &selection.node { Selection::Field(field) => { if ctx.is_skip(&field.directives)? { continue; @@ -81,7 +81,9 @@ where continue; } - if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition { + if let Some(TypeCondition::On(name)) = + inline_fragment.type_condition.as_ref().map(|v| &v.node) + { if name.as_str() == Subscription::type_name() { create_subscription_stream( schema, diff --git a/src/types/connection/connection_type.rs b/src/types/connection/connection_type.rs index fb231805..e6b2c7d2 100644 --- a/src/types/connection/connection_type.rs +++ b/src/types/connection/connection_type.rs @@ -179,25 +179,26 @@ impl ObjectType async fn resolve_field(&self, ctx: &Context<'_>) -> Result { if ctx.name.as_str() == "pageInfo" { let ctx_obj = ctx.with_selection_set(&ctx.selection_set); - return OutputValueType::resolve(self.page_info().await, &ctx_obj, ctx.position).await; + return OutputValueType::resolve(self.page_info().await, &ctx_obj, ctx.position()) + .await; } else if ctx.name.as_str() == "edges" { let ctx_obj = ctx.with_selection_set(&ctx.selection_set); - return OutputValueType::resolve(&self.edges().await, &ctx_obj, ctx.position).await; + return OutputValueType::resolve(&self.edges().await, &ctx_obj, ctx.position()).await; } else if ctx.name.as_str() == "totalCount" { let ctx_obj = ctx.with_selection_set(&ctx.selection_set); - return OutputValueType::resolve(&self.total_count().await, &ctx_obj, ctx.position) + return OutputValueType::resolve(&self.total_count().await, &ctx_obj, ctx.position()) .await; } else if ctx.name.as_str() == T::type_name().to_plural().to_camel_case() { let ctx_obj = ctx.with_selection_set(&ctx.selection_set); let items = self.nodes.iter().map(|(_, _, item)| item).collect_vec(); - return OutputValueType::resolve(&items, &ctx_obj, ctx.position).await; + return OutputValueType::resolve(&items, &ctx_obj, ctx.position()).await; } Err(Error::Query { - pos: ctx.position, + pos: ctx.position(), path: None, err: QueryError::FieldNotFound { - field_name: ctx.name.clone(), + field_name: ctx.name.clone_inner(), object: Connection::::type_name().to_string(), }, }) diff --git a/src/types/connection/edge.rs b/src/types/connection/edge.rs index eee59dad..25aff0d1 100644 --- a/src/types/connection/edge.rs +++ b/src/types/connection/edge.rs @@ -1,7 +1,7 @@ use crate::{ - do_resolve, registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Result, Type, + do_resolve, registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Pos, Result, + Type, }; -use graphql_parser::Pos; use std::borrow::Cow; use std::collections::HashMap; @@ -105,7 +105,7 @@ where async fn resolve_field(&self, ctx: &Context<'_>) -> Result { if ctx.name.as_str() == "node" { let ctx_obj = ctx.with_selection_set(&ctx.selection_set); - return OutputValueType::resolve(self.node().await, &ctx_obj, ctx.position).await; + return OutputValueType::resolve(self.node().await, &ctx_obj, ctx.position()).await; } else if ctx.name.as_str() == "cursor" { return Ok(self.cursor().await.into()); } diff --git a/src/types/empty_mutation.rs b/src/types/empty_mutation.rs index 2e65b149..ca1d721c 100644 --- a/src/types/empty_mutation.rs +++ b/src/types/empty_mutation.rs @@ -1,8 +1,7 @@ use crate::{ - registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, QueryError, Result, - Type, + registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, Pos, QueryError, + Result, Type, }; -use graphql_parser::Pos; use std::borrow::Cow; /// Empty mutation diff --git a/src/types/empty_subscription.rs b/src/types/empty_subscription.rs index 7cb7438b..629a09aa 100644 --- a/src/types/empty_subscription.rs +++ b/src/types/empty_subscription.rs @@ -1,10 +1,9 @@ use crate::context::Environment; use crate::{ - registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, QueryError, Result, - Schema, SubscriptionType, Type, + registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, Pos, QueryError, + Result, Schema, SubscriptionType, Type, }; use futures::Stream; -use graphql_parser::Pos; use std::borrow::Cow; use std::pin::Pin; use std::sync::Arc; diff --git a/src/types/enum.rs b/src/types/enum.rs index c3c5146d..62bdbb25 100644 --- a/src/types/enum.rs +++ b/src/types/enum.rs @@ -1,5 +1,4 @@ -use crate::{Result, Type}; -use graphql_parser::query::Value; +use crate::{Result, Type, Value}; #[allow(missing_docs)] pub struct EnumItem { diff --git a/src/types/list.rs b/src/types/list.rs index 060dc3dc..63fd30bf 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -1,5 +1,6 @@ -use crate::{registry, ContextSelectionSet, InputValueType, OutputValueType, Result, Type, Value}; -use graphql_parser::Pos; +use crate::{ + registry, ContextSelectionSet, InputValueType, OutputValueType, Pos, Result, Type, Value, +}; use std::borrow::Cow; impl Type for Vec { diff --git a/src/types/query_root.rs b/src/types/query_root.rs index 6c8ed422..55d157a2 100644 --- a/src/types/query_root.rs +++ b/src/types/query_root.rs @@ -1,11 +1,10 @@ use crate::model::{__Schema, __Type}; use crate::scalars::Any; use crate::{ - do_resolve, registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, + do_resolve, registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, Pos, QueryError, Result, Type, Value, }; use async_graphql_derive::SimpleObject; -use graphql_parser::Pos; use std::borrow::Cow; use std::collections::HashMap; @@ -84,10 +83,10 @@ impl ObjectType for QueryRoot { if ctx.name.as_str() == "__schema" { if self.disable_introspection { return Err(Error::Query { - pos: ctx.position, + pos: ctx.position(), path: Some(ctx.path_node.as_ref().unwrap().to_json()), err: QueryError::FieldNotFound { - field_name: ctx.name.clone(), + field_name: ctx.name.clone_inner(), object: Self::type_name().to_string(), }, }); @@ -99,11 +98,11 @@ impl ObjectType for QueryRoot { registry: &ctx.registry, }, &ctx_obj, - ctx.position, + ctx.position(), ) .await; } else if ctx.name.as_str() == "__type" { - let type_name: String = ctx.param_value("name", ctx.position, || Value::Null)?; + let type_name: String = ctx.param_value("name", || Value::Null)?; let ctx_obj = ctx.with_selection_set(&ctx.selection_set); return OutputValueType::resolve( &ctx.registry @@ -111,15 +110,14 @@ impl ObjectType for QueryRoot { .get(&type_name) .map(|ty| __Type::new_simple(ctx.registry, ty)), &ctx_obj, - ctx.position, + ctx.position(), ) .await; } else if ctx.name.as_str() == "_entities" { - let representations: Vec = - ctx.param_value("representations", ctx.position, || Value::Null)?; + let representations: Vec = ctx.param_value("representations", || Value::Null)?; let mut res = Vec::new(); for item in representations { - res.push(self.inner.find_entity(ctx, ctx.position, &item.0).await?); + res.push(self.inner.find_entity(ctx, &item.0).await?); } return Ok(res.into()); } else if ctx.name.as_str() == "_service" { @@ -129,7 +127,7 @@ impl ObjectType for QueryRoot { sdl: Some(ctx.registry.create_federation_sdl()), }, &ctx_obj, - ctx.position, + ctx.position(), ) .await; } diff --git a/src/validation/mod.rs b/src/validation/mod.rs index b15b6746..efd97115 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -7,9 +7,9 @@ mod visitors; #[cfg(test)] mod test_harness; +use crate::parser::ast::Document; use crate::registry::Registry; use crate::{CacheControl, Error, Result}; -use graphql_parser::query::Document; use visitor::{visit, VisitorContext, VisitorNil}; pub struct CheckResult { diff --git a/src/validation/rules/arguments_of_correct_type.rs b/src/validation/rules/arguments_of_correct_type.rs index 8df27343..f9f95cae 100644 --- a/src/validation/rules/arguments_of_correct_type.rs +++ b/src/validation/rules/arguments_of_correct_type.rs @@ -1,11 +1,9 @@ use crate::context::QueryPathNode; +use crate::parser::ast::{Directive, Field}; use crate::registry::InputValue; use crate::validation::utils::is_valid_input_value; use crate::validation::visitor::{Visitor, VisitorContext}; -use crate::QueryPathSegment; -use graphql_parser::query::Field; -use graphql_parser::schema::{Directive, Value}; -use graphql_parser::Pos; +use crate::{QueryPathSegment, Spanned, Value}; use std::collections::HashMap; #[derive(Default)] @@ -14,33 +12,36 @@ pub struct ArgumentsOfCorrectType<'a> { } impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> { - fn enter_directive(&mut self, ctx: &mut VisitorContext<'a>, directive: &'a Directive) { + fn enter_directive(&mut self, ctx: &mut VisitorContext<'a>, directive: &'a Spanned) { self.current_args = ctx .registry .directives - .get(&directive.name) + .get(directive.name.as_str()) .map(|d| &d.args); } - fn exit_directive(&mut self, _ctx: &mut VisitorContext<'a>, _directive: &'a Directive) { + fn exit_directive( + &mut self, + _ctx: &mut VisitorContext<'a>, + _directive: &'a Spanned, + ) { self.current_args = None; } fn enter_argument( &mut self, ctx: &mut VisitorContext<'a>, - pos: Pos, - name: &str, - value: &'a Value, + name: &'a Spanned, + value: &'a Spanned, ) { if let Some(arg) = self .current_args - .and_then(|args| args.get(name).map(|input| input)) + .and_then(|args| args.get(name.as_str()).map(|input| input)) { if let Some(validator) = &arg.validator { if let Some(reason) = validator.is_valid(value) { ctx.report_error( - vec![pos], + vec![name.position()], format!("Invalid value for argument \"{}\", {}", arg.name, reason), ); return; @@ -56,19 +57,22 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> { segment: QueryPathSegment::Name(arg.name), }, ) { - ctx.report_error(vec![pos], format!("Invalid value for argument {}", reason)); + ctx.report_error( + vec![name.position()], + format!("Invalid value for argument {}", reason), + ); } } } - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Field) { + fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Spanned) { self.current_args = ctx .parent_type() .and_then(|p| p.field_by_name(&field.name)) .map(|f| &f.args); } - fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Field) { + fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Spanned) { self.current_args = None; } } diff --git a/src/validation/rules/default_values_of_correct_type.rs b/src/validation/rules/default_values_of_correct_type.rs index 785d1ca5..a175b5a0 100644 --- a/src/validation/rules/default_values_of_correct_type.rs +++ b/src/validation/rules/default_values_of_correct_type.rs @@ -1,8 +1,8 @@ use crate::context::QueryPathNode; +use crate::parser::ast::{Type, VariableDefinition}; use crate::validation::utils::is_valid_input_value; use crate::validation::visitor::{Visitor, VisitorContext}; -use crate::QueryPathSegment; -use graphql_parser::query::{Type, VariableDefinition}; +use crate::{QueryPathSegment, Spanned}; pub struct DefaultValuesOfCorrectType; @@ -10,11 +10,11 @@ impl<'a> Visitor<'a> for DefaultValuesOfCorrectType { fn enter_variable_definition( &mut self, ctx: &mut VisitorContext<'a>, - variable_definition: &'a VariableDefinition, + variable_definition: &'a Spanned, ) { if let Some(value) = &variable_definition.default_value { - if let Type::NonNullType(_) = variable_definition.var_type { - ctx.report_error(vec![variable_definition.position],format!( + 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, )); @@ -28,7 +28,7 @@ impl<'a> Visitor<'a> for DefaultValuesOfCorrectType { }, ) { ctx.report_error( - vec![variable_definition.position], + vec![variable_definition.position()], format!("Invalid default value for argument {}", reason), ) } diff --git a/src/validation/rules/fields_on_correct_type.rs b/src/validation/rules/fields_on_correct_type.rs index 12cfc425..5814785c 100644 --- a/src/validation/rules/fields_on_correct_type.rs +++ b/src/validation/rules/fields_on_correct_type.rs @@ -1,18 +1,18 @@ -use crate::registry; +use crate::parser::ast::Field; use crate::validation::suggestion::make_suggestion; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::Field; +use crate::{registry, Spanned}; #[derive(Default)] pub struct FieldsOnCorrectType; impl<'a> Visitor<'a> for FieldsOnCorrectType { - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Field) { + fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Spanned) { if let Some(parent_type) = ctx.parent_type() { if let Some(registry::Type::Union { .. }) | Some(registry::Type::Interface { .. }) = ctx.parent_type() { - if field.name == "__typename" { + if field.name.as_str() == "__typename" { return; } } @@ -23,7 +23,7 @@ impl<'a> Visitor<'a> for FieldsOnCorrectType { .is_none() { ctx.report_error( - vec![field.position], + vec![field.position()], format!( "Unknown field \"{}\" on type \"{}\".{}", field.name, diff --git a/src/validation/rules/fragments_on_composite_types.rs b/src/validation/rules/fragments_on_composite_types.rs index de5fe7c1..03514346 100644 --- a/src/validation/rules/fragments_on_composite_types.rs +++ b/src/validation/rules/fragments_on_composite_types.rs @@ -1,5 +1,6 @@ +use crate::parser::ast::{FragmentDefinition, InlineFragment, TypeCondition}; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::{FragmentDefinition, InlineFragment, TypeCondition}; +use crate::Spanned; #[derive(Default)] pub struct FragmentsOnCompositeTypes; @@ -8,13 +9,13 @@ impl<'a> Visitor<'a> for FragmentsOnCompositeTypes { fn enter_fragment_definition( &mut self, ctx: &mut VisitorContext<'a>, - fragment_definition: &'a FragmentDefinition, + fragment_definition: &'a Spanned, ) { if let Some(current_type) = ctx.current_type() { if !current_type.is_composite() { - let TypeCondition::On(name) = &fragment_definition.type_condition; + let TypeCondition::On(name) = &fragment_definition.type_condition.node; ctx.report_error( - vec![fragment_definition.position], + vec![fragment_definition.position()], format!( "Fragment \"{}\" cannot condition non composite type \"{}\"", fragment_definition.name, name @@ -27,12 +28,12 @@ impl<'a> Visitor<'a> for FragmentsOnCompositeTypes { fn enter_inline_fragment( &mut self, ctx: &mut VisitorContext<'a>, - inline_fragment: &'a InlineFragment, + inline_fragment: &'a Spanned, ) { if let Some(current_type) = ctx.current_type() { if !current_type.is_composite() { ctx.report_error( - vec![inline_fragment.position], + vec![inline_fragment.position()], format!( "Fragment cannot condition non composite type \"{}\"", current_type.name() diff --git a/src/validation/rules/known_argument_names.rs b/src/validation/rules/known_argument_names.rs index 9b1641f4..36ef60ce 100644 --- a/src/validation/rules/known_argument_names.rs +++ b/src/validation/rules/known_argument_names.rs @@ -1,9 +1,8 @@ +use crate::parser::ast::{Directive, Field}; use crate::registry::InputValue; use crate::validation::suggestion::make_suggestion; use crate::validation::visitor::{Visitor, VisitorContext}; -use crate::Value; -use graphql_parser::query::{Directive, Field}; -use graphql_parser::Pos; +use crate::{Spanned, Value}; use std::collections::HashMap; enum ArgsType<'a> { @@ -34,34 +33,37 @@ impl<'a> KnownArgumentNames<'a> { } impl<'a> Visitor<'a> for KnownArgumentNames<'a> { - fn enter_directive(&mut self, ctx: &mut VisitorContext<'a>, directive: &'a Directive) { + fn enter_directive(&mut self, ctx: &mut VisitorContext<'a>, directive: &'a Spanned) { self.current_args = ctx .registry .directives - .get(&directive.name) + .get(directive.name.as_str()) .map(|d| (&d.args, ArgsType::Directive(&directive.name))); } - fn exit_directive(&mut self, _ctx: &mut VisitorContext<'a>, _directive: &'a Directive) { + fn exit_directive( + &mut self, + _ctx: &mut VisitorContext<'a>, + _directive: &'a Spanned, + ) { self.current_args = None; } fn enter_argument( &mut self, ctx: &mut VisitorContext<'a>, - pos: Pos, - name: &str, - _value: &'a Value, + name: &'a Spanned, + _value: &'a Spanned, ) { if let Some((args, arg_type)) = &self.current_args { - if !args.contains_key(name) { + if !args.contains_key(name.as_str()) { match arg_type { ArgsType::Field { field_name, type_name, } => { ctx.report_error( - vec![pos], + vec![name.position()], format!( "Unknown argument \"{}\" on field \"{}\" of type \"{}\".{}", name, @@ -73,7 +75,7 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> { } ArgsType::Directive(directive_name) => { ctx.report_error( - vec![pos], + vec![name.position()], format!( "Unknown argument \"{}\" on directive \"{}\".{}", name, @@ -87,7 +89,7 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> { } } - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Field) { + fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Spanned) { if let Some(parent_type) = ctx.parent_type() { if let Some(schema_field) = parent_type.field_by_name(&field.name) { self.current_args = Some(( @@ -101,7 +103,7 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> { } } - fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Field) { + fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Spanned) { self.current_args = None; } } diff --git a/src/validation/rules/known_directives.rs b/src/validation/rules/known_directives.rs index 91858cad..2218beaa 100644 --- a/src/validation/rules/known_directives.rs +++ b/src/validation/rules/known_directives.rs @@ -1,9 +1,9 @@ use crate::model::__DirectiveLocation; -use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::{ - Field, FragmentDefinition, FragmentSpread, InlineFragment, OperationDefinition, +use crate::parser::ast::{ + Directive, Field, FragmentDefinition, FragmentSpread, InlineFragment, OperationDefinition, }; -use graphql_parser::schema::Directive; +use crate::validation::visitor::{Visitor, VisitorContext}; +use crate::Spanned; #[derive(Default)] pub struct KnownDirectives { @@ -14,9 +14,9 @@ impl<'a> Visitor<'a> for KnownDirectives { fn enter_operation_definition( &mut self, _ctx: &mut VisitorContext<'a>, - operation_definition: &'a OperationDefinition, + operation_definition: &'a Spanned, ) { - self.location_stack.push(match operation_definition { + self.location_stack.push(match &operation_definition.node { OperationDefinition::SelectionSet(_) | OperationDefinition::Query(_) => { __DirectiveLocation::QUERY } @@ -28,7 +28,7 @@ impl<'a> Visitor<'a> for KnownDirectives { fn exit_operation_definition( &mut self, _ctx: &mut VisitorContext<'a>, - _operation_definition: &'a OperationDefinition, + _operation_definition: &'a Spanned, ) { self.location_stack.pop(); } @@ -36,7 +36,7 @@ impl<'a> Visitor<'a> for KnownDirectives { fn enter_fragment_definition( &mut self, _ctx: &mut VisitorContext<'a>, - _fragment_definition: &'a FragmentDefinition, + _fragment_definition: &'a Spanned, ) { self.location_stack .push(__DirectiveLocation::FRAGMENT_DEFINITION); @@ -45,17 +45,17 @@ impl<'a> Visitor<'a> for KnownDirectives { fn exit_fragment_definition( &mut self, _ctx: &mut VisitorContext<'a>, - _fragment_definition: &'a FragmentDefinition, + _fragment_definition: &'a Spanned, ) { self.location_stack.pop(); } - fn enter_directive(&mut self, ctx: &mut VisitorContext<'a>, directive: &'a Directive) { + fn enter_directive(&mut self, ctx: &mut VisitorContext<'a>, directive: &'a Spanned) { if let Some(schema_directive) = ctx.registry.directives.get(directive.name.as_str()) { if let Some(current_location) = self.location_stack.last() { if !schema_directive.locations.contains(current_location) { ctx.report_error( - vec![directive.position], + vec![directive.position()], format!( "Directive \"{}\" may not be used on \"{:?}\"", directive.name, current_location @@ -65,24 +65,24 @@ impl<'a> Visitor<'a> for KnownDirectives { } } else { ctx.report_error( - vec![directive.position], + vec![directive.position()], format!("Unknown directive \"{}\"", directive.name), ); } } - fn enter_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Field) { + fn enter_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Spanned) { self.location_stack.push(__DirectiveLocation::FIELD); } - fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Field) { + fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Spanned) { self.location_stack.pop(); } fn enter_fragment_spread( &mut self, _ctx: &mut VisitorContext<'a>, - _fragment_spread: &'a FragmentSpread, + _fragment_spread: &'a Spanned, ) { self.location_stack .push(__DirectiveLocation::FRAGMENT_SPREAD); @@ -91,7 +91,7 @@ impl<'a> Visitor<'a> for KnownDirectives { fn exit_fragment_spread( &mut self, _ctx: &mut VisitorContext<'a>, - _fragment_spread: &'a FragmentSpread, + _fragment_spread: &'a Spanned, ) { self.location_stack.pop(); } @@ -99,7 +99,7 @@ impl<'a> Visitor<'a> for KnownDirectives { fn enter_inline_fragment( &mut self, _ctx: &mut VisitorContext<'a>, - _inline_fragment: &'a InlineFragment, + _inline_fragment: &'a Spanned, ) { self.location_stack .push(__DirectiveLocation::INLINE_FRAGMENT); @@ -108,7 +108,7 @@ impl<'a> Visitor<'a> for KnownDirectives { fn exit_inline_fragment( &mut self, _ctx: &mut VisitorContext<'a>, - _inline_fragment: &'a InlineFragment, + _inline_fragment: &'a Spanned, ) { self.location_stack.pop(); } diff --git a/src/validation/rules/known_fragment_names.rs b/src/validation/rules/known_fragment_names.rs index e68e41eb..d4baa169 100644 --- a/src/validation/rules/known_fragment_names.rs +++ b/src/validation/rules/known_fragment_names.rs @@ -1,5 +1,6 @@ +use crate::parser::ast::FragmentSpread; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::FragmentSpread; +use crate::Spanned; #[derive(Default)] pub struct KnownFragmentNames; @@ -8,11 +9,11 @@ impl<'a> Visitor<'a> for KnownFragmentNames { fn enter_fragment_spread( &mut self, ctx: &mut VisitorContext<'a>, - fragment_spread: &'a FragmentSpread, + fragment_spread: &'a Spanned, ) { if !ctx.is_known_fragment(&fragment_spread.fragment_name) { ctx.report_error( - vec![fragment_spread.position], + vec![fragment_spread.position()], format!(r#"Unknown fragment: "{}""#, fragment_spread.fragment_name), ); } diff --git a/src/validation/rules/known_type_names.rs b/src/validation/rules/known_type_names.rs index 61c55aa9..747241a8 100644 --- a/src/validation/rules/known_type_names.rs +++ b/src/validation/rules/known_type_names.rs @@ -1,9 +1,7 @@ +use crate::parser::ast::{FragmentDefinition, InlineFragment, TypeCondition, VariableDefinition}; use crate::registry::TypeName; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::{ - FragmentDefinition, InlineFragment, TypeCondition, VariableDefinition, -}; -use graphql_parser::Pos; +use crate::{Pos, Spanned}; #[derive(Default)] pub struct KnownTypeNames; @@ -12,31 +10,33 @@ impl<'a> Visitor<'a> for KnownTypeNames { fn enter_fragment_definition( &mut self, ctx: &mut VisitorContext<'a>, - fragment_definition: &'a FragmentDefinition, + fragment_definition: &'a Spanned, ) { - let TypeCondition::On(name) = &fragment_definition.type_condition; - validate_type(ctx, &name, fragment_definition.position); + let TypeCondition::On(name) = &fragment_definition.type_condition.node; + validate_type(ctx, name.as_str(), fragment_definition.position()); } fn enter_variable_definition( &mut self, ctx: &mut VisitorContext<'a>, - variable_definition: &'a VariableDefinition, + variable_definition: &'a Spanned, ) { validate_type( ctx, TypeName::concrete_typename(&variable_definition.var_type.to_string()), - variable_definition.position, + variable_definition.position(), ); } fn enter_inline_fragment( &mut self, ctx: &mut VisitorContext<'a>, - inline_fragment: &'a InlineFragment, + inline_fragment: &'a Spanned, ) { - if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition { - validate_type(ctx, &name, inline_fragment.position); + if let Some(TypeCondition::On(name)) = + inline_fragment.type_condition.as_ref().map(|c| &c.node) + { + validate_type(ctx, name.as_str(), inline_fragment.position()); } } } diff --git a/src/validation/rules/lone_anonymous_operation.rs b/src/validation/rules/lone_anonymous_operation.rs index 7b081799..135e19a2 100644 --- a/src/validation/rules/lone_anonymous_operation.rs +++ b/src/validation/rules/lone_anonymous_operation.rs @@ -1,5 +1,6 @@ +use crate::parser::ast::{Definition, Document, OperationDefinition}; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::{Definition, Document, OperationDefinition}; +use crate::Spanned; #[derive(Default)] pub struct LoneAnonymousOperation { @@ -11,7 +12,7 @@ impl<'a> Visitor<'a> for LoneAnonymousOperation { self.operation_count = Some( doc.definitions .iter() - .filter(|d| match d { + .filter(|d| match &d.node { Definition::Operation(_) => true, Definition::Fragment(_) => false, }) @@ -22,19 +23,19 @@ impl<'a> Visitor<'a> for LoneAnonymousOperation { fn enter_operation_definition( &mut self, ctx: &mut VisitorContext<'a>, - operation_definition: &'a OperationDefinition, + operation_definition: &'a Spanned, ) { if let Some(operation_count) = self.operation_count { - let (err, pos) = match operation_definition { - OperationDefinition::SelectionSet(s) => (operation_count > 1, s.span.0), + 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) + (operation_count > 1, query.position()) } OperationDefinition::Mutation(mutation) if mutation.name.is_none() => { - (operation_count > 1, mutation.position) + (operation_count > 1, mutation.position()) } OperationDefinition::Subscription(subscription) if subscription.name.is_none() => { - (operation_count > 1, subscription.position) + (operation_count > 1, subscription.position()) } _ => { return; diff --git a/src/validation/rules/no_fragment_cycles.rs b/src/validation/rules/no_fragment_cycles.rs index 0b6faeeb..cd9626b5 100644 --- a/src/validation/rules/no_fragment_cycles.rs +++ b/src/validation/rules/no_fragment_cycles.rs @@ -1,7 +1,7 @@ use crate::error::RuleError; +use crate::parser::ast::{Document, FragmentDefinition, FragmentSpread}; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::{Document, FragmentDefinition, FragmentSpread}; -use graphql_parser::Pos; +use crate::{Pos, Spanned}; use std::collections::{HashMap, HashSet}; struct CycleDetector<'a> { @@ -75,7 +75,7 @@ impl<'a> Visitor<'a> for NoFragmentCycles<'a> { fn enter_fragment_definition( &mut self, _ctx: &mut VisitorContext<'a>, - fragment_definition: &'a FragmentDefinition, + fragment_definition: &'a Spanned, ) { self.current_fragment = Some(&fragment_definition.name); self.fragment_order.push(&fragment_definition.name); @@ -84,7 +84,7 @@ impl<'a> Visitor<'a> for NoFragmentCycles<'a> { fn exit_fragment_definition( &mut self, _ctx: &mut VisitorContext<'a>, - _fragment_definition: &'a FragmentDefinition, + _fragment_definition: &'a Spanned, ) { self.current_fragment = None; } @@ -92,13 +92,13 @@ impl<'a> Visitor<'a> for NoFragmentCycles<'a> { fn enter_fragment_spread( &mut self, _ctx: &mut VisitorContext<'a>, - fragment_spread: &'a FragmentSpread, + fragment_spread: &'a Spanned, ) { if let Some(current_fragment) = self.current_fragment { self.spreads .entry(current_fragment) .or_insert_with(Vec::new) - .push((&fragment_spread.fragment_name, fragment_spread.position)); + .push((&fragment_spread.fragment_name, fragment_spread.position())); } } } diff --git a/src/validation/rules/no_undefined_variables.rs b/src/validation/rules/no_undefined_variables.rs index 62d3d1c0..6ea92e06 100644 --- a/src/validation/rules/no_undefined_variables.rs +++ b/src/validation/rules/no_undefined_variables.rs @@ -1,10 +1,9 @@ -use crate::validation::utils::{operation_name, referenced_variables, Scope}; -use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::{ +use crate::parser::ast::{ Document, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition, }; -use graphql_parser::schema::Value; -use graphql_parser::Pos; +use crate::validation::utils::{operation_name, referenced_variables, Scope}; +use crate::validation::visitor::{Visitor, VisitorContext}; +use crate::{Pos, Spanned, Value}; use std::collections::{HashMap, HashSet}; #[derive(Default)] @@ -76,7 +75,7 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> { fn enter_operation_definition( &mut self, _ctx: &mut VisitorContext<'a>, - operation_definition: &'a OperationDefinition, + operation_definition: &'a Spanned, ) { let (op_name, pos) = operation_name(&operation_definition); self.current_scope = Some(Scope::Operation(op_name)); @@ -87,7 +86,7 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> { fn enter_fragment_definition( &mut self, _ctx: &mut VisitorContext<'a>, - fragment_definition: &'a FragmentDefinition, + fragment_definition: &'a Spanned, ) { self.current_scope = Some(Scope::Fragment(fragment_definition.name.as_str())); } @@ -95,7 +94,7 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> { fn enter_variable_definition( &mut self, _ctx: &mut VisitorContext<'a>, - variable_definition: &'a VariableDefinition, + variable_definition: &'a Spanned, ) { if let Some(Scope::Operation(ref name)) = self.current_scope { if let Some(&mut (_, ref mut vars)) = self.defined_variables.get_mut(name) { @@ -107,9 +106,8 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> { fn enter_argument( &mut self, _ctx: &mut VisitorContext<'a>, - pos: Pos, - _name: &'a str, - value: &'a Value, + name: &'a Spanned, + value: &'a Spanned, ) { if let Some(ref scope) = self.current_scope { self.used_variables @@ -118,7 +116,7 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> { .extend( referenced_variables(value) .into_iter() - .map(|name| (name, pos)), + .map(|n| (n, name.position())), ); } } @@ -126,7 +124,7 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> { fn enter_fragment_spread( &mut self, _ctx: &mut VisitorContext<'a>, - fragment_spread: &'a FragmentSpread, + fragment_spread: &'a Spanned, ) { if let Some(ref scope) = self.current_scope { self.spreads diff --git a/src/validation/rules/no_unused_fragments.rs b/src/validation/rules/no_unused_fragments.rs index 3934326a..63678542 100644 --- a/src/validation/rules/no_unused_fragments.rs +++ b/src/validation/rules/no_unused_fragments.rs @@ -1,9 +1,9 @@ -use crate::validation::utils::{operation_name, Scope}; -use crate::validation::visitor::{Visitor, VisitorContext}; -use crate::Pos; -use graphql_parser::query::{ +use crate::parser::ast::{ Definition, Document, FragmentDefinition, FragmentSpread, OperationDefinition, }; +use crate::validation::utils::{operation_name, Scope}; +use crate::validation::visitor::{Visitor, VisitorContext}; +use crate::{Pos, Spanned}; use std::collections::{HashMap, HashSet}; #[derive(Default)] @@ -36,7 +36,7 @@ impl<'a> Visitor<'a> for NoUnusedFragments<'a> { let mut reachable = HashSet::new(); for def in &doc.definitions { - if let Definition::Operation(operation_definition) = def { + if let Definition::Operation(operation_definition) = &def.node { let (name, _) = operation_name(operation_definition); self.find_reachable_fragments(&Scope::Operation(name), &mut reachable); } @@ -55,7 +55,7 @@ impl<'a> Visitor<'a> for NoUnusedFragments<'a> { fn enter_operation_definition( &mut self, _ctx: &mut VisitorContext<'a>, - operation_definition: &'a OperationDefinition, + operation_definition: &'a Spanned, ) { let (op_name, _) = operation_name(operation_definition); self.current_scope = Some(Scope::Operation(op_name)); @@ -64,19 +64,19 @@ impl<'a> Visitor<'a> for NoUnusedFragments<'a> { fn enter_fragment_definition( &mut self, _ctx: &mut VisitorContext<'a>, - fragment_definition: &'a FragmentDefinition, + fragment_definition: &'a Spanned, ) { self.current_scope = Some(Scope::Fragment(fragment_definition.name.as_str())); self.defined_fragments.insert(( fragment_definition.name.as_str(), - fragment_definition.position, + fragment_definition.position(), )); } fn enter_fragment_spread( &mut self, _ctx: &mut VisitorContext<'a>, - fragment_spread: &'a FragmentSpread, + fragment_spread: &'a Spanned, ) { if let Some(ref scope) = self.current_scope { self.spreads diff --git a/src/validation/rules/no_unused_variables.rs b/src/validation/rules/no_unused_variables.rs index 5ab06b0c..f660bab3 100644 --- a/src/validation/rules/no_unused_variables.rs +++ b/src/validation/rules/no_unused_variables.rs @@ -1,10 +1,9 @@ -use crate::validation::utils::{operation_name, referenced_variables, Scope}; -use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::{ +use crate::parser::ast::{ Document, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition, }; -use graphql_parser::schema::Value; -use graphql_parser::Pos; +use crate::validation::utils::{operation_name, referenced_variables, Scope}; +use crate::validation::visitor::{Visitor, VisitorContext}; +use crate::{Pos, Spanned, Value}; use std::collections::{HashMap, HashSet}; #[derive(Default)] @@ -76,7 +75,7 @@ impl<'a> Visitor<'a> for NoUnusedVariables<'a> { fn enter_operation_definition( &mut self, _ctx: &mut VisitorContext<'a>, - operation_definition: &'a OperationDefinition, + operation_definition: &'a Spanned, ) { let (op_name, _) = operation_name(operation_definition); self.current_scope = Some(Scope::Operation(op_name)); @@ -86,7 +85,7 @@ impl<'a> Visitor<'a> for NoUnusedVariables<'a> { fn enter_fragment_definition( &mut self, _ctx: &mut VisitorContext<'a>, - fragment_definition: &'a FragmentDefinition, + fragment_definition: &'a Spanned, ) { self.current_scope = Some(Scope::Fragment(fragment_definition.name.as_str())); } @@ -94,13 +93,13 @@ impl<'a> Visitor<'a> for NoUnusedVariables<'a> { fn enter_variable_definition( &mut self, _ctx: &mut VisitorContext<'a>, - variable_definition: &'a VariableDefinition, + variable_definition: &'a Spanned, ) { 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.position(), )); } } @@ -109,9 +108,8 @@ impl<'a> Visitor<'a> for NoUnusedVariables<'a> { fn enter_argument( &mut self, _ctx: &mut VisitorContext<'a>, - _pos: Pos, - _name: &'a str, - value: &'a Value, + _name: &'a Spanned, + value: &'a Spanned, ) { if let Some(ref scope) = self.current_scope { self.used_variables @@ -124,7 +122,7 @@ impl<'a> Visitor<'a> for NoUnusedVariables<'a> { fn enter_fragment_spread( &mut self, _ctx: &mut VisitorContext<'a>, - fragment_spread: &'a FragmentSpread, + fragment_spread: &'a Spanned, ) { if let Some(ref scope) = self.current_scope { self.spreads diff --git a/src/validation/rules/overlapping_fields_can_be_merged.rs b/src/validation/rules/overlapping_fields_can_be_merged.rs index 08a23ca1..8eefa6e5 100644 --- a/src/validation/rules/overlapping_fields_can_be_merged.rs +++ b/src/validation/rules/overlapping_fields_can_be_merged.rs @@ -1,5 +1,6 @@ +use crate::parser::ast::{Field, Selection, SelectionSet}; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::{Field, Selection, SelectionSet}; +use crate::Spanned; use std::collections::HashMap; #[derive(Default)] @@ -9,7 +10,7 @@ impl<'a> Visitor<'a> for OverlappingFieldsCanBeMerged { fn enter_selection_set( &mut self, ctx: &mut VisitorContext<'a>, - selection_set: &'a SelectionSet, + selection_set: &'a Spanned, ) { let mut find_conflicts = FindConflicts { outputs: Default::default(), @@ -20,18 +21,19 @@ impl<'a> Visitor<'a> for OverlappingFieldsCanBeMerged { } struct FindConflicts<'a, 'ctx> { - outputs: HashMap<&'a str, &'a Field>, + outputs: HashMap<&'a str, &'a Spanned>, ctx: &'a mut VisitorContext<'ctx>, } impl<'a, 'ctx> FindConflicts<'a, 'ctx> { - pub fn find(&mut self, selection_set: &'a SelectionSet) { + pub fn find(&mut self, selection_set: &'a Spanned) { for selection in &selection_set.items { - match selection { + match &selection.node { Selection::Field(field) => { let output_name = field .alias - .as_deref() + .as_ref() + .map(|name| name.as_str()) .unwrap_or_else(|| field.name.as_str()); self.add_output(output_name, field); } @@ -47,11 +49,11 @@ impl<'a, 'ctx> FindConflicts<'a, 'ctx> { } } - fn add_output(&mut self, name: &'a str, field: &'a Field) { + fn add_output(&mut self, name: &'a str, field: &'a Spanned) { if let Some(prev_field) = self.outputs.get(name) { if prev_field.name != field.name { self.ctx.report_error( - vec![prev_field.position, field.position], + vec![prev_field.position(), field.position()], 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)); } @@ -59,20 +61,16 @@ impl<'a, 'ctx> FindConflicts<'a, 'ctx> { // check arguments if prev_field.arguments.len() != field.arguments.len() { self.ctx.report_error( - vec![prev_field.position, field.position], + vec![prev_field.position(), field.position()], 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 - .arguments - .iter() - .find(|(other_name, _)| other_name == name) - .map(|(_, v)| v) + match field.get_argument(name.as_str()) { Some(other_value) if value == other_value => {} _=> self.ctx.report_error( - vec![prev_field.position, field.position], + vec![prev_field.position(), field.position()], 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 87c75786..a4aa3eaa 100644 --- a/src/validation/rules/possible_fragment_spreads.rs +++ b/src/validation/rules/possible_fragment_spreads.rs @@ -1,5 +1,6 @@ +use crate::parser::ast::{Definition, Document, FragmentSpread, InlineFragment, TypeCondition}; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::{Definition, Document, FragmentSpread, InlineFragment, TypeCondition}; +use crate::Spanned; use std::collections::HashMap; #[derive(Default)] @@ -10,8 +11,8 @@ 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 { - let TypeCondition::On(type_name) = &fragment.type_condition; + if let Definition::Fragment(fragment) = &d.node { + let TypeCondition::On(type_name) = &fragment.type_condition.node; self.fragment_types .insert(fragment.name.as_str(), type_name); } @@ -21,7 +22,7 @@ impl<'a> Visitor<'a> for PossibleFragmentSpreads<'a> { fn enter_fragment_spread( &mut self, ctx: &mut VisitorContext<'a>, - fragment_spread: &'a FragmentSpread, + fragment_spread: &'a Spanned, ) { if let Some(fragment_type) = self .fragment_types @@ -31,7 +32,7 @@ impl<'a> Visitor<'a> for PossibleFragmentSpreads<'a> { 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.position()], format!( "Fragment \"{}\" cannot be spread here as objects of type \"{}\" can never be of type \"{}\"", &fragment_spread.fragment_name, current_type.name(), fragment_type @@ -46,14 +47,16 @@ impl<'a> Visitor<'a> for PossibleFragmentSpreads<'a> { fn enter_inline_fragment( &mut self, ctx: &mut VisitorContext<'a>, - inline_fragment: &'a InlineFragment, + inline_fragment: &'a Spanned, ) { if let Some(parent_type) = ctx.parent_type() { - if let Some(TypeCondition::On(fragment_type)) = &inline_fragment.type_condition { + if let Some(TypeCondition::On(fragment_type)) = + &inline_fragment.type_condition.as_ref().map(|c| &c.node) + { if let Some(on_type) = ctx.registry.types.get(fragment_type.as_str()) { if !parent_type.type_overlap(&on_type) { ctx.report_error( - vec![inline_fragment.position], + vec![inline_fragment.position()], format!( "Fragment cannot be spread here as objects of type \"{}\" \ can never be of type \"{}\"", diff --git a/src/validation/rules/provided_non_null_arguments.rs b/src/validation/rules/provided_non_null_arguments.rs index 0bb672af..9dda0edf 100644 --- a/src/validation/rules/provided_non_null_arguments.rs +++ b/src/validation/rules/provided_non_null_arguments.rs @@ -1,24 +1,24 @@ +use crate::parser::ast::{Directive, Field}; use crate::registry::TypeName; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::Field; -use graphql_parser::schema::Directive; +use crate::Spanned; #[derive(Default)] pub struct ProvidedNonNullArguments; impl<'a> Visitor<'a> for ProvidedNonNullArguments { - fn enter_directive(&mut self, ctx: &mut VisitorContext<'a>, directive: &'a Directive) { - if let Some(schema_directive) = ctx.registry.directives.get(&directive.name) { + fn enter_directive(&mut self, ctx: &mut VisitorContext<'a>, directive: &'a Spanned) { + if let Some(schema_directive) = ctx.registry.directives.get(directive.name.as_str()) { for arg in schema_directive.args.values() { if TypeName::create(&arg.ty).is_non_null() && arg.default_value.is_none() && directive .arguments .iter() - .find(|(name, _)| name == arg.name) + .find(|(name, _)| name.as_str() == arg.name) .is_none() { - ctx.report_error(vec![directive.position], + ctx.report_error(vec![directive.position()], format!( "Directive \"@{}\" argument \"{}\" of type \"{}\" is required but not provided", directive.name, arg.name, arg.ty @@ -28,7 +28,7 @@ impl<'a> Visitor<'a> for ProvidedNonNullArguments { } } - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Field) { + fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Spanned) { if let Some(parent_type) = ctx.parent_type() { if let Some(schema_field) = parent_type.field_by_name(&field.name) { for arg in schema_field.args.values() { @@ -37,10 +37,10 @@ impl<'a> Visitor<'a> for ProvidedNonNullArguments { && field .arguments .iter() - .find(|(name, _)| name == arg.name) + .find(|(name, _)| name.as_str() == arg.name) .is_none() { - ctx.report_error(vec![field.position], + ctx.report_error(vec![field.position()], format!( r#"Field "{}" argument "{}" of type "{}" is required but not provided"#, field.name, arg.name, parent_type.name() diff --git a/src/validation/rules/scalar_leafs.rs b/src/validation/rules/scalar_leafs.rs index 806df063..ec36ce68 100644 --- a/src/validation/rules/scalar_leafs.rs +++ b/src/validation/rules/scalar_leafs.rs @@ -1,22 +1,23 @@ +use crate::parser::ast::Field; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::Field; +use crate::Spanned; #[derive(Default)] pub struct ScalarLeafs; impl<'a> Visitor<'a> for ScalarLeafs { - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Field) { + fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Spanned) { if let Some(ty) = ctx.parent_type() { if let Some(schema_field) = ty.field_by_name(&field.name) { 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!( + ctx.report_error(vec![field.position()], format!( "Field \"{}\" must not have a selection since type \"{}\" has no subfields", field.name, ty.name() )) } else if !ty.is_leaf() && field.selection_set.items.is_empty() { ctx.report_error( - vec![field.position], + vec![field.position()], format!( "Field \"{}\" of type \"{}\" must have a selection of subfields", field.name, diff --git a/src/validation/rules/unique_argument_names.rs b/src/validation/rules/unique_argument_names.rs index 94b0f5a9..5c307da8 100644 --- a/src/validation/rules/unique_argument_names.rs +++ b/src/validation/rules/unique_argument_names.rs @@ -1,7 +1,6 @@ +use crate::parser::ast::{Directive, Field}; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::Field; -use graphql_parser::schema::{Directive, Value}; -use graphql_parser::Pos; +use crate::{Spanned, Value}; use std::collections::HashSet; #[derive(Default)] @@ -10,26 +9,29 @@ pub struct UniqueArgumentNames<'a> { } impl<'a> Visitor<'a> for UniqueArgumentNames<'a> { - fn enter_directive(&mut self, _ctx: &mut VisitorContext<'a>, _directive: &'a Directive) { + fn enter_directive( + &mut self, + _ctx: &mut VisitorContext<'a>, + _directive: &'a Spanned, + ) { self.names.clear(); } fn enter_argument( &mut self, ctx: &mut VisitorContext<'a>, - pos: Pos, - name: &'a str, - _value: &'a Value, + name: &'a Spanned, + _value: &'a Spanned, ) { if !self.names.insert(name) { ctx.report_error( - vec![pos], + vec![name.position()], format!("There can only be one argument named \"{}\"", name), ) } } - fn enter_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Field) { + fn enter_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Spanned) { self.names.clear(); } } diff --git a/src/validation/rules/unique_fragment_names.rs b/src/validation/rules/unique_fragment_names.rs index 52172ae3..9054025d 100644 --- a/src/validation/rules/unique_fragment_names.rs +++ b/src/validation/rules/unique_fragment_names.rs @@ -1,5 +1,6 @@ +use crate::parser::ast::FragmentDefinition; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::FragmentDefinition; +use crate::Spanned; use std::collections::HashSet; #[derive(Default)] @@ -11,11 +12,11 @@ impl<'a> Visitor<'a> for UniqueFragmentNames<'a> { fn enter_fragment_definition( &mut self, ctx: &mut VisitorContext<'a>, - fragment_definition: &'a FragmentDefinition, + fragment_definition: &'a Spanned, ) { if !self.names.insert(&fragment_definition.name) { ctx.report_error( - vec![fragment_definition.position], + vec![fragment_definition.position()], format!( "There can only be one fragment named \"{}\"", fragment_definition.name diff --git a/src/validation/rules/unique_operation_names.rs b/src/validation/rules/unique_operation_names.rs index c48c96fb..84239468 100644 --- a/src/validation/rules/unique_operation_names.rs +++ b/src/validation/rules/unique_operation_names.rs @@ -1,5 +1,6 @@ +use crate::parser::ast::{Mutation, OperationDefinition, Query, Subscription}; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::{Mutation, OperationDefinition, Query, Subscription}; +use crate::Spanned; use std::collections::HashSet; #[derive(Default)] @@ -11,25 +12,28 @@ impl<'a> Visitor<'a> for UniqueOperationNames<'a> { fn enter_operation_definition( &mut self, ctx: &mut VisitorContext<'a>, - operation_definition: &'a OperationDefinition, + operation_definition: &'a Spanned, ) { - let name = match operation_definition { - OperationDefinition::Query(Query { name, position, .. }) => { - name.as_ref().map(|name| (name, position)) - } - OperationDefinition::Mutation(Mutation { name, position, .. }) => { - name.as_ref().map(|name| (name, position)) - } - OperationDefinition::Subscription(Subscription { name, position, .. }) => { - name.as_ref().map(|name| (name, position)) - } + let name = match &operation_definition.node { + OperationDefinition::Query(Spanned { + node: Query { name, .. }, + .. + }) => name.as_ref(), + OperationDefinition::Mutation(Spanned { + node: Mutation { name, .. }, + .. + }) => name.as_ref(), + OperationDefinition::Subscription(Spanned { + node: Subscription { name, .. }, + .. + }) => name.as_ref(), OperationDefinition::SelectionSet(_) => None, }; - if let Some((name, pos)) = name { + if let Some(name) = name { if !self.names.insert(name.as_str()) { ctx.report_error( - vec![*pos], + vec![name.position()], format!("There can only be one operation named \"{}\"", name), ) } diff --git a/src/validation/rules/unique_variable_names.rs b/src/validation/rules/unique_variable_names.rs index 87680dca..b8bfeda0 100644 --- a/src/validation/rules/unique_variable_names.rs +++ b/src/validation/rules/unique_variable_names.rs @@ -1,5 +1,6 @@ +use crate::parser::ast::{OperationDefinition, VariableDefinition}; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::{OperationDefinition, VariableDefinition}; +use crate::Spanned; use std::collections::HashSet; #[derive(Default)] @@ -11,7 +12,7 @@ impl<'a> Visitor<'a> for UniqueVariableNames<'a> { fn enter_operation_definition( &mut self, _ctx: &mut VisitorContext<'a>, - _operation_definition: &'a OperationDefinition, + _operation_definition: &'a Spanned, ) { self.names.clear(); } @@ -19,11 +20,11 @@ impl<'a> Visitor<'a> for UniqueVariableNames<'a> { fn enter_variable_definition( &mut self, ctx: &mut VisitorContext<'a>, - variable_definition: &'a VariableDefinition, + variable_definition: &'a Spanned, ) { if !self.names.insert(variable_definition.name.as_str()) { ctx.report_error( - vec![variable_definition.position], + vec![variable_definition.position()], format!( "There can only be one variable named \"${}\"", variable_definition.name diff --git a/src/validation/rules/upload_file.rs b/src/validation/rules/upload_file.rs index f07c34c0..74263443 100644 --- a/src/validation/rules/upload_file.rs +++ b/src/validation/rules/upload_file.rs @@ -1,5 +1,6 @@ +use crate::parser::ast::OperationDefinition; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::OperationDefinition; +use crate::Spanned; #[derive(Default)] pub struct UploadFile; @@ -8,25 +9,25 @@ impl<'a> Visitor<'a> for UploadFile { fn enter_operation_definition( &mut self, ctx: &mut VisitorContext<'a>, - operation_definition: &'a OperationDefinition, + operation_definition: &'a Spanned, ) { - if let OperationDefinition::Query(query) = operation_definition { + 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], + vec![var.position()], "The Upload type is only allowed to be defined on a mutation", ); } } } - } else if let OperationDefinition::Subscription(subscription) = operation_definition { + } 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], + vec![var.position()], "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 d5e7d65d..c2efdfcf 100644 --- a/src/validation/rules/variables_are_input_types.rs +++ b/src/validation/rules/variables_are_input_types.rs @@ -1,5 +1,6 @@ +use crate::parser::ast::VariableDefinition; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::VariableDefinition; +use crate::Spanned; #[derive(Default)] pub struct VariablesAreInputTypes; @@ -8,7 +9,7 @@ impl<'a> Visitor<'a> for VariablesAreInputTypes { fn enter_variable_definition( &mut self, ctx: &mut VisitorContext<'a>, - variable_definition: &'a VariableDefinition, + variable_definition: &'a Spanned, ) { if let Some(ty) = ctx .registry @@ -16,7 +17,7 @@ impl<'a> Visitor<'a> for VariablesAreInputTypes { { if !ty.is_input() { ctx.report_error( - vec![variable_definition.position], + vec![variable_definition.position()], format!( "Variable \"{}\" cannot be of non-input type \"{}\"", &variable_definition.name, diff --git a/src/validation/rules/variables_in_allowed_position.rs b/src/validation/rules/variables_in_allowed_position.rs index f8f36ae2..4a59a35e 100644 --- a/src/validation/rules/variables_in_allowed_position.rs +++ b/src/validation/rules/variables_in_allowed_position.rs @@ -1,18 +1,17 @@ +use crate::parser::ast::{ + Document, FragmentDefinition, FragmentSpread, OperationDefinition, Type, VariableDefinition, +}; use crate::registry::TypeName; use crate::validation::utils::{operation_name, Scope}; use crate::validation::visitor::{Visitor, VisitorContext}; -use crate::Value; -use graphql_parser::query::{ - Document, FragmentDefinition, FragmentSpread, OperationDefinition, Type, VariableDefinition, -}; -use graphql_parser::Pos; +use crate::{Pos, Spanned, Value}; use std::collections::{HashMap, HashSet}; #[derive(Default)] pub struct VariableInAllowedPosition<'a> { spreads: HashMap, HashSet<&'a str>>, variable_usages: HashMap, Vec<(&'a str, Pos, TypeName<'a>)>>, - variable_defs: HashMap, Vec<(Pos, &'a VariableDefinition)>>, + variable_defs: HashMap, Vec<&'a Spanned>>, current_scope: Option>, } @@ -20,7 +19,7 @@ impl<'a> VariableInAllowedPosition<'a> { fn collect_incorrect_usages( &self, from: &Scope<'a>, - var_defs: &[(Pos, &'a VariableDefinition)], + var_defs: &[&'a Spanned], ctx: &mut VisitorContext<'a>, visited: &mut HashSet>, ) { @@ -32,18 +31,16 @@ 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_pos, var_def)) = - var_defs.iter().find(|(_, def)| def.name == *var_name) - { - let expected_type = match (&var_def.default_value, &var_def.var_type) { - (Some(_), Type::ListType(_)) => var_def.var_type.to_string() + "!", - (Some(_), Type::NamedType(_)) => var_def.var_type.to_string() + "!", - (_, _) => var_def.var_type.to_string(), + if let Some(def) = var_defs.iter().find(|def| def.name.as_str() == *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 !var_type.is_subtype(&TypeName::create(&expected_type)) { ctx.report_error( - vec![*def_pos, *usage_pos], + vec![def.position(), *usage_pos], format!( "Variable \"{}\" of type \"{}\" used in position expecting type \"{}\"", var_name, var_type, expected_type @@ -72,7 +69,7 @@ impl<'a> Visitor<'a> for VariableInAllowedPosition<'a> { fn enter_operation_definition( &mut self, _ctx: &mut VisitorContext<'a>, - operation_definition: &'a OperationDefinition, + operation_definition: &'a Spanned, ) { let (op_name, _) = operation_name(operation_definition); self.current_scope = Some(Scope::Operation(op_name)); @@ -81,7 +78,7 @@ impl<'a> Visitor<'a> for VariableInAllowedPosition<'a> { fn enter_fragment_definition( &mut self, _ctx: &mut VisitorContext<'a>, - fragment_definition: &'a FragmentDefinition, + fragment_definition: &'a Spanned, ) { self.current_scope = Some(Scope::Fragment(fragment_definition.name.as_str())); } @@ -89,20 +86,20 @@ impl<'a> Visitor<'a> for VariableInAllowedPosition<'a> { fn enter_variable_definition( &mut self, _ctx: &mut VisitorContext<'a>, - variable_definition: &'a VariableDefinition, + variable_definition: &'a Spanned, ) { if let Some(ref scope) = self.current_scope { self.variable_defs .entry(scope.clone()) .or_insert_with(Vec::new) - .push((variable_definition.position, variable_definition)); + .push(variable_definition); } } fn enter_fragment_spread( &mut self, _ctx: &mut VisitorContext<'a>, - fragment_spread: &'a FragmentSpread, + fragment_spread: &'a Spanned, ) { if let Some(ref scope) = self.current_scope { self.spreads diff --git a/src/validation/test_harness.rs b/src/validation/test_harness.rs index 4a84074f..ee0f0bb4 100644 --- a/src/validation/test_harness.rs +++ b/src/validation/test_harness.rs @@ -2,9 +2,9 @@ #![allow(dead_code)] #![allow(unreachable_code)] +use crate::parser::parse_query; use crate::validation::visitor::{visit, Visitor, VisitorContext}; use crate::*; -use graphql_parser::parse_query; #[InputObject(internal)] struct TestInput { diff --git a/src/validation/utils.rs b/src/validation/utils.rs index ede96259..db02dd00 100644 --- a/src/validation/utils.rs +++ b/src/validation/utils.rs @@ -1,6 +1,6 @@ use crate::context::QueryPathNode; +use crate::parser::ast::OperationDefinition; use crate::{registry, Pos, QueryPathSegment, Value}; -use graphql_parser::query::OperationDefinition; use std::collections::HashSet; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -36,12 +36,18 @@ 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.span.0), - OperationDefinition::Query(query) => (query.name.as_deref(), query.position), - OperationDefinition::Mutation(mutation) => (mutation.name.as_deref(), mutation.position), - OperationDefinition::Subscription(subscription) => { - (subscription.name.as_deref(), subscription.position) + 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(), + ), } } diff --git a/src/validation/visitor.rs b/src/validation/visitor.rs index 3d1367ca..ce482052 100644 --- a/src/validation/visitor.rs +++ b/src/validation/visitor.rs @@ -1,11 +1,10 @@ use crate::error::RuleError; -use crate::registry; -use crate::registry::{Type, TypeName}; -use graphql_parser::query::{ +use crate::parser::ast::{ Definition, Directive, Document, Field, FragmentDefinition, FragmentSpread, InlineFragment, - OperationDefinition, Selection, SelectionSet, TypeCondition, Value, VariableDefinition, + OperationDefinition, Selection, SelectionSet, TypeCondition, VariableDefinition, }; -use graphql_parser::Pos; +use crate::registry::{self, Type, TypeName}; +use crate::{Pos, Spanned, Value}; use std::collections::HashMap; pub struct VisitorContext<'a> { @@ -13,7 +12,7 @@ pub struct VisitorContext<'a> { pub errors: Vec, type_stack: Vec>, input_type: Vec>>, - fragments: HashMap<&'a str, &'a FragmentDefinition>, + fragments: HashMap<&'a str, &'a Spanned>, } impl<'a> VisitorContext<'a> { @@ -26,7 +25,7 @@ impl<'a> VisitorContext<'a> { fragments: doc .definitions .iter() - .filter_map(|d| match d { + .filter_map(|d| match &d.node { Definition::Fragment(fragment) => Some((fragment.name.as_str(), fragment)), _ => None, }) @@ -84,7 +83,7 @@ impl<'a> VisitorContext<'a> { self.fragments.contains_key(name) } - pub fn fragment(&self, name: &str) -> Option<&'a FragmentDefinition> { + pub fn fragment(&self, name: &str) -> Option<&'a Spanned> { self.fragments.get(name).copied() } } @@ -96,104 +95,122 @@ pub trait Visitor<'a> { fn enter_operation_definition( &mut self, _ctx: &mut VisitorContext<'a>, - _operation_definition: &'a OperationDefinition, + _operation_definition: &'a Spanned, ) { } fn exit_operation_definition( &mut self, _ctx: &mut VisitorContext<'a>, - _operation_definition: &'a OperationDefinition, + _operation_definition: &'a Spanned, ) { } fn enter_fragment_definition( &mut self, _ctx: &mut VisitorContext<'a>, - _fragment_definition: &'a FragmentDefinition, + _fragment_definition: &'a Spanned, ) { } fn exit_fragment_definition( &mut self, _ctx: &mut VisitorContext<'a>, - _fragment_definition: &'a FragmentDefinition, + _fragment_definition: &'a Spanned, ) { } fn enter_variable_definition( &mut self, _ctx: &mut VisitorContext<'a>, - _variable_definition: &'a VariableDefinition, + _variable_definition: &'a Spanned, ) { } fn exit_variable_definition( &mut self, _ctx: &mut VisitorContext<'a>, - _variable_definition: &'a VariableDefinition, + _variable_definition: &'a Spanned, ) { } - fn enter_directive(&mut self, _ctx: &mut VisitorContext<'a>, _directive: &'a Directive) {} - fn exit_directive(&mut self, _ctx: &mut VisitorContext<'a>, _directive: &'a Directive) {} + fn enter_directive( + &mut self, + _ctx: &mut VisitorContext<'a>, + _directive: &'a Spanned, + ) { + } + fn exit_directive( + &mut self, + _ctx: &mut VisitorContext<'a>, + _directive: &'a Spanned, + ) { + } fn enter_argument( &mut self, _ctx: &mut VisitorContext<'a>, - _pos: Pos, - _name: &'a str, - _value: &'a Value, + _name: &'a Spanned, + _value: &'a Spanned, ) { } fn exit_argument( &mut self, _ctx: &mut VisitorContext<'a>, - _pos: Pos, - _name: &'a str, - _value: &'a Value, + _name: &'a Spanned, + _value: &'a Spanned, ) { } fn enter_selection_set( &mut self, _ctx: &mut VisitorContext<'a>, - _selection_set: &'a SelectionSet, + _selection_set: &'a Spanned, ) { } fn exit_selection_set( &mut self, _ctx: &mut VisitorContext<'a>, - _selection_set: &'a SelectionSet, + _selection_set: &'a Spanned, ) { } - fn enter_selection(&mut self, _ctx: &mut VisitorContext<'a>, _selection: &'a Selection) {} - fn exit_selection(&mut self, _ctx: &mut VisitorContext<'a>, _selection: &'a Selection) {} + fn enter_selection( + &mut self, + _ctx: &mut VisitorContext<'a>, + _selection: &'a Spanned, + ) { + } + fn exit_selection( + &mut self, + _ctx: &mut VisitorContext<'a>, + _selection: &'a Spanned, + ) { + } - fn enter_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Field) {} - fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Field) {} + fn enter_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Spanned) {} + fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Spanned) {} fn enter_fragment_spread( &mut self, _ctx: &mut VisitorContext<'a>, - _fragment_spread: &'a FragmentSpread, + _fragment_spread: &'a Spanned, ) { } fn exit_fragment_spread( &mut self, _ctx: &mut VisitorContext<'a>, - _fragment_spread: &'a FragmentSpread, + _fragment_spread: &'a Spanned, ) { } fn enter_inline_fragment( &mut self, _ctx: &mut VisitorContext<'a>, - _inline_fragment: &'a InlineFragment, + _inline_fragment: &'a Spanned, ) { } fn exit_inline_fragment( &mut self, _ctx: &mut VisitorContext<'a>, - _inline_fragment: &'a InlineFragment, + _inline_fragment: &'a Spanned, ) { } @@ -251,7 +268,7 @@ where fn enter_operation_definition( &mut self, ctx: &mut VisitorContext<'a>, - operation_definition: &'a OperationDefinition, + operation_definition: &'a Spanned, ) { self.0.enter_operation_definition(ctx, operation_definition); self.1.enter_operation_definition(ctx, operation_definition); @@ -260,7 +277,7 @@ where fn exit_operation_definition( &mut self, ctx: &mut VisitorContext<'a>, - operation_definition: &'a OperationDefinition, + operation_definition: &'a Spanned, ) { self.0.exit_operation_definition(ctx, operation_definition); self.1.exit_operation_definition(ctx, operation_definition); @@ -269,7 +286,7 @@ where fn enter_fragment_definition( &mut self, ctx: &mut VisitorContext<'a>, - fragment_definition: &'a FragmentDefinition, + fragment_definition: &'a Spanned, ) { self.0.enter_fragment_definition(ctx, fragment_definition); self.1.enter_fragment_definition(ctx, fragment_definition); @@ -278,7 +295,7 @@ where fn exit_fragment_definition( &mut self, ctx: &mut VisitorContext<'a>, - fragment_definition: &'a FragmentDefinition, + fragment_definition: &'a Spanned, ) { self.0.exit_fragment_definition(ctx, fragment_definition); self.1.exit_fragment_definition(ctx, fragment_definition); @@ -287,7 +304,7 @@ where fn enter_variable_definition( &mut self, ctx: &mut VisitorContext<'a>, - variable_definition: &'a VariableDefinition, + variable_definition: &'a Spanned, ) { self.0.enter_variable_definition(ctx, variable_definition); self.1.enter_variable_definition(ctx, variable_definition); @@ -296,18 +313,18 @@ where fn exit_variable_definition( &mut self, ctx: &mut VisitorContext<'a>, - variable_definition: &'a VariableDefinition, + variable_definition: &'a Spanned, ) { self.0.exit_variable_definition(ctx, variable_definition); self.1.exit_variable_definition(ctx, variable_definition); } - fn enter_directive(&mut self, ctx: &mut VisitorContext<'a>, directive: &'a Directive) { + fn enter_directive(&mut self, ctx: &mut VisitorContext<'a>, directive: &'a Spanned) { self.0.enter_directive(ctx, directive); self.1.enter_directive(ctx, directive); } - fn exit_directive(&mut self, ctx: &mut VisitorContext<'a>, directive: &'a Directive) { + fn exit_directive(&mut self, ctx: &mut VisitorContext<'a>, directive: &'a Spanned) { self.0.exit_directive(ctx, directive); self.1.exit_directive(ctx, directive); } @@ -315,29 +332,27 @@ where fn enter_argument( &mut self, ctx: &mut VisitorContext<'a>, - pos: Pos, - name: &'a str, - value: &'a Value, + name: &'a Spanned, + value: &'a Spanned, ) { - self.0.enter_argument(ctx, pos, name, value); - self.1.enter_argument(ctx, pos, name, value); + self.0.enter_argument(ctx, name, value); + self.1.enter_argument(ctx, name, value); } fn exit_argument( &mut self, ctx: &mut VisitorContext<'a>, - pos: Pos, - name: &'a str, - value: &'a Value, + name: &'a Spanned, + value: &'a Spanned, ) { - self.0.exit_argument(ctx, pos, name, value); - self.1.exit_argument(ctx, pos, name, value); + self.0.exit_argument(ctx, name, value); + self.1.exit_argument(ctx, name, value); } fn enter_selection_set( &mut self, ctx: &mut VisitorContext<'a>, - selection_set: &'a SelectionSet, + selection_set: &'a Spanned, ) { self.0.enter_selection_set(ctx, selection_set); self.1.enter_selection_set(ctx, selection_set); @@ -346,28 +361,28 @@ where fn exit_selection_set( &mut self, ctx: &mut VisitorContext<'a>, - selection_set: &'a SelectionSet, + selection_set: &'a Spanned, ) { self.0.exit_selection_set(ctx, selection_set); self.1.exit_selection_set(ctx, selection_set); } - fn enter_selection(&mut self, ctx: &mut VisitorContext<'a>, selection: &'a Selection) { + fn enter_selection(&mut self, ctx: &mut VisitorContext<'a>, selection: &'a Spanned) { self.0.enter_selection(ctx, selection); self.1.enter_selection(ctx, selection); } - fn exit_selection(&mut self, ctx: &mut VisitorContext<'a>, selection: &'a Selection) { + fn exit_selection(&mut self, ctx: &mut VisitorContext<'a>, selection: &'a Spanned) { self.0.exit_selection(ctx, selection); self.1.exit_selection(ctx, selection); } - fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Field) { + fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Spanned) { self.0.enter_field(ctx, field); self.1.enter_field(ctx, field); } - fn exit_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Field) { + fn exit_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Spanned) { self.0.exit_field(ctx, field); self.1.exit_field(ctx, field); } @@ -375,7 +390,7 @@ where fn enter_fragment_spread( &mut self, ctx: &mut VisitorContext<'a>, - fragment_spread: &'a FragmentSpread, + fragment_spread: &'a Spanned, ) { self.0.enter_fragment_spread(ctx, fragment_spread); self.1.enter_fragment_spread(ctx, fragment_spread); @@ -384,7 +399,7 @@ where fn exit_fragment_spread( &mut self, ctx: &mut VisitorContext<'a>, - fragment_spread: &'a FragmentSpread, + fragment_spread: &'a Spanned, ) { self.0.exit_fragment_spread(ctx, fragment_spread); self.1.exit_fragment_spread(ctx, fragment_spread); @@ -393,7 +408,7 @@ where fn enter_inline_fragment( &mut self, ctx: &mut VisitorContext<'a>, - inline_fragment: &'a InlineFragment, + inline_fragment: &'a Spanned, ) { self.0.enter_inline_fragment(ctx, inline_fragment); self.1.enter_inline_fragment(ctx, inline_fragment); @@ -402,7 +417,7 @@ where fn exit_inline_fragment( &mut self, ctx: &mut VisitorContext<'a>, - inline_fragment: &'a InlineFragment, + inline_fragment: &'a Spanned, ) { self.0.exit_inline_fragment(ctx, inline_fragment); self.1.exit_inline_fragment(ctx, inline_fragment); @@ -421,13 +436,13 @@ fn visit_definitions<'a, V: Visitor<'a>>( doc: &'a Document, ) { for d in &doc.definitions { - match d { + match &d.node { Definition::Operation(operation) => { visit_operation_definition(v, ctx, operation); } Definition::Fragment(fragment) => { - let TypeCondition::On(name) = &fragment.type_condition; - ctx.with_type(ctx.registry.types.get(name), |ctx| { + let TypeCondition::On(name) = &fragment.type_condition.node; + ctx.with_type(ctx.registry.types.get(name.as_str()), |ctx| { visit_fragment_definition(v, ctx, fragment) }); } @@ -438,10 +453,10 @@ fn visit_definitions<'a, V: Visitor<'a>>( fn visit_operation_definition<'a, V: Visitor<'a>>( v: &mut V, ctx: &mut VisitorContext<'a>, - operation: &'a OperationDefinition, + operation: &'a Spanned, ) { v.enter_operation_definition(ctx, operation); - match 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) @@ -463,7 +478,7 @@ fn visit_operation_definition<'a, V: Visitor<'a>>( }); } else { ctx.report_error( - vec![mutation.position], + vec![mutation.position()], "Schema is not configured for mutations.", ); } @@ -477,7 +492,7 @@ fn visit_operation_definition<'a, V: Visitor<'a>>( }); } else { ctx.report_error( - vec![subscription.position], + vec![subscription.position()], "Schema is not configured for subscriptions.", ); } @@ -489,7 +504,7 @@ fn visit_operation_definition<'a, V: Visitor<'a>>( fn visit_selection_set<'a, V: Visitor<'a>>( v: &mut V, ctx: &mut VisitorContext<'a>, - selection_set: &'a SelectionSet, + selection_set: &'a Spanned, ) { if !selection_set.items.is_empty() { v.enter_selection_set(ctx, selection_set); @@ -503,12 +518,12 @@ fn visit_selection_set<'a, V: Visitor<'a>>( fn visit_selection<'a, V: Visitor<'a>>( v: &mut V, ctx: &mut VisitorContext<'a>, - selection: &'a Selection, + selection: &'a Spanned, ) { v.enter_selection(ctx, selection); - match selection { + match &selection.node { Selection::Field(field) => { - if field.name != "__typename" { + if field.name.as_str() != "__typename" { ctx.with_type( ctx.current_type() .and_then(|ty| ty.field_by_name(&field.name)) @@ -525,8 +540,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 { - ctx.with_type(ctx.registry.types.get(name), |ctx| { + if let Some(TypeCondition::On(name)) = + &inline_fragment.type_condition.as_ref().map(|c| &c.node) + { + ctx.with_type(ctx.registry.types.get(name.as_str()), |ctx| { visit_inline_fragment(v, ctx, inline_fragment) }); } @@ -535,20 +552,24 @@ fn visit_selection<'a, V: Visitor<'a>>( v.exit_selection(ctx, selection); } -fn visit_field<'a, V: Visitor<'a>>(v: &mut V, ctx: &mut VisitorContext<'a>, field: &'a Field) { +fn visit_field<'a, V: Visitor<'a>>( + v: &mut V, + ctx: &mut VisitorContext<'a>, + field: &'a Spanned, +) { v.enter_field(ctx, field); for (name, value) in &field.arguments { - v.enter_argument(ctx, field.position, name, value); + 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())) .map(|input_ty| TypeName::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.position(), expected_ty, value) }); - v.exit_argument(ctx, field.position, name, value); + v.exit_argument(ctx, name, value); } visit_directives(v, ctx, &field.directives); @@ -611,7 +632,7 @@ fn visit_input_value<'a, V: Visitor<'a>>( fn visit_variable_definitions<'a, V: Visitor<'a>>( v: &mut V, ctx: &mut VisitorContext<'a>, - variable_definitions: &'a [VariableDefinition], + variable_definitions: &'a [Spanned], ) { for d in variable_definitions { v.enter_variable_definition(ctx, d); @@ -622,22 +643,22 @@ fn visit_variable_definitions<'a, V: Visitor<'a>>( fn visit_directives<'a, V: Visitor<'a>>( v: &mut V, ctx: &mut VisitorContext<'a>, - directives: &'a [Directive], + directives: &'a [Spanned], ) { for d in directives { v.enter_directive(ctx, d); - let schema_directive = ctx.registry.directives.get(&d.name); + let schema_directive = ctx.registry.directives.get(d.name.as_str()); for (name, value) in &d.arguments { - v.enter_argument(ctx, d.position, name, value); + v.enter_argument(ctx, name, value); let expected_ty = schema_directive .and_then(|schema_directive| schema_directive.args.get(name.as_str())) .map(|input_ty| TypeName::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.position(), expected_ty, value) }); - v.exit_argument(ctx, d.position, name, value); + v.exit_argument(ctx, name, value); } v.exit_directive(ctx, d); @@ -647,7 +668,7 @@ fn visit_directives<'a, V: Visitor<'a>>( fn visit_fragment_definition<'a, V: Visitor<'a>>( v: &mut V, ctx: &mut VisitorContext<'a>, - fragment: &'a FragmentDefinition, + fragment: &'a Spanned, ) { v.enter_fragment_definition(ctx, fragment); visit_directives(v, ctx, &fragment.directives); @@ -658,7 +679,7 @@ fn visit_fragment_definition<'a, V: Visitor<'a>>( fn visit_fragment_spread<'a, V: Visitor<'a>>( v: &mut V, ctx: &mut VisitorContext<'a>, - fragment_spread: &'a FragmentSpread, + fragment_spread: &'a Spanned, ) { v.enter_fragment_spread(ctx, fragment_spread); visit_directives(v, ctx, &fragment_spread.directives); @@ -668,7 +689,7 @@ fn visit_fragment_spread<'a, V: Visitor<'a>>( fn visit_inline_fragment<'a, V: Visitor<'a>>( v: &mut V, ctx: &mut VisitorContext<'a>, - inline_fragment: &'a InlineFragment, + inline_fragment: &'a Spanned, ) { v.enter_inline_fragment(ctx, inline_fragment); visit_directives(v, ctx, &inline_fragment.directives); diff --git a/src/validation/visitors/cache_control.rs b/src/validation/visitors/cache_control.rs index 6edc427f..2d48549a 100644 --- a/src/validation/visitors/cache_control.rs +++ b/src/validation/visitors/cache_control.rs @@ -1,14 +1,18 @@ +use crate::parser::ast::{Field, SelectionSet}; use crate::registry::Type; use crate::validation::visitor::{Visitor, VisitorContext}; -use crate::CacheControl; -use graphql_parser::query::{Field, SelectionSet}; +use crate::{CacheControl, Spanned}; pub struct CacheControlCalculate<'a> { pub cache_control: &'a mut CacheControl, } impl<'ctx, 'a> Visitor<'ctx> for CacheControlCalculate<'a> { - fn enter_selection_set(&mut self, ctx: &mut VisitorContext<'_>, _selection_set: &SelectionSet) { + fn enter_selection_set( + &mut self, + ctx: &mut VisitorContext<'_>, + _selection_set: &Spanned, + ) { if let Some(current_type) = ctx.current_type() { if let Type::Object { cache_control, .. } = current_type { self.cache_control.merge(cache_control); @@ -16,7 +20,7 @@ impl<'ctx, 'a> Visitor<'ctx> for CacheControlCalculate<'a> { } } - fn enter_field(&mut self, ctx: &mut VisitorContext<'_>, field: &Field) { + fn enter_field(&mut self, ctx: &mut VisitorContext<'_>, field: &Spanned) { if let Some(registry_field) = ctx .parent_type() .and_then(|parent| parent.field_by_name(&field.name)) diff --git a/src/validation/visitors/complexity.rs b/src/validation/visitors/complexity.rs index 3508c22f..e09f14d4 100644 --- a/src/validation/visitors/complexity.rs +++ b/src/validation/visitors/complexity.rs @@ -1,12 +1,13 @@ +use crate::parser::ast::Field; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::Field; +use crate::Spanned; pub struct ComplexityCalculate<'a> { pub complexity: &'a mut usize, } impl<'ctx, 'a> Visitor<'ctx> for ComplexityCalculate<'a> { - fn enter_field(&mut self, _ctx: &mut VisitorContext<'_>, _field: &Field) { + fn enter_field(&mut self, _ctx: &mut VisitorContext<'_>, _field: &Spanned) { *self.complexity += 1; } } diff --git a/src/validation/visitors/depth.rs b/src/validation/visitors/depth.rs index 9f9c06ba..5927c2d2 100644 --- a/src/validation/visitors/depth.rs +++ b/src/validation/visitors/depth.rs @@ -1,5 +1,6 @@ +use crate::parser::ast::{FragmentSpread, InlineFragment, SelectionSet}; use crate::validation::visitor::{Visitor, VisitorContext}; -use graphql_parser::query::{FragmentSpread, InlineFragment, SelectionSet}; +use crate::Spanned; pub struct DepthCalculate<'a> { max_depth: &'a mut i32, @@ -20,7 +21,7 @@ impl<'ctx, 'a> Visitor<'ctx> for DepthCalculate<'a> { fn enter_selection_set( &mut self, _ctx: &mut VisitorContext<'ctx>, - _selection_set: &'ctx SelectionSet, + _selection_set: &'ctx Spanned, ) { self.current_depth += 1; *self.max_depth = (*self.max_depth).max(self.current_depth); @@ -29,7 +30,7 @@ impl<'ctx, 'a> Visitor<'ctx> for DepthCalculate<'a> { fn exit_selection_set( &mut self, _ctx: &mut VisitorContext<'ctx>, - _selection_set: &'ctx SelectionSet, + _selection_set: &'ctx Spanned, ) { self.current_depth -= 1; } @@ -37,7 +38,7 @@ impl<'ctx, 'a> Visitor<'ctx> for DepthCalculate<'a> { fn enter_fragment_spread( &mut self, _ctx: &mut VisitorContext<'ctx>, - _fragment_spread: &'ctx FragmentSpread, + _fragment_spread: &'ctx Spanned, ) { self.current_depth -= 1; } @@ -45,7 +46,7 @@ impl<'ctx, 'a> Visitor<'ctx> for DepthCalculate<'a> { fn exit_fragment_spread( &mut self, _ctx: &mut VisitorContext<'ctx>, - _fragment_spread: &'ctx FragmentSpread, + _fragment_spread: &'ctx Spanned, ) { self.current_depth += 1; } @@ -53,7 +54,7 @@ impl<'ctx, 'a> Visitor<'ctx> for DepthCalculate<'a> { fn enter_inline_fragment( &mut self, _ctx: &mut VisitorContext<'ctx>, - _inline_fragment: &'ctx InlineFragment, + _inline_fragment: &'ctx Spanned, ) { self.current_depth -= 1; } @@ -61,7 +62,7 @@ impl<'ctx, 'a> Visitor<'ctx> for DepthCalculate<'a> { fn exit_inline_fragment( &mut self, _ctx: &mut VisitorContext<'ctx>, - _inline_fragment: &'ctx InlineFragment, + _inline_fragment: &'ctx Spanned, ) { self.current_depth += 1; } diff --git a/src/validators/int_validators.rs b/src/validators/int_validators.rs index 80adaff7..ec3d936a 100644 --- a/src/validators/int_validators.rs +++ b/src/validators/int_validators.rs @@ -1,5 +1,5 @@ use crate::validators::InputValueValidator; -use graphql_parser::query::Value; +use crate::Value; /// Integer range validator pub struct IntRange { @@ -13,12 +13,10 @@ pub struct IntRange { impl InputValueValidator for IntRange { fn is_valid(&self, value: &Value) -> Option { if let Value::Int(n) = value { - if n.as_i64().unwrap() < self.min || n.as_i64().unwrap() > self.max { + if *n < self.min || *n > self.max { Some(format!( "the value is {}, but the range must be between {} and {}", - n.as_i64().unwrap(), - self.min, - self.max + *n, self.min, self.max )) } else { None @@ -38,11 +36,10 @@ pub struct IntLessThan { impl InputValueValidator for IntLessThan { fn is_valid(&self, value: &Value) -> Option { if let Value::Int(n) = value { - if n.as_i64().unwrap() >= self.value { + if *n >= self.value { Some(format!( "the value is {}, must be less than {}", - n.as_i64().unwrap(), - self.value + *n, self.value )) } else { None @@ -62,11 +59,10 @@ pub struct IntGreaterThan { impl InputValueValidator for IntGreaterThan { fn is_valid(&self, value: &Value) -> Option { if let Value::Int(n) = value { - if n.as_i64().unwrap() <= self.value { + if *n <= self.value { Some(format!( "the value is {}, must be greater than {}", - n.as_i64().unwrap(), - self.value + *n, self.value )) } else { None @@ -83,11 +79,8 @@ pub struct IntNonZero {} impl InputValueValidator for IntNonZero { fn is_valid(&self, value: &Value) -> Option { if let Value::Int(n) = value { - if n.as_i64().unwrap() == 0 { - Some(format!( - "the value is {}, but must be nonzero", - n.as_i64().unwrap(), - )) + if *n == 0 { + Some(format!("the value is {}, but must be nonzero", *n,)) } else { None } @@ -106,12 +99,8 @@ pub struct IntEqual { impl InputValueValidator for IntEqual { fn is_valid(&self, value: &Value) -> Option { if let Value::Int(n) = value { - if n.as_i64().unwrap() != self.value { - Some(format!( - "the value is {}, must be equal {}", - n.as_i64().unwrap(), - self.value - )) + if *n != self.value { + Some(format!("the value is {}, must be equal {}", *n, self.value)) } else { None } diff --git a/src/validators/list_validators.rs b/src/validators/list_validators.rs index ec1eb2a0..beeea22c 100644 --- a/src/validators/list_validators.rs +++ b/src/validators/list_validators.rs @@ -1,5 +1,5 @@ use crate::validators::InputValueValidator; -use graphql_parser::query::Value; +use crate::Value; /// List minimum length validator pub struct ListMinLength { diff --git a/src/validators/mod.rs b/src/validators/mod.rs index cc88f757..de09e79c 100644 --- a/src/validators/mod.rs +++ b/src/validators/mod.rs @@ -4,7 +4,7 @@ mod int_validators; mod list_validators; mod string_validators; -use graphql_parser::schema::Value; +use crate::Value; pub use int_validators::{IntEqual, IntGreaterThan, IntLessThan, IntNonZero, IntRange}; pub use list_validators::{ListMaxLength, ListMinLength}; diff --git a/src/validators/string_validators.rs b/src/validators/string_validators.rs index 46d0b0c0..79277bf7 100644 --- a/src/validators/string_validators.rs +++ b/src/validators/string_validators.rs @@ -1,5 +1,5 @@ use crate::validators::InputValueValidator; -use graphql_parser::schema::Value; +use crate::Value; use once_cell::sync::Lazy; use regex::Regex; diff --git a/tests/queries/directive_args.graphql b/tests/queries/directive_args.graphql new file mode 100644 index 00000000..5937cce5 --- /dev/null +++ b/tests/queries/directive_args.graphql @@ -0,0 +1,3 @@ +query { + node @dir(a: 1, b: "2", c: true, d: false, e: null) +} diff --git a/tests/queries/fragment.graphql b/tests/queries/fragment.graphql new file mode 100644 index 00000000..724403bc --- /dev/null +++ b/tests/queries/fragment.graphql @@ -0,0 +1,4 @@ +fragment frag on Friend { + __typename + node +} diff --git a/tests/queries/fragment_spread.graphql b/tests/queries/fragment_spread.graphql new file mode 100644 index 00000000..940fb624 --- /dev/null +++ b/tests/queries/fragment_spread.graphql @@ -0,0 +1,6 @@ +query { + node { + id + ...something + } +} diff --git a/tests/queries/inline_fragment.graphql b/tests/queries/inline_fragment.graphql new file mode 100644 index 00000000..f09fb529 --- /dev/null +++ b/tests/queries/inline_fragment.graphql @@ -0,0 +1,8 @@ +query { + node { + id + ... on User { + name + } + } +} diff --git a/tests/queries/inline_fragment_dir.graphql b/tests/queries/inline_fragment_dir.graphql new file mode 100644 index 00000000..4f9db15b --- /dev/null +++ b/tests/queries/inline_fragment_dir.graphql @@ -0,0 +1,8 @@ +query { + node { + id + ... on User @defer { + name + } + } +} diff --git a/tests/queries/kitchen-sink.graphql b/tests/queries/kitchen-sink.graphql new file mode 100644 index 00000000..ff4a05c4 --- /dev/null +++ b/tests/queries/kitchen-sink.graphql @@ -0,0 +1,55 @@ +# Copyright (c) 2015-present, Facebook, Inc. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +query queryName($foo: ComplexType, $site: Site = MOBILE) { + whoever123is: node(id: [123, 456]) { + id , + ... on User @defer { + field2 { + id , + alias: field1(first:10, after:$foo,) @include(if: $foo) { + id, + ...frag + } + } + } + ... @skip(unless: $foo) { + id + } + ... { + id + } + } +} + +mutation likeStory { + like(story: 123) @defer { + story { + id + } + } +} + +subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) { + storyLikeSubscribe(input: $input) { + story { + likers { + count + } + likeSentence { + text + } + } + } +} + +fragment frag on Friend { + foo(size: $size, bar: $b, obj: {key: "value"}) +} + +{ + unnamed(truthy: true, falsey: false, nullish: null), + query +} diff --git a/tests/queries/kitchen-sink_canonical.graphql b/tests/queries/kitchen-sink_canonical.graphql new file mode 100644 index 00000000..bad98b71 --- /dev/null +++ b/tests/queries/kitchen-sink_canonical.graphql @@ -0,0 +1,50 @@ +query queryName($foo: ComplexType, $site: Site = MOBILE) { + whoever123is: node(id: [123, 456]) { + id + ... on User @defer { + field2 { + id + alias: field1(first: 10, after: $foo) @include(if: $foo) { + id + ...frag + } + } + } + ... @skip(unless: $foo) { + id + } + ... { + id + } + } +} + +mutation likeStory { + like(story: 123) @defer { + story { + id + } + } +} + +subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) { + storyLikeSubscribe(input: $input) { + story { + likers { + count + } + likeSentence { + text + } + } + } +} + +fragment frag on Friend { + foo(size: $size, bar: $b, obj: {key: "value"}) +} + +{ + unnamed(truthy: true, falsey: false, nullish: null) + query +} diff --git a/tests/queries/minimal.graphql b/tests/queries/minimal.graphql new file mode 100644 index 00000000..e0d57a03 --- /dev/null +++ b/tests/queries/minimal.graphql @@ -0,0 +1,3 @@ +{ + a +} diff --git a/tests/queries/minimal_mutation.graphql b/tests/queries/minimal_mutation.graphql new file mode 100644 index 00000000..bf0c4eb5 --- /dev/null +++ b/tests/queries/minimal_mutation.graphql @@ -0,0 +1,3 @@ +mutation { + notify +} diff --git a/tests/queries/minimal_query.graphql b/tests/queries/minimal_query.graphql new file mode 100644 index 00000000..5017efe9 --- /dev/null +++ b/tests/queries/minimal_query.graphql @@ -0,0 +1,3 @@ +query { + node +} diff --git a/tests/queries/mutation_directive.graphql b/tests/queries/mutation_directive.graphql new file mode 100644 index 00000000..21b63216 --- /dev/null +++ b/tests/queries/mutation_directive.graphql @@ -0,0 +1,3 @@ +mutation @directive { + node +} diff --git a/tests/queries/named_query.graphql b/tests/queries/named_query.graphql new file mode 100644 index 00000000..f3f48a2c --- /dev/null +++ b/tests/queries/named_query.graphql @@ -0,0 +1,3 @@ +query Foo { + field +} diff --git a/tests/queries/nested_selection.graphql b/tests/queries/nested_selection.graphql new file mode 100644 index 00000000..defe356c --- /dev/null +++ b/tests/queries/nested_selection.graphql @@ -0,0 +1,5 @@ +query { + node { + id + } +} diff --git a/tests/queries/query_aliases.graphql b/tests/queries/query_aliases.graphql new file mode 100644 index 00000000..5069783c --- /dev/null +++ b/tests/queries/query_aliases.graphql @@ -0,0 +1,3 @@ +query { + an_alias: node +} diff --git a/tests/queries/query_arguments.graphql b/tests/queries/query_arguments.graphql new file mode 100644 index 00000000..681c3996 --- /dev/null +++ b/tests/queries/query_arguments.graphql @@ -0,0 +1,3 @@ +query { + node(id: 1) +} diff --git a/tests/queries/query_directive.graphql b/tests/queries/query_directive.graphql new file mode 100644 index 00000000..e649dc17 --- /dev/null +++ b/tests/queries/query_directive.graphql @@ -0,0 +1,3 @@ +query @directive { + node +} diff --git a/tests/queries/query_list_argument.graphql b/tests/queries/query_list_argument.graphql new file mode 100644 index 00000000..096e0c15 --- /dev/null +++ b/tests/queries/query_list_argument.graphql @@ -0,0 +1,3 @@ +query { + node(id: 1, list: [123, 456]) +} diff --git a/tests/queries/query_object_argument.graphql b/tests/queries/query_object_argument.graphql new file mode 100644 index 00000000..05379b05 --- /dev/null +++ b/tests/queries/query_object_argument.graphql @@ -0,0 +1,3 @@ +query { + node(id: 1, obj: {key1: 123, key2: 456}) +} diff --git a/tests/queries/query_var_default_float.graphql b/tests/queries/query_var_default_float.graphql new file mode 100644 index 00000000..d3e03ff5 --- /dev/null +++ b/tests/queries/query_var_default_float.graphql @@ -0,0 +1,3 @@ +query Foo($site: Float = 0.5) { + field +} diff --git a/tests/queries/query_var_default_list.graphql b/tests/queries/query_var_default_list.graphql new file mode 100644 index 00000000..7b6a0af8 --- /dev/null +++ b/tests/queries/query_var_default_list.graphql @@ -0,0 +1,3 @@ +query Foo($site: [Int] = [123, 456]) { + field +} diff --git a/tests/queries/query_var_default_object.graphql b/tests/queries/query_var_default_object.graphql new file mode 100644 index 00000000..69c6d4da --- /dev/null +++ b/tests/queries/query_var_default_object.graphql @@ -0,0 +1,3 @@ +query Foo($site: Site = {url: null}) { + field +} diff --git a/tests/queries/query_var_default_string.graphql b/tests/queries/query_var_default_string.graphql new file mode 100644 index 00000000..64bc3870 --- /dev/null +++ b/tests/queries/query_var_default_string.graphql @@ -0,0 +1,3 @@ +query Foo($site: String = "string") { + field +} diff --git a/tests/queries/query_var_defaults.graphql b/tests/queries/query_var_defaults.graphql new file mode 100644 index 00000000..91b11f8f --- /dev/null +++ b/tests/queries/query_var_defaults.graphql @@ -0,0 +1,3 @@ +query Foo($site: Site = MOBILE) { + field +} diff --git a/tests/queries/query_vars.graphql b/tests/queries/query_vars.graphql new file mode 100644 index 00000000..cb43b2b1 --- /dev/null +++ b/tests/queries/query_vars.graphql @@ -0,0 +1,3 @@ +query Foo($arg: SomeType) { + field +} diff --git a/tests/queries/string_literal.graphql b/tests/queries/string_literal.graphql new file mode 100644 index 00000000..160efb44 --- /dev/null +++ b/tests/queries/string_literal.graphql @@ -0,0 +1,3 @@ +query { + node(id: "hello") +} diff --git a/tests/queries/subscription_directive.graphql b/tests/queries/subscription_directive.graphql new file mode 100644 index 00000000..c643e53b --- /dev/null +++ b/tests/queries/subscription_directive.graphql @@ -0,0 +1,3 @@ +subscription @directive { + node +}