Merge branch 'parser'

Implement a new GraphQL query parser and remove the dependency on graphql-parser.
This commit is contained in:
sunli 2020-05-09 17:55:04 +08:00
parent f63c923521
commit dc7c8d5280
96 changed files with 1795 additions and 580 deletions

View File

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

View File

@ -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<String>,
ctx: &#crate_name::ContextSelectionSet<'a>,
futures: &mut Vec<#crate_name::BoxFieldFuture<'a>>,
) -> #crate_name::Result<()> {

View File

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

View File

@ -93,7 +93,7 @@ pub fn generate(object_args: &args::Object, input: &mut DeriveInput) -> Result<T
let ident = &item.ident;
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()))?; });
.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()))?; });
if field.is_ref {
getters.push(quote! {
@ -114,9 +114,9 @@ pub fn generate(object_args: &args::Object, input: &mut DeriveInput) -> Result<T
resolvers.push(quote! {
if ctx.name.as_str() == #field_name {
#guard
let res = self.#ident(ctx).await.map_err(|err| err.into_error_with_path(ctx.position, ctx.path_node.as_ref().unwrap().to_json()))?;
let res = self.#ident(ctx).await.map_err(|err| err.into_error_with_path(ctx.position(), ctx.path_node.as_ref().unwrap().to_json()))?;
let ctx_obj = ctx.with_selection_set(&ctx.selection_set);
return #crate_name::OutputValueType::resolve(&res, &ctx_obj, ctx.position).await;
return #crate_name::OutputValueType::resolve(&res, &ctx_obj, ctx.position()).await;
}
});
}
@ -176,9 +176,9 @@ pub fn generate(object_args: &args::Object, input: &mut DeriveInput) -> Result<T
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()))
}
}

View File

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

View File

@ -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<String>,
ctx: &#crate_name::ContextSelectionSet<'a>,
futures: &mut Vec<#crate_name::BoxFieldFuture<'a>>,
) -> #crate_name::Result<()> {

View File

@ -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<String>,
ctx: &ContextSelectionSet<'a>,
futures: &mut Vec<BoxFieldFuture<'a>>,
) -> 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<serde_json::Value> {
Err(QueryError::EntityNotFound.into_error(pos))
async fn find_entity(&self, ctx: &Context<'_>, _params: &Value) -> Result<serde_json::Value> {
Err(QueryError::EntityNotFound.into_error(ctx.position()))
}
}
@ -134,7 +129,7 @@ pub trait InputObjectType: InputValueType {}
///
/// fn parse(value: &Value) -> Option<Self> {
/// if let Value::Int(n) = value {
/// Some(MyInt(n.as_i64().unwrap() as i32))
/// Some(MyInt(*n as i32))
/// } else {
/// None
/// }

View File

@ -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<Self> {
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<SelectionSet>>;
/// Context object for resolve field
pub type Context<'a> = ContextBase<'a, &'a Field>;
pub type Context<'a> = ContextBase<'a, &'a Spanned<Field>>;
/// 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<VariableDefinition>],
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<VariableDefinition>,
pub variable_definitions: Vec<Spanned<VariableDefinition>>,
pub fragments: HashMap<String, FragmentDefinition>,
pub ctx_data: Arc<Data>,
}
@ -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<Field>) -> ContextBase<'a, &'a Spanned<Field>> {
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<SelectionSet>,
) -> ContextBase<'a, &'a Spanned<SelectionSet>> {
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<bool> {
pub fn is_skip(&self, directives: &[Spanned<Directive>]) -> Result<bool> {
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<SelectionSet>> {
#[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<SelectionSet>> {
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<Field>> {
#[doc(hidden)]
pub fn param_value<T: InputValueType, F: FnOnce() -> Value>(
&self,
name: &str,
pos: Pos,
default: F,
) -> Result<T> {
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())
}
}

View File

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

View File

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

View File

@ -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<T>`
/// Result type
pub type Result<T> = std::result::Result<T, Error>;
// internal types

View File

@ -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,
)?;

141
src/parser/ast.rs Normal file
View File

@ -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<Type>),
NonNull(Box<Type>),
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Named(name) => write!(f, "{}", name),
Type::List(ty) => write!(f, "[{}]", ty),
Type::NonNull(ty) => write!(f, "{}!", ty),
}
}
}
#[derive(Clone, Debug)]
pub struct Directive {
pub name: Spanned<String>,
pub arguments: Vec<(Spanned<String>, Spanned<Value>)>,
}
impl Directive {
pub fn get_argument(&self, name: &str) -> Option<&Spanned<Value>> {
self.arguments
.iter()
.find(|item| item.0.as_str() == name)
.map(|item| &item.1)
}
}
#[derive(Clone, Debug)]
pub struct Document {
pub definitions: Vec<Spanned<Definition>>,
}
#[derive(Clone, Debug)]
pub enum Definition {
Operation(Spanned<OperationDefinition>),
Fragment(Spanned<FragmentDefinition>),
}
#[derive(Clone, Debug)]
pub enum TypeCondition {
On(Spanned<String>),
}
#[derive(Clone, Debug)]
pub struct FragmentDefinition {
pub name: Spanned<String>,
pub type_condition: Spanned<TypeCondition>,
pub directives: Vec<Spanned<Directive>>,
pub selection_set: Spanned<SelectionSet>,
}
#[derive(Clone, Debug)]
pub enum OperationDefinition {
SelectionSet(Spanned<SelectionSet>),
Query(Spanned<Query>),
Mutation(Spanned<Mutation>),
Subscription(Spanned<Subscription>),
}
#[derive(Clone, Debug)]
pub struct Query {
pub name: Option<Spanned<String>>,
pub variable_definitions: Vec<Spanned<VariableDefinition>>,
pub directives: Vec<Spanned<Directive>>,
pub selection_set: Spanned<SelectionSet>,
}
#[derive(Clone, Debug)]
pub struct Mutation {
pub name: Option<Spanned<String>>,
pub variable_definitions: Vec<Spanned<VariableDefinition>>,
pub directives: Vec<Spanned<Directive>>,
pub selection_set: Spanned<SelectionSet>,
}
#[derive(Clone, Debug)]
pub struct Subscription {
pub name: Option<Spanned<String>>,
pub variable_definitions: Vec<Spanned<VariableDefinition>>,
pub directives: Vec<Spanned<Directive>>,
pub selection_set: Spanned<SelectionSet>,
}
#[derive(Clone, Debug, Default)]
pub struct SelectionSet {
pub items: Vec<Spanned<Selection>>,
}
#[derive(Clone, Debug)]
pub struct VariableDefinition {
pub name: Spanned<String>,
pub var_type: Spanned<Type>,
pub default_value: Option<Spanned<Value>>,
}
#[derive(Clone, Debug)]
pub enum Selection {
Field(Spanned<Field>),
FragmentSpread(Spanned<FragmentSpread>),
InlineFragment(Spanned<InlineFragment>),
}
#[derive(Clone, Debug)]
pub struct Field {
pub alias: Option<Spanned<String>>,
pub name: Spanned<String>,
pub arguments: Vec<(Spanned<String>, Spanned<Value>)>,
pub directives: Vec<Spanned<Directive>>,
pub selection_set: Spanned<SelectionSet>,
}
impl Field {
pub fn get_argument(&self, name: &str) -> Option<&Spanned<Value>> {
self.arguments
.iter()
.find(|item| item.0.as_str() == name)
.map(|item| &item.1)
}
}
#[derive(Clone, Debug)]
pub struct FragmentSpread {
pub fragment_name: Spanned<String>,
pub directives: Vec<Spanned<Directive>>,
}
#[derive(Clone, Debug)]
pub struct InlineFragment {
pub type_condition: Option<Spanned<TypeCondition>>,
pub directives: Vec<Spanned<Directive>>,
pub selection_set: Spanned<SelectionSet>,
}

8
src/parser/mod.rs Normal file
View File

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

49
src/parser/query.pest Normal file
View File

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

536
src/parser/query_parser.rs Normal file
View File

@ -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<Rule>;
pub fn parse_query<T: AsRef<str>>(input: T) -> Result<Document> {
let document_pair: Pair<Rule> = QueryParser::parse(Rule::document, input.as_ref())?
.next()
.unwrap();
let mut definitions = Vec::new();
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<Rule>) -> Result<Spanned<OperationDefinition>> {
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<Rule>) -> Result<Value> {
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::value => return Ok(parse_value(pair)?),
_ => unreachable!(),
}
}
unreachable!()
}
fn parse_type(pair: Pair<Rule>) -> Result<Type> {
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<Rule>) -> Result<Spanned<VariableDefinition>> {
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<Rule>) -> Result<Vec<Spanned<VariableDefinition>>> {
let mut vars = Vec::new();
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::variable_definition => vars.push(parse_variable_definition(pair)?),
_ => unreachable!(),
}
}
Ok(vars)
}
fn parse_directive(pair: Pair<Rule>) -> Result<Spanned<Directive>> {
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<Rule>) -> Result<Vec<Spanned<Directive>>> {
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<Rule>) -> Result<Spanned<String>> {
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<Rule>) -> Result<Value> {
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<Rule>) -> 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<Rule>) -> Result<Value> {
let mut map = BTreeMap::new();
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::pair => {
map.extend(std::iter::once(parse_object_pair(pair)?));
}
_ => unreachable!(),
}
}
Ok(Value::Object(map))
}
fn parse_array_value(pair: Pair<Rule>) -> Result<Value> {
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<Rule>) -> Result<(Spanned<String>, Spanned<Value>)> {
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<Rule>) -> Result<Vec<(Spanned<String>, Spanned<Value>)>> {
let mut arguments = Vec::new();
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::pair => arguments.extend(std::iter::once(parse_pair(pair)?)),
_ => unreachable!(),
}
}
Ok(arguments)
}
fn parse_alias(pair: Pair<Rule>) -> Result<Spanned<String>> {
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<Rule>) -> Result<Spanned<Field>> {
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<Rule>) -> Result<Spanned<FragmentSpread>> {
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<Rule>) -> Result<Spanned<TypeCondition>> {
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<Rule>) -> Result<Spanned<InlineFragment>> {
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<Rule>) -> Result<Spanned<SelectionSet>> {
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<Rule>) -> Result<Spanned<FragmentDefinition>> {
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<String> {
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();
}
}
}
}

148
src/parser/span.rs Normal file
View File

@ -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<T: ?Sized> {
pub span: Span,
pub node: T,
}
impl<T: fmt::Display> fmt::Display for Spanned<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.node.fmt(f)
}
}
impl<T: Clone> Spanned<T> {
#[inline]
#[allow(missing_docs)]
pub fn clone_inner(&self) -> T {
self.node.clone()
}
}
impl<T: PartialEq> PartialEq for Spanned<T> {
fn eq(&self, other: &Self) -> bool {
self.node.eq(&other.node)
}
}
impl<T: PartialOrd> PartialOrd for Spanned<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.node.partial_cmp(&other.node)
}
}
impl<T: Ord> Ord for Spanned<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.node.cmp(&other.node)
}
}
impl<T: Ord> Eq for Spanned<T> {}
impl<T: ?Sized> Deref for Spanned<T> {
type Target = T;
fn deref(&self) -> &T {
&self.node
}
}
impl<T: ?Sized> DerefMut for Spanned<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.node
}
}
impl<T: Hash> Hash for Spanned<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.node.hash(state)
}
}
impl Borrow<str> for Spanned<String> {
fn borrow(&self) -> &str {
self.node.as_str()
}
}
impl BorrowMut<str> for Spanned<String> {
fn borrow_mut(&mut self) -> &mut str {
self.node.as_mut_str()
}
}
impl<T> Spanned<T> {
pub(crate) fn new(node: T, pair_span: pest::Span<'_>) -> Spanned<T> {
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<F: FnOnce(Self) -> R, R>(self, f: F) -> Spanned<R> {
Spanned {
span: self.span,
node: f(self),
}
}
}

117
src/parser/value.rs Normal file
View File

@ -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<Value>),
Object(BTreeMap<String, Value>),
}
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, "}}")
}
}
}
}

View File

@ -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<SelectionSet>,
&'a [Spanned<VariableDefinition>],
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;
}

View File

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

View File

@ -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,
)?;

View File

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

View File

@ -16,7 +16,7 @@ macro_rules! impl_float_scalars {
fn parse(value: &Value) -> Option<Self> {
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
}

View File

@ -106,7 +106,7 @@ impl ScalarType for ID {
fn parse(value: &Value) -> Option<Self> {
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,
}

View File

@ -16,7 +16,7 @@ macro_rules! impl_integer_scalars {
fn parse(value: &Value) -> Option<Self> {
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<Self> {
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
}

View File

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

View File

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

View File

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

View File

@ -179,25 +179,26 @@ impl<T: OutputValueType + Send + Sync, E: ObjectType + Sync + Send> ObjectType
async fn resolve_field(&self, ctx: &Context<'_>) -> Result<serde_json::Value> {
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::<T, E>::type_name().to_string(),
},
})

View File

@ -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<serde_json::Value> {
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());
}

View File

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

View File

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

View File

@ -1,5 +1,4 @@
use crate::{Result, Type};
use graphql_parser::query::Value;
use crate::{Result, Type, Value};
#[allow(missing_docs)]
pub struct EnumItem<T> {

View File

@ -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<T: Type> Type for Vec<T> {

View File

@ -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<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
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<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
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<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
.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<Any> =
ctx.param_value("representations", ctx.position, || Value::Null)?;
let representations: Vec<Any> = 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<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
sdl: Some(ctx.registry.create_federation_sdl()),
},
&ctx_obj,
ctx.position,
ctx.position(),
)
.await;
}

View File

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

View File

@ -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<Directive>) {
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<Directive>,
) {
self.current_args = None;
}
fn enter_argument(
&mut self,
ctx: &mut VisitorContext<'a>,
pos: Pos,
name: &str,
value: &'a Value,
name: &'a Spanned<String>,
value: &'a Spanned<Value>,
) {
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<Field>) {
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<Field>) {
self.current_args = None;
}
}

View File

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

View File

@ -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<Field>) {
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,

View File

@ -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<FragmentDefinition>,
) {
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<InlineFragment>,
) {
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()

View File

@ -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<Directive>) {
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<Directive>,
) {
self.current_args = None;
}
fn enter_argument(
&mut self,
ctx: &mut VisitorContext<'a>,
pos: Pos,
name: &str,
_value: &'a Value,
name: &'a Spanned<String>,
_value: &'a Spanned<Value>,
) {
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<Field>) {
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<Field>) {
self.current_args = None;
}
}

View File

@ -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<OperationDefinition>,
) {
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<OperationDefinition>,
) {
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<FragmentDefinition>,
) {
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<FragmentDefinition>,
) {
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<Directive>) {
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<Field>) {
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<Field>) {
self.location_stack.pop();
}
fn enter_fragment_spread(
&mut self,
_ctx: &mut VisitorContext<'a>,
_fragment_spread: &'a FragmentSpread,
_fragment_spread: &'a Spanned<FragmentSpread>,
) {
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<FragmentSpread>,
) {
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<InlineFragment>,
) {
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<InlineFragment>,
) {
self.location_stack.pop();
}

View File

@ -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<FragmentSpread>,
) {
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),
);
}

View File

@ -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<FragmentDefinition>,
) {
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<VariableDefinition>,
) {
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<InlineFragment>,
) {
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());
}
}
}

View File

@ -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<OperationDefinition>,
) {
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;

View File

@ -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<FragmentDefinition>,
) {
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<FragmentDefinition>,
) {
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<FragmentSpread>,
) {
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()));
}
}
}

View File

@ -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<OperationDefinition>,
) {
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<FragmentDefinition>,
) {
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<VariableDefinition>,
) {
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<String>,
value: &'a Spanned<Value>,
) {
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<FragmentSpread>,
) {
if let Some(ref scope) = self.current_scope {
self.spreads

View File

@ -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<OperationDefinition>,
) {
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<FragmentDefinition>,
) {
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<FragmentSpread>,
) {
if let Some(ref scope) = self.current_scope {
self.spreads

View File

@ -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<OperationDefinition>,
) {
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<FragmentDefinition>,
) {
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<VariableDefinition>,
) {
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<String>,
value: &'a Spanned<Value>,
) {
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<FragmentSpread>,
) {
if let Some(ref scope) = self.current_scope {
self.spreads

View File

@ -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<SelectionSet>,
) {
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<Field>>,
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<SelectionSet>) {
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<Field>) {
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)),
}
}

View File

@ -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<FragmentSpread>,
) {
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<InlineFragment>,
) {
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 \"{}\"",

View File

@ -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<Directive>) {
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<Field>) {
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()

View File

@ -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<Field>) {
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,

View File

@ -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<Directive>,
) {
self.names.clear();
}
fn enter_argument(
&mut self,
ctx: &mut VisitorContext<'a>,
pos: Pos,
name: &'a str,
_value: &'a Value,
name: &'a Spanned<String>,
_value: &'a Spanned<Value>,
) {
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<Field>) {
self.names.clear();
}
}

View File

@ -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<FragmentDefinition>,
) {
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

View File

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

View File

@ -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<OperationDefinition>,
) {
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<VariableDefinition>,
) {
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

View File

@ -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<OperationDefinition>,
) {
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",
);
}

View File

@ -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<VariableDefinition>,
) {
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,

View File

@ -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<Scope<'a>, HashSet<&'a str>>,
variable_usages: HashMap<Scope<'a>, Vec<(&'a str, Pos, TypeName<'a>)>>,
variable_defs: HashMap<Scope<'a>, Vec<(Pos, &'a VariableDefinition)>>,
variable_defs: HashMap<Scope<'a>, Vec<&'a Spanned<VariableDefinition>>>,
current_scope: Option<Scope<'a>>,
}
@ -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<VariableDefinition>],
ctx: &mut VisitorContext<'a>,
visited: &mut HashSet<Scope<'a>>,
) {
@ -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<OperationDefinition>,
) {
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<FragmentDefinition>,
) {
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<VariableDefinition>,
) {
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<FragmentSpread>,
) {
if let Some(ref scope) = self.current_scope {
self.spreads

View File

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

View File

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

View File

@ -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<RuleError>,
type_stack: Vec<Option<&'a registry::Type>>,
input_type: Vec<Option<TypeName<'a>>>,
fragments: HashMap<&'a str, &'a FragmentDefinition>,
fragments: HashMap<&'a str, &'a Spanned<FragmentDefinition>>,
}
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<FragmentDefinition>> {
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<OperationDefinition>,
) {
}
fn exit_operation_definition(
&mut self,
_ctx: &mut VisitorContext<'a>,
_operation_definition: &'a OperationDefinition,
_operation_definition: &'a Spanned<OperationDefinition>,
) {
}
fn enter_fragment_definition(
&mut self,
_ctx: &mut VisitorContext<'a>,
_fragment_definition: &'a FragmentDefinition,
_fragment_definition: &'a Spanned<FragmentDefinition>,
) {
}
fn exit_fragment_definition(
&mut self,
_ctx: &mut VisitorContext<'a>,
_fragment_definition: &'a FragmentDefinition,
_fragment_definition: &'a Spanned<FragmentDefinition>,
) {
}
fn enter_variable_definition(
&mut self,
_ctx: &mut VisitorContext<'a>,
_variable_definition: &'a VariableDefinition,
_variable_definition: &'a Spanned<VariableDefinition>,
) {
}
fn exit_variable_definition(
&mut self,
_ctx: &mut VisitorContext<'a>,
_variable_definition: &'a VariableDefinition,
_variable_definition: &'a Spanned<VariableDefinition>,
) {
}
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<Directive>,
) {
}
fn exit_directive(
&mut self,
_ctx: &mut VisitorContext<'a>,
_directive: &'a Spanned<Directive>,
) {
}
fn enter_argument(
&mut self,
_ctx: &mut VisitorContext<'a>,
_pos: Pos,
_name: &'a str,
_value: &'a Value,
_name: &'a Spanned<String>,
_value: &'a Spanned<Value>,
) {
}
fn exit_argument(
&mut self,
_ctx: &mut VisitorContext<'a>,
_pos: Pos,
_name: &'a str,
_value: &'a Value,
_name: &'a Spanned<String>,
_value: &'a Spanned<Value>,
) {
}
fn enter_selection_set(
&mut self,
_ctx: &mut VisitorContext<'a>,
_selection_set: &'a SelectionSet,
_selection_set: &'a Spanned<SelectionSet>,
) {
}
fn exit_selection_set(
&mut self,
_ctx: &mut VisitorContext<'a>,
_selection_set: &'a SelectionSet,
_selection_set: &'a Spanned<SelectionSet>,
) {
}
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<Selection>,
) {
}
fn exit_selection(
&mut self,
_ctx: &mut VisitorContext<'a>,
_selection: &'a Spanned<Selection>,
) {
}
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<Field>) {}
fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Spanned<Field>) {}
fn enter_fragment_spread(
&mut self,
_ctx: &mut VisitorContext<'a>,
_fragment_spread: &'a FragmentSpread,
_fragment_spread: &'a Spanned<FragmentSpread>,
) {
}
fn exit_fragment_spread(
&mut self,
_ctx: &mut VisitorContext<'a>,
_fragment_spread: &'a FragmentSpread,
_fragment_spread: &'a Spanned<FragmentSpread>,
) {
}
fn enter_inline_fragment(
&mut self,
_ctx: &mut VisitorContext<'a>,
_inline_fragment: &'a InlineFragment,
_inline_fragment: &'a Spanned<InlineFragment>,
) {
}
fn exit_inline_fragment(
&mut self,
_ctx: &mut VisitorContext<'a>,
_inline_fragment: &'a InlineFragment,
_inline_fragment: &'a Spanned<InlineFragment>,
) {
}
@ -251,7 +268,7 @@ where
fn enter_operation_definition(
&mut self,
ctx: &mut VisitorContext<'a>,
operation_definition: &'a OperationDefinition,
operation_definition: &'a Spanned<OperationDefinition>,
) {
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<OperationDefinition>,
) {
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<FragmentDefinition>,
) {
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<FragmentDefinition>,
) {
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<VariableDefinition>,
) {
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<VariableDefinition>,
) {
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<Directive>) {
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<Directive>) {
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<String>,
value: &'a Spanned<Value>,
) {
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<String>,
value: &'a Spanned<Value>,
) {
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<SelectionSet>,
) {
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<SelectionSet>,
) {
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<Selection>) {
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<Selection>) {
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<Field>) {
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<Field>) {
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<FragmentSpread>,
) {
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<FragmentSpread>,
) {
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<InlineFragment>,
) {
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<InlineFragment>,
) {
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<OperationDefinition>,
) {
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<SelectionSet>,
) {
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<Selection>,
) {
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<Field>,
) {
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<VariableDefinition>],
) {
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<Directive>],
) {
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<FragmentDefinition>,
) {
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<FragmentSpread>,
) {
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<InlineFragment>,
) {
v.enter_inline_fragment(ctx, inline_fragment);
visit_directives(v, ctx, &inline_fragment.directives);

View File

@ -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<SelectionSet>,
) {
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<Field>) {
if let Some(registry_field) = ctx
.parent_type()
.and_then(|parent| parent.field_by_name(&field.name))

View File

@ -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<Field>) {
*self.complexity += 1;
}
}

View File

@ -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<SelectionSet>,
) {
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<SelectionSet>,
) {
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<FragmentSpread>,
) {
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<FragmentSpread>,
) {
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<InlineFragment>,
) {
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<InlineFragment>,
) {
self.current_depth += 1;
}

View File

@ -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<String> {
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<String> {
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<String> {
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<String> {
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<String> {
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
}

View File

@ -1,5 +1,5 @@
use crate::validators::InputValueValidator;
use graphql_parser::query::Value;
use crate::Value;
/// List minimum length validator
pub struct ListMinLength {

View File

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

View File

@ -1,5 +1,5 @@
use crate::validators::InputValueValidator;
use graphql_parser::schema::Value;
use crate::Value;
use once_cell::sync::Lazy;
use regex::Regex;

View File

@ -0,0 +1,3 @@
query {
node @dir(a: 1, b: "2", c: true, d: false, e: null)
}

View File

@ -0,0 +1,4 @@
fragment frag on Friend {
__typename
node
}

View File

@ -0,0 +1,6 @@
query {
node {
id
...something
}
}

View File

@ -0,0 +1,8 @@
query {
node {
id
... on User {
name
}
}
}

View File

@ -0,0 +1,8 @@
query {
node {
id
... on User @defer {
name
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,3 @@
{
a
}

View File

@ -0,0 +1,3 @@
mutation {
notify
}

View File

@ -0,0 +1,3 @@
query {
node
}

View File

@ -0,0 +1,3 @@
mutation @directive {
node
}

View File

@ -0,0 +1,3 @@
query Foo {
field
}

View File

@ -0,0 +1,5 @@
query {
node {
id
}
}

View File

@ -0,0 +1,3 @@
query {
an_alias: node
}

View File

@ -0,0 +1,3 @@
query {
node(id: 1)
}

View File

@ -0,0 +1,3 @@
query @directive {
node
}

View File

@ -0,0 +1,3 @@
query {
node(id: 1, list: [123, 456])
}

View File

@ -0,0 +1,3 @@
query {
node(id: 1, obj: {key1: 123, key2: 456})
}

View File

@ -0,0 +1,3 @@
query Foo($site: Float = 0.5) {
field
}

View File

@ -0,0 +1,3 @@
query Foo($site: [Int] = [123, 456]) {
field
}

View File

@ -0,0 +1,3 @@
query Foo($site: Site = {url: null}) {
field
}

View File

@ -0,0 +1,3 @@
query Foo($site: String = "string") {
field
}

View File

@ -0,0 +1,3 @@
query Foo($site: Site = MOBILE) {
field
}

View File

@ -0,0 +1,3 @@
query Foo($arg: SomeType) {
field
}

View File

@ -0,0 +1,3 @@
query {
node(id: "hello")
}

View File

@ -0,0 +1,3 @@
subscription @directive {
node
}