Merge branch 'parser'
Implement a new GraphQL query parser and remove the dependency on graphql-parser.
This commit is contained in:
parent
f63c923521
commit
dc7c8d5280
|
@ -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 }
|
||||
|
|
|
@ -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<()> {
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<()> {
|
||||
|
|
25
src/base.rs
25
src/base.rs
|
@ -1,7 +1,8 @@
|
|||
use crate::parser::Pos;
|
||||
use crate::registry::Registry;
|
||||
use crate::{registry, Context, ContextSelectionSet, FieldResult, QueryError, Result, ID};
|
||||
use graphql_parser::query::Value;
|
||||
use graphql_parser::Pos;
|
||||
use crate::{
|
||||
registry, Context, ContextSelectionSet, FieldResult, QueryError, Result, Spanned, Value, ID,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
@ -79,20 +80,19 @@ pub trait ObjectType: OutputValueType {
|
|||
/// Collect the fields with the `name` inline object
|
||||
fn collect_inline_fields<'a>(
|
||||
&'a self,
|
||||
name: &str,
|
||||
_pos: Pos,
|
||||
name: &Spanned<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
|
||||
/// }
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
21
src/error.rs
21
src/error.rs
|
@ -1,5 +1,6 @@
|
|||
use graphql_parser::query::{ParseError, Value};
|
||||
use graphql_parser::Pos;
|
||||
use crate::parser::ParseError;
|
||||
use crate::{Pos, Value};
|
||||
use pest::error::LineColLocation;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// FieldError type
|
||||
|
@ -256,22 +257,14 @@ pub struct RuleError {
|
|||
|
||||
impl From<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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -76,12 +76,15 @@
|
|||
extern crate thiserror;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate pest_derive;
|
||||
|
||||
mod base;
|
||||
mod context;
|
||||
mod error;
|
||||
mod model;
|
||||
mod mutation_resolver;
|
||||
mod parser;
|
||||
mod query;
|
||||
mod resolver;
|
||||
mod scalars;
|
||||
|
@ -101,8 +104,6 @@ pub use async_trait;
|
|||
#[doc(hidden)]
|
||||
pub use futures;
|
||||
#[doc(hidden)]
|
||||
pub use graphql_parser;
|
||||
#[doc(hidden)]
|
||||
pub use serde_json;
|
||||
|
||||
pub mod http;
|
||||
|
@ -114,8 +115,7 @@ pub use context::{
|
|||
pub use error::{
|
||||
Error, ErrorExtensions, FieldError, FieldResult, ParseRequestError, QueryError, ResultExt,
|
||||
};
|
||||
pub use graphql_parser::query::Value;
|
||||
pub use graphql_parser::Pos;
|
||||
pub use parser::{Pos, Spanned, Value};
|
||||
pub use query::{IntoQueryBuilder, IntoQueryBuilderOpts, QueryBuilder, QueryResponse};
|
||||
pub use registry::CacheControl;
|
||||
pub use scalars::{Any, Json, ID};
|
||||
|
@ -130,7 +130,7 @@ pub use types::{
|
|||
};
|
||||
pub use validation::ValidationMode;
|
||||
|
||||
/// Result type, are actually `anyhow::Result<T>`
|
||||
/// Result type
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
// internal types
|
||||
|
|
|
@ -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
141
src/parser/ast.rs
Normal 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
8
src/parser/mod.rs
Normal 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
49
src/parser/query.pest
Normal 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
536
src/parser/query_parser.rs
Normal 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
148
src/parser/span.rs
Normal 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
117
src/parser/value.rs
Normal 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, "}}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
src/query.rs
37
src/query.rs
|
@ -1,14 +1,15 @@
|
|||
use crate::context::{Data, ResolveId};
|
||||
use crate::error::ParseRequestError;
|
||||
use crate::mutation_resolver::do_mutation_resolve;
|
||||
use crate::registry::CacheControl;
|
||||
use crate::validation::{check_rules, CheckResult};
|
||||
use crate::{do_resolve, ContextBase, Error, Result, Schema};
|
||||
use crate::{ObjectType, QueryError, Variables};
|
||||
use graphql_parser::query::{
|
||||
use crate::parser::ast::{
|
||||
Definition, Document, OperationDefinition, SelectionSet, VariableDefinition,
|
||||
};
|
||||
use graphql_parser::{parse_query, Pos};
|
||||
use crate::parser::parse_query;
|
||||
use crate::registry::CacheControl;
|
||||
use crate::validation::{check_rules, CheckResult};
|
||||
use crate::{
|
||||
do_resolve, ContextBase, Error, ObjectType, Pos, QueryError, Result, Schema, Spanned, Variables,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
|
@ -181,8 +182,8 @@ impl QueryBuilder {
|
|||
})?;
|
||||
|
||||
for definition in &document.definitions {
|
||||
if let Definition::Fragment(fragment) = &definition {
|
||||
fragments.insert(fragment.name.clone(), fragment.clone());
|
||||
if let Definition::Fragment(fragment) = &definition.node {
|
||||
fragments.insert(fragment.name.clone_inner(), fragment.clone_inner());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,27 +233,34 @@ impl QueryBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn current_operation<'a>(
|
||||
document: &'a Document,
|
||||
operation_name: Option<&str>,
|
||||
) -> Option<(&'a SelectionSet, &'a [VariableDefinition], bool)> {
|
||||
) -> Option<(
|
||||
&'a Spanned<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;
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
)?;
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 \"{}\"",
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::validators::InputValueValidator;
|
||||
use graphql_parser::query::Value;
|
||||
use crate::Value;
|
||||
|
||||
/// List minimum length validator
|
||||
pub struct ListMinLength {
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::validators::InputValueValidator;
|
||||
use graphql_parser::schema::Value;
|
||||
use crate::Value;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
|
|
3
tests/queries/directive_args.graphql
Normal file
3
tests/queries/directive_args.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query {
|
||||
node @dir(a: 1, b: "2", c: true, d: false, e: null)
|
||||
}
|
4
tests/queries/fragment.graphql
Normal file
4
tests/queries/fragment.graphql
Normal file
|
@ -0,0 +1,4 @@
|
|||
fragment frag on Friend {
|
||||
__typename
|
||||
node
|
||||
}
|
6
tests/queries/fragment_spread.graphql
Normal file
6
tests/queries/fragment_spread.graphql
Normal file
|
@ -0,0 +1,6 @@
|
|||
query {
|
||||
node {
|
||||
id
|
||||
...something
|
||||
}
|
||||
}
|
8
tests/queries/inline_fragment.graphql
Normal file
8
tests/queries/inline_fragment.graphql
Normal file
|
@ -0,0 +1,8 @@
|
|||
query {
|
||||
node {
|
||||
id
|
||||
... on User {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
8
tests/queries/inline_fragment_dir.graphql
Normal file
8
tests/queries/inline_fragment_dir.graphql
Normal file
|
@ -0,0 +1,8 @@
|
|||
query {
|
||||
node {
|
||||
id
|
||||
... on User @defer {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
55
tests/queries/kitchen-sink.graphql
Normal file
55
tests/queries/kitchen-sink.graphql
Normal 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
|
||||
}
|
50
tests/queries/kitchen-sink_canonical.graphql
Normal file
50
tests/queries/kitchen-sink_canonical.graphql
Normal 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
|
||||
}
|
3
tests/queries/minimal.graphql
Normal file
3
tests/queries/minimal.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
a
|
||||
}
|
3
tests/queries/minimal_mutation.graphql
Normal file
3
tests/queries/minimal_mutation.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
mutation {
|
||||
notify
|
||||
}
|
3
tests/queries/minimal_query.graphql
Normal file
3
tests/queries/minimal_query.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query {
|
||||
node
|
||||
}
|
3
tests/queries/mutation_directive.graphql
Normal file
3
tests/queries/mutation_directive.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
mutation @directive {
|
||||
node
|
||||
}
|
3
tests/queries/named_query.graphql
Normal file
3
tests/queries/named_query.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query Foo {
|
||||
field
|
||||
}
|
5
tests/queries/nested_selection.graphql
Normal file
5
tests/queries/nested_selection.graphql
Normal file
|
@ -0,0 +1,5 @@
|
|||
query {
|
||||
node {
|
||||
id
|
||||
}
|
||||
}
|
3
tests/queries/query_aliases.graphql
Normal file
3
tests/queries/query_aliases.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query {
|
||||
an_alias: node
|
||||
}
|
3
tests/queries/query_arguments.graphql
Normal file
3
tests/queries/query_arguments.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query {
|
||||
node(id: 1)
|
||||
}
|
3
tests/queries/query_directive.graphql
Normal file
3
tests/queries/query_directive.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query @directive {
|
||||
node
|
||||
}
|
3
tests/queries/query_list_argument.graphql
Normal file
3
tests/queries/query_list_argument.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query {
|
||||
node(id: 1, list: [123, 456])
|
||||
}
|
3
tests/queries/query_object_argument.graphql
Normal file
3
tests/queries/query_object_argument.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query {
|
||||
node(id: 1, obj: {key1: 123, key2: 456})
|
||||
}
|
3
tests/queries/query_var_default_float.graphql
Normal file
3
tests/queries/query_var_default_float.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query Foo($site: Float = 0.5) {
|
||||
field
|
||||
}
|
3
tests/queries/query_var_default_list.graphql
Normal file
3
tests/queries/query_var_default_list.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query Foo($site: [Int] = [123, 456]) {
|
||||
field
|
||||
}
|
3
tests/queries/query_var_default_object.graphql
Normal file
3
tests/queries/query_var_default_object.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query Foo($site: Site = {url: null}) {
|
||||
field
|
||||
}
|
3
tests/queries/query_var_default_string.graphql
Normal file
3
tests/queries/query_var_default_string.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query Foo($site: String = "string") {
|
||||
field
|
||||
}
|
3
tests/queries/query_var_defaults.graphql
Normal file
3
tests/queries/query_var_defaults.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query Foo($site: Site = MOBILE) {
|
||||
field
|
||||
}
|
3
tests/queries/query_vars.graphql
Normal file
3
tests/queries/query_vars.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query Foo($arg: SomeType) {
|
||||
field
|
||||
}
|
3
tests/queries/string_literal.graphql
Normal file
3
tests/queries/string_literal.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
query {
|
||||
node(id: "hello")
|
||||
}
|
3
tests/queries/subscription_directive.graphql
Normal file
3
tests/queries/subscription_directive.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
subscription @directive {
|
||||
node
|
||||
}
|
Loading…
Reference in New Issue
Block a user