v0.10.2
This commit is contained in:
parent
0169fd6122
commit
3de914df7a
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-graphql"
|
name = "async-graphql"
|
||||||
version = "0.10.1"
|
version = "0.10.2"
|
||||||
authors = ["sunli <scott_s829@163.com>"]
|
authors = ["sunli <scott_s829@163.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "The GraphQL server library implemented by rust"
|
description = "The GraphQL server library implemented by rust"
|
||||||
|
@ -17,7 +17,7 @@ readme = "README.md"
|
||||||
default = ["chrono", "uuid"]
|
default = ["chrono", "uuid"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-graphql-derive = { path = "async-graphql-derive", version = "0.10.1" }
|
async-graphql-derive = { path = "async-graphql-derive", version = "0.10.2" }
|
||||||
graphql-parser = "0.2.3"
|
graphql-parser = "0.2.3"
|
||||||
anyhow = "1.0.26"
|
anyhow = "1.0.26"
|
||||||
thiserror = "1.0.11"
|
thiserror = "1.0.11"
|
||||||
|
|
10
README.md
10
README.md
|
@ -85,14 +85,14 @@
|
||||||
- [ ] Validation rules
|
- [ ] Validation rules
|
||||||
- [X] ArgumentsOfCorrectType
|
- [X] ArgumentsOfCorrectType
|
||||||
- [X] DefaultValuesOfCorrectType
|
- [X] DefaultValuesOfCorrectType
|
||||||
- [ ] FieldsOnCorrectType
|
- [X] FieldsOnCorrectType
|
||||||
- [ ] FragmentsOnCompositeTypes
|
- [X] FragmentsOnCompositeTypes
|
||||||
- [ ] KnownArgumentNames
|
- [X] KnownArgumentNames
|
||||||
- [ ] KnownDirectives
|
- [ ] KnownDirectives
|
||||||
- [ ] KnownFragmentNames
|
- [X] KnownFragmentNames
|
||||||
- [ ] KnownTypeNames
|
- [ ] KnownTypeNames
|
||||||
- [ ] LoneAnonymousOperation
|
- [ ] LoneAnonymousOperation
|
||||||
- [ ] NoFragmentCycles
|
- [X] NoFragmentCycles
|
||||||
- [ ] NoUndefinedVariables
|
- [ ] NoUndefinedVariables
|
||||||
- [ ] NoUnusedFragments
|
- [ ] NoUnusedFragments
|
||||||
- [ ] NoUnusedVariables
|
- [ ] NoUnusedVariables
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-graphql-derive"
|
name = "async-graphql-derive"
|
||||||
version = "0.10.1"
|
version = "0.10.2"
|
||||||
authors = ["sunli <scott_s829@163.com>"]
|
authors = ["sunli <scott_s829@163.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "The GraphQL server library implemented by rust"
|
description = "The GraphQL server library implemented by rust"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::model::{__EnumValue, __Field, __InputValue, __TypeKind};
|
use crate::model::{__EnumValue, __Field, __InputValue, __TypeKind};
|
||||||
use crate::registry;
|
use crate::registry;
|
||||||
use crate::registry::{Type, TypeInfo};
|
use crate::registry::{Type, TypeName};
|
||||||
use async_graphql_derive::Object;
|
use async_graphql_derive::Object;
|
||||||
|
|
||||||
enum TypeDetail<'a> {
|
enum TypeDetail<'a> {
|
||||||
|
@ -23,16 +23,16 @@ impl<'a> __Type<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(registry: &'a registry::Registry, type_name: &str) -> __Type<'a> {
|
pub fn new(registry: &'a registry::Registry, type_name: &str) -> __Type<'a> {
|
||||||
match TypeInfo::create(type_name) {
|
match TypeName::create(type_name) {
|
||||||
TypeInfo::NonNull(ty) => __Type {
|
TypeName::NonNull(ty) => __Type {
|
||||||
registry,
|
registry,
|
||||||
detail: TypeDetail::NonNull(ty.to_string()),
|
detail: TypeDetail::NonNull(ty.to_string()),
|
||||||
},
|
},
|
||||||
TypeInfo::List(ty) => __Type {
|
TypeName::List(ty) => __Type {
|
||||||
registry,
|
registry,
|
||||||
detail: TypeDetail::List(ty.to_string()),
|
detail: TypeDetail::List(ty.to_string()),
|
||||||
},
|
},
|
||||||
TypeInfo::Type(ty) => __Type {
|
TypeName::Name(ty) => __Type {
|
||||||
registry,
|
registry,
|
||||||
detail: TypeDetail::Simple(®istry.types[ty]),
|
detail: TypeDetail::Simple(®istry.types[ty]),
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,20 +17,20 @@ fn parse_list(type_name: &str) -> Option<&str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum TypeInfo<'a> {
|
pub enum TypeName<'a> {
|
||||||
List(&'a str),
|
List(&'a str),
|
||||||
NonNull(&'a str),
|
NonNull(&'a str),
|
||||||
Type(&'a str),
|
Name(&'a str),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TypeInfo<'a> {
|
impl<'a> TypeName<'a> {
|
||||||
pub fn create(type_name: &str) -> TypeInfo {
|
pub fn create(type_name: &str) -> TypeName {
|
||||||
if let Some(type_name) = parse_non_null(type_name) {
|
if let Some(type_name) = parse_non_null(type_name) {
|
||||||
TypeInfo::NonNull(type_name)
|
TypeName::NonNull(type_name)
|
||||||
} else if let Some(type_name) = parse_list(type_name) {
|
} else if let Some(type_name) = parse_list(type_name) {
|
||||||
TypeInfo::List(type_name)
|
TypeName::List(type_name)
|
||||||
} else {
|
} else {
|
||||||
TypeInfo::Type(type_name)
|
TypeName::Name(type_name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,23 @@ impl Type {
|
||||||
Type::InputObject { name, .. } => name,
|
Type::InputObject { name, .. } => name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_composite(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Type::Object { .. } => true,
|
||||||
|
Type::Interface { .. } => true,
|
||||||
|
Type::Union { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_leaf(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Type::Enum { .. } => true,
|
||||||
|
Type::Scalar { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Directive {
|
pub struct Directive {
|
||||||
|
@ -147,7 +164,7 @@ impl Registry {
|
||||||
fields.insert(
|
fields.insert(
|
||||||
"__typename",
|
"__typename",
|
||||||
Field {
|
Field {
|
||||||
name: "",
|
name: "__typename",
|
||||||
description: None,
|
description: None,
|
||||||
args: Default::default(),
|
args: Default::default(),
|
||||||
ty: "String!".to_string(),
|
ty: "String!".to_string(),
|
||||||
|
@ -179,10 +196,10 @@ impl Registry {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_basic_type(&self, type_name: &str) -> Option<&Type> {
|
pub fn get_basic_type(&self, type_name: &str) -> Option<&Type> {
|
||||||
match TypeInfo::create(type_name) {
|
match TypeName::create(type_name) {
|
||||||
TypeInfo::Type(type_name) => self.types.get(type_name),
|
TypeName::Name(type_name) => self.types.get(type_name),
|
||||||
TypeInfo::List(type_name) => self.get_basic_type(type_name),
|
TypeName::List(type_name) => self.get_basic_type(type_name),
|
||||||
TypeInfo::NonNull(type_name) => self.get_basic_type(type_name),
|
TypeName::NonNull(type_name) => self.get_basic_type(type_name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,30 @@
|
||||||
use crate::error::RuleError;
|
use crate::error::RuleError;
|
||||||
use crate::registry::{Registry, Type};
|
use crate::registry::{Registry, Type};
|
||||||
|
use graphql_parser::query::{Definition, Document};
|
||||||
use graphql_parser::Pos;
|
use graphql_parser::Pos;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
pub struct ValidatorContext<'a> {
|
pub struct ValidatorContext<'a> {
|
||||||
pub registry: &'a Registry,
|
pub registry: &'a Registry,
|
||||||
pub errors: Vec<RuleError>,
|
pub errors: Vec<RuleError>,
|
||||||
type_stack: Vec<&'a Type>,
|
type_stack: Vec<&'a Type>,
|
||||||
|
fragments_names: HashSet<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ValidatorContext<'a> {
|
impl<'a> ValidatorContext<'a> {
|
||||||
pub fn new(registry: &'a Registry) -> Self {
|
pub fn new(registry: &'a Registry, doc: &'a Document) -> Self {
|
||||||
Self {
|
Self {
|
||||||
registry,
|
registry,
|
||||||
errors: Default::default(),
|
errors: Default::default(),
|
||||||
type_stack: Default::default(),
|
type_stack: Default::default(),
|
||||||
|
fragments_names: doc
|
||||||
|
.definitions
|
||||||
|
.iter()
|
||||||
|
.filter_map(|d| match d {
|
||||||
|
Definition::Fragment(fragment) => Some(fragment.name.as_str()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,13 +35,25 @@ impl<'a> ValidatorContext<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn append_errors(&mut self, errors: Vec<RuleError>) {
|
||||||
|
self.errors.extend(errors);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_type<F: FnMut(&mut ValidatorContext<'a>)>(&mut self, ty: &'a Type, mut f: F) {
|
pub fn with_type<F: FnMut(&mut ValidatorContext<'a>)>(&mut self, ty: &'a Type, mut f: F) {
|
||||||
self.type_stack.push(ty);
|
self.type_stack.push(ty);
|
||||||
f(self);
|
f(self);
|
||||||
self.type_stack.pop();
|
self.type_stack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parent_type(&self) -> &'a Type {
|
pub fn parent_type(&self) -> Option<&'a Type> {
|
||||||
|
self.type_stack.get(self.type_stack.len() - 2).map(|t| *t)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_type(&self) -> &'a Type {
|
||||||
self.type_stack.last().unwrap()
|
self.type_stack.last().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_known_fragment(&self, name: &str) -> bool {
|
||||||
|
self.fragments_names.contains(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,15 @@ mod utils;
|
||||||
mod visitor;
|
mod visitor;
|
||||||
|
|
||||||
pub fn check_rules(registry: &Registry, doc: &Document) -> Result<()> {
|
pub fn check_rules(registry: &Registry, doc: &Document) -> Result<()> {
|
||||||
let mut ctx = ValidatorContext::new(registry);
|
let mut ctx = ValidatorContext::new(registry, doc);
|
||||||
let mut visitor = VisitorNil
|
let mut visitor = VisitorNil
|
||||||
.with(rules::ArgumentsOfCorrectType::default())
|
.with(rules::ArgumentsOfCorrectType::default())
|
||||||
.with(rules::DefaultValuesOfCorrectType);
|
.with(rules::DefaultValuesOfCorrectType)
|
||||||
|
.with(rules::FieldsOnCorrectType)
|
||||||
|
.with(rules::FragmentsOnCompositeTypes)
|
||||||
|
.with(rules::KnownArgumentNames::default())
|
||||||
|
.with(rules::NoFragmentCycles::default())
|
||||||
|
.with(rules::KnownFragmentNames);
|
||||||
|
|
||||||
visit(&mut visitor, &mut ctx, doc);
|
visit(&mut visitor, &mut ctx, doc);
|
||||||
if !ctx.errors.is_empty() {
|
if !ctx.errors.is_empty() {
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
|
||||||
fn enter_field(&mut self, ctx: &mut ValidatorContext<'a>, field: &'a Field) {
|
fn enter_field(&mut self, ctx: &mut ValidatorContext<'a>, field: &'a Field) {
|
||||||
self.current_args = ctx
|
self.current_args = ctx
|
||||||
.parent_type()
|
.parent_type()
|
||||||
.field_by_name(&field.name)
|
.and_then(|p| p.field_by_name(&field.name))
|
||||||
.map(|f| (&f.args, field.position));
|
.map(|f| (&f.args, field.position));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ use crate::validation::utils::is_valid_input_value;
|
||||||
use crate::validation::visitor::Visitor;
|
use crate::validation::visitor::Visitor;
|
||||||
use graphql_parser::query::{Type, VariableDefinition};
|
use graphql_parser::query::{Type, VariableDefinition};
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DefaultValuesOfCorrectType;
|
pub struct DefaultValuesOfCorrectType;
|
||||||
|
|
||||||
impl<'a> Visitor<'a> for DefaultValuesOfCorrectType {
|
impl<'a> Visitor<'a> for DefaultValuesOfCorrectType {
|
||||||
|
@ -15,7 +14,7 @@ impl<'a> Visitor<'a> for DefaultValuesOfCorrectType {
|
||||||
if let Some(value) = &variable_definition.default_value {
|
if let Some(value) = &variable_definition.default_value {
|
||||||
if let Type::NonNullType(_) = variable_definition.var_type {
|
if let Type::NonNullType(_) = variable_definition.var_type {
|
||||||
ctx.report_error(vec![variable_definition.position],format!(
|
ctx.report_error(vec![variable_definition.position],format!(
|
||||||
"Argument \"{}\" has type \"{}\" and is not nullable, so it't can't have a default value",
|
"Argument \"#{}\" has type \"{}\" and is not nullable, so it't can't have a default value",
|
||||||
variable_definition.name, variable_definition.var_type,
|
variable_definition.name, variable_definition.var_type,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
|
|
33
src/validation/rules/fields_on_correct_type.rs
Normal file
33
src/validation/rules/fields_on_correct_type.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use crate::registry::Type;
|
||||||
|
use crate::validation::context::ValidatorContext;
|
||||||
|
use crate::validation::visitor::Visitor;
|
||||||
|
use graphql_parser::query::Field;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FieldsOnCorrectType;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for FieldsOnCorrectType {
|
||||||
|
fn enter_field(&mut self, ctx: &mut ValidatorContext<'a>, field: &'a Field) {
|
||||||
|
if ctx
|
||||||
|
.parent_type()
|
||||||
|
.unwrap()
|
||||||
|
.field_by_name(&field.name)
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
if let Some(Type::Union { .. }) = ctx.parent_type() {
|
||||||
|
if field.name == "__typename" {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.report_error(
|
||||||
|
vec![field.position],
|
||||||
|
format!(
|
||||||
|
"Unknown field \"{}\" on type \"{}\"",
|
||||||
|
field.name,
|
||||||
|
ctx.parent_type().unwrap().name()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
src/validation/rules/fragments_on_composite_types.rs
Normal file
41
src/validation/rules/fragments_on_composite_types.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use crate::validation::context::ValidatorContext;
|
||||||
|
use crate::validation::visitor::Visitor;
|
||||||
|
use graphql_parser::query::{FragmentDefinition, InlineFragment, TypeCondition};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FragmentsOnCompositeTypes;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for FragmentsOnCompositeTypes {
|
||||||
|
fn enter_fragment_definition(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
fragment_definition: &'a FragmentDefinition,
|
||||||
|
) {
|
||||||
|
if !ctx.current_type().is_composite() {
|
||||||
|
let TypeCondition::On(name) = &fragment_definition.type_condition;
|
||||||
|
ctx.report_error(
|
||||||
|
vec![fragment_definition.position],
|
||||||
|
format!(
|
||||||
|
"Fragment \"{}\" cannot condition non composite type \"{}\"",
|
||||||
|
fragment_definition.name, name
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_inline_fragment(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
inline_fragment: &'a InlineFragment,
|
||||||
|
) {
|
||||||
|
if !ctx.current_type().is_composite() {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![inline_fragment.position],
|
||||||
|
format!(
|
||||||
|
"Fragment cannot condition non composite type \"{}\"",
|
||||||
|
ctx.current_type().name()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
src/validation/rules/known_argument_names.rs
Normal file
86
src/validation/rules/known_argument_names.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use crate::registry::InputValue;
|
||||||
|
use crate::validation::context::ValidatorContext;
|
||||||
|
use crate::validation::visitor::Visitor;
|
||||||
|
use crate::Value;
|
||||||
|
use graphql_parser::query::{Directive, Field};
|
||||||
|
use graphql_parser::Pos;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
enum ArgsType<'a> {
|
||||||
|
Directive(&'a str),
|
||||||
|
Field {
|
||||||
|
field_name: &'a str,
|
||||||
|
type_name: &'a str,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct KnownArgumentNames<'a> {
|
||||||
|
current_args: Option<(&'a HashMap<&'static str, InputValue>, ArgsType<'a>, Pos)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
|
||||||
|
fn enter_directive(&mut self, ctx: &mut ValidatorContext<'a>, directive: &'a Directive) {
|
||||||
|
self.current_args = ctx.registry.directives.get(&directive.name).map(|d| {
|
||||||
|
(
|
||||||
|
&d.args,
|
||||||
|
ArgsType::Directive(&directive.name),
|
||||||
|
directive.position,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_directive(&mut self, _ctx: &mut ValidatorContext<'a>, _directive: &'a Directive) {
|
||||||
|
self.current_args = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_argument(&mut self, ctx: &mut ValidatorContext<'a>, name: &str, _value: &'a Value) {
|
||||||
|
if let Some((args, arg_type, pos)) = &self.current_args {
|
||||||
|
if !args.contains_key(name) {
|
||||||
|
match arg_type {
|
||||||
|
ArgsType::Field {
|
||||||
|
field_name,
|
||||||
|
type_name,
|
||||||
|
} => {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![*pos],
|
||||||
|
format!(
|
||||||
|
"Unknown argument \"{}\" on field \"{}\" of type \"{}\"",
|
||||||
|
name, field_name, type_name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ArgsType::Directive(directive_name) => {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![*pos],
|
||||||
|
format!(
|
||||||
|
"Unknown argument \"{}\" on directive \"{}\"",
|
||||||
|
name, directive_name
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_field(&mut self, ctx: &mut ValidatorContext<'a>, field: &'a Field) {
|
||||||
|
self.current_args = ctx
|
||||||
|
.parent_type()
|
||||||
|
.and_then(|p| p.field_by_name(&field.name))
|
||||||
|
.map(|f| {
|
||||||
|
(
|
||||||
|
&f.args,
|
||||||
|
ArgsType::Field {
|
||||||
|
field_name: &field.name,
|
||||||
|
type_name: ctx.parent_type().unwrap().name(),
|
||||||
|
},
|
||||||
|
field.position,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_field(&mut self, _ctx: &mut ValidatorContext<'a>, _field: &'a Field) {
|
||||||
|
self.current_args = None;
|
||||||
|
}
|
||||||
|
}
|
21
src/validation/rules/known_fragment_names.rs
Normal file
21
src/validation/rules/known_fragment_names.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use crate::validation::context::ValidatorContext;
|
||||||
|
use crate::validation::visitor::Visitor;
|
||||||
|
use graphql_parser::query::FragmentSpread;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct KnownFragmentNames;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for KnownFragmentNames {
|
||||||
|
fn enter_fragment_spread(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
fragment_spread: &'a FragmentSpread,
|
||||||
|
) {
|
||||||
|
if !ctx.is_known_fragment(&fragment_spread.fragment_name) {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![fragment_spread.position],
|
||||||
|
format!(r#"Unknown fragment: "{}""#, fragment_spread.fragment_name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,15 @@
|
||||||
mod arguments_of_correct_type;
|
mod arguments_of_correct_type;
|
||||||
mod default_values_of_correct_type;
|
mod default_values_of_correct_type;
|
||||||
|
mod fields_on_correct_type;
|
||||||
|
mod fragments_on_composite_types;
|
||||||
|
mod known_argument_names;
|
||||||
|
mod known_fragment_names;
|
||||||
|
mod no_fragment_cycles;
|
||||||
|
|
||||||
pub use arguments_of_correct_type::ArgumentsOfCorrectType;
|
pub use arguments_of_correct_type::ArgumentsOfCorrectType;
|
||||||
pub use default_values_of_correct_type::DefaultValuesOfCorrectType;
|
pub use default_values_of_correct_type::DefaultValuesOfCorrectType;
|
||||||
|
pub use fields_on_correct_type::FieldsOnCorrectType;
|
||||||
|
pub use fragments_on_composite_types::FragmentsOnCompositeTypes;
|
||||||
|
pub use known_argument_names::KnownArgumentNames;
|
||||||
|
pub use known_fragment_names::KnownFragmentNames;
|
||||||
|
pub use no_fragment_cycles::NoFragmentCycles;
|
||||||
|
|
105
src/validation/rules/no_fragment_cycles.rs
Normal file
105
src/validation/rules/no_fragment_cycles.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
use crate::error::RuleError;
|
||||||
|
use crate::validation::context::ValidatorContext;
|
||||||
|
use crate::validation::visitor::Visitor;
|
||||||
|
use graphql_parser::query::{Document, FragmentDefinition, FragmentSpread};
|
||||||
|
use graphql_parser::Pos;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
struct CycleDetector<'a> {
|
||||||
|
visited: HashSet<&'a str>,
|
||||||
|
spreads: &'a HashMap<&'a str, Vec<(&'a str, Pos)>>,
|
||||||
|
path_indices: HashMap<&'a str, usize>,
|
||||||
|
errors: Vec<RuleError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CycleDetector<'a> {
|
||||||
|
fn detect_from(&mut self, from: &'a str, path: &mut Vec<(&'a str, Pos)>) {
|
||||||
|
self.visited.insert(from);
|
||||||
|
|
||||||
|
if !self.spreads.contains_key(from) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.path_indices.insert(from, path.len());
|
||||||
|
|
||||||
|
for (name, pos) in &self.spreads[from] {
|
||||||
|
let index = self.path_indices.get(name).cloned();
|
||||||
|
|
||||||
|
if let Some(index) = index {
|
||||||
|
let err_pos = if index < path.len() {
|
||||||
|
path[index].1
|
||||||
|
} else {
|
||||||
|
*pos
|
||||||
|
};
|
||||||
|
|
||||||
|
self.errors.push(RuleError {
|
||||||
|
locations: vec![err_pos],
|
||||||
|
message: format!("Cannot spread fragment \"{}\"", name),
|
||||||
|
});
|
||||||
|
} else if !self.visited.contains(name) {
|
||||||
|
path.push((name, *pos));
|
||||||
|
self.detect_from(name, path);
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.path_indices.remove(from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NoFragmentCycles<'a> {
|
||||||
|
current_fragment: Option<&'a str>,
|
||||||
|
spreads: HashMap<&'a str, Vec<(&'a str, Pos)>>,
|
||||||
|
fragment_order: Vec<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for NoFragmentCycles<'a> {
|
||||||
|
fn exit_document(&mut self, ctx: &mut ValidatorContext<'a>, _doc: &'a Document) {
|
||||||
|
let mut detector = CycleDetector {
|
||||||
|
visited: HashSet::new(),
|
||||||
|
spreads: &self.spreads,
|
||||||
|
path_indices: HashMap::new(),
|
||||||
|
errors: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for frag in &self.fragment_order {
|
||||||
|
if !detector.visited.contains(frag) {
|
||||||
|
let mut path = Vec::new();
|
||||||
|
detector.detect_from(frag, &mut path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.append_errors(detector.errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_fragment_definition(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
fragment_definition: &'a FragmentDefinition,
|
||||||
|
) {
|
||||||
|
self.current_fragment = Some(&fragment_definition.name);
|
||||||
|
self.fragment_order.push(&fragment_definition.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_fragment_definition(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
_fragment_definition: &'a FragmentDefinition,
|
||||||
|
) {
|
||||||
|
self.current_fragment = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_fragment_spread(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
fragment_spread: &'a 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
use crate::registry::{Registry, Type, TypeInfo};
|
use crate::registry::{Registry, Type, TypeName};
|
||||||
use crate::Value;
|
use crate::Value;
|
||||||
|
|
||||||
pub fn is_valid_input_value(registry: &Registry, type_name: &str, value: &Value) -> bool {
|
pub fn is_valid_input_value(registry: &Registry, type_name: &str, value: &Value) -> bool {
|
||||||
match TypeInfo::create(type_name) {
|
match TypeName::create(type_name) {
|
||||||
TypeInfo::NonNull(type_name) => match value {
|
TypeName::NonNull(type_name) => match value {
|
||||||
Value::Null => false,
|
Value::Null => false,
|
||||||
_ => is_valid_input_value(registry, type_name, value),
|
_ => is_valid_input_value(registry, type_name, value),
|
||||||
},
|
},
|
||||||
TypeInfo::List(type_name) => match value {
|
TypeName::List(type_name) => match value {
|
||||||
Value::List(elems) => elems
|
Value::List(elems) => elems
|
||||||
.iter()
|
.iter()
|
||||||
.all(|elem| is_valid_input_value(registry, type_name, elem)),
|
.all(|elem| is_valid_input_value(registry, type_name, elem)),
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
TypeInfo::Type(type_name) => {
|
TypeName::Name(type_name) => {
|
||||||
if let Value::Null = value {
|
if let Value::Null = value {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,12 +341,39 @@ fn visit_selection<'a, V: Visitor<'a>>(
|
||||||
) {
|
) {
|
||||||
v.enter_selection(ctx, selection);
|
v.enter_selection(ctx, selection);
|
||||||
match selection {
|
match selection {
|
||||||
Selection::Field(field) => visit_field(v, ctx, field),
|
Selection::Field(field) => {
|
||||||
|
if let Some(schema_field) = ctx.current_type().field_by_name(&field.name) {
|
||||||
|
ctx.with_type(
|
||||||
|
ctx.registry.get_basic_type(&schema_field.ty).unwrap(),
|
||||||
|
|ctx| {
|
||||||
|
visit_field(v, ctx, field);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![field.position],
|
||||||
|
format!(
|
||||||
|
"Cannot query field \"{}\" on type \"{}\".",
|
||||||
|
field.name,
|
||||||
|
ctx.current_type().name()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Selection::FragmentSpread(fragment_spread) => {
|
Selection::FragmentSpread(fragment_spread) => {
|
||||||
visit_fragment_spread(v, ctx, fragment_spread)
|
visit_fragment_spread(v, ctx, fragment_spread)
|
||||||
}
|
}
|
||||||
Selection::InlineFragment(inline_fragment) => {
|
Selection::InlineFragment(inline_fragment) => {
|
||||||
visit_inline_fragment(v, ctx, inline_fragment)
|
if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition {
|
||||||
|
if let Some(ty) = ctx.registry.types.get(name) {
|
||||||
|
ctx.with_type(ty, |ctx| visit_inline_fragment(v, ctx, inline_fragment));
|
||||||
|
} else {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![inline_fragment.position],
|
||||||
|
format!("Unknown type \"{}\".", name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v.exit_selection(ctx, selection);
|
v.exit_selection(ctx, selection);
|
||||||
|
@ -356,25 +383,7 @@ fn visit_field<'a, V: Visitor<'a>>(v: &mut V, ctx: &mut ValidatorContext<'a>, fi
|
||||||
v.enter_field(ctx, field);
|
v.enter_field(ctx, field);
|
||||||
visit_arguments(v, ctx, &field.arguments);
|
visit_arguments(v, ctx, &field.arguments);
|
||||||
visit_directives(v, ctx, &field.directives);
|
visit_directives(v, ctx, &field.directives);
|
||||||
|
|
||||||
if let Some(schema_field) = ctx.parent_type().field_by_name(&field.name) {
|
|
||||||
ctx.with_type(
|
|
||||||
ctx.registry.get_basic_type(&schema_field.ty).unwrap(),
|
|
||||||
|ctx| {
|
|
||||||
visit_selection_set(v, ctx, &field.selection_set);
|
visit_selection_set(v, ctx, &field.selection_set);
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ctx.report_error(
|
|
||||||
vec![field.position],
|
|
||||||
format!(
|
|
||||||
"Cannot query field \"{}\" on type \"{}\".",
|
|
||||||
field.name,
|
|
||||||
ctx.parent_type().name()
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
v.exit_field(ctx, field);
|
v.exit_field(ctx, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user