add some validation rules
This commit is contained in:
parent
5caf8f9c57
commit
a1d47aaf90
|
@ -92,13 +92,13 @@
|
|||
- [X] NoFragmentCycles
|
||||
- [X] NoUndefinedVariables
|
||||
- [X] NoUnusedFragments
|
||||
- [ ] NoUnusedVariables
|
||||
- [X] NoUnusedVariables
|
||||
- [ ] OverlappingFieldsCanBeMerged
|
||||
- [ ] PossibleFragmentSpreads
|
||||
- [ ] ProvidedNonNullArguments
|
||||
- [ ] ScalarLeafs
|
||||
- [ ] UniqueArgumentNames
|
||||
- [ ] UniqueFragmentNames
|
||||
- [X] UniqueArgumentNames
|
||||
- [X] UniqueFragmentNames
|
||||
- [ ] UniqueInputFieldNames
|
||||
- [ ] UniqueOperationNames
|
||||
- [ ] UniqueVariableNames
|
||||
|
|
|
@ -23,7 +23,10 @@ pub fn check_rules(registry: &Registry, doc: &Document) -> Result<()> {
|
|||
.with(rules::KnownTypeNames)
|
||||
.with(rules::LoneAnonymousOperation::default())
|
||||
.with(rules::NoUndefinedVariables::default())
|
||||
.with(rules::NoUnusedFragments::default());
|
||||
.with(rules::NoUnusedFragments::default())
|
||||
.with(rules::NoUnusedVariables::default())
|
||||
.with(rules::UniqueArgumentNames::default())
|
||||
.with(rules::UniqueFragmentNames::default());
|
||||
|
||||
visit(&mut visitor, &mut ctx, doc);
|
||||
if !ctx.errors.is_empty() {
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::collections::HashMap;
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct ArgumentsOfCorrectType<'a> {
|
||||
current_args: Option<(&'a HashMap<&'static str, InputValue>, Pos)>,
|
||||
current_args: Option<&'a HashMap<&'static str, InputValue>>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
|
||||
|
@ -18,17 +18,23 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
|
|||
.registry
|
||||
.directives
|
||||
.get(&directive.name)
|
||||
.map(|d| (&d.args, directive.position));
|
||||
.map(|d| &d.args);
|
||||
}
|
||||
|
||||
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((arg, pos)) = self
|
||||
fn enter_argument(
|
||||
&mut self,
|
||||
ctx: &mut ValidatorContext<'a>,
|
||||
pos: Pos,
|
||||
name: &str,
|
||||
value: &'a Value,
|
||||
) {
|
||||
if let Some(arg) = self
|
||||
.current_args
|
||||
.and_then(|(args, pos)| args.get(name).map(|input| (input, pos)))
|
||||
.and_then(|args| args.get(name).map(|input| input))
|
||||
{
|
||||
if !is_valid_input_value(ctx.registry, &arg.ty, value) {
|
||||
ctx.report_error(
|
||||
|
@ -46,7 +52,7 @@ impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
|
|||
self.current_args = ctx
|
||||
.parent_type()
|
||||
.and_then(|p| p.field_by_name(&field.name))
|
||||
.map(|f| (&f.args, field.position));
|
||||
.map(|f| &f.args);
|
||||
}
|
||||
|
||||
fn exit_field(&mut self, _ctx: &mut ValidatorContext<'a>, _field: &'a Field) {
|
||||
|
|
|
@ -14,7 +14,7 @@ impl<'a> Visitor<'a> for DefaultValuesOfCorrectType {
|
|||
if let Some(value) = &variable_definition.default_value {
|
||||
if let Type::NonNullType(_) = variable_definition.var_type {
|
||||
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,
|
||||
));
|
||||
} else {
|
||||
|
|
|
@ -16,26 +16,30 @@ enum ArgsType<'a> {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct KnownArgumentNames<'a> {
|
||||
current_args: Option<(&'a HashMap<&'static str, InputValue>, ArgsType<'a>, Pos)>,
|
||||
current_args: Option<(&'a HashMap<&'static str, InputValue>, ArgsType<'a>)>,
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
});
|
||||
self.current_args = ctx
|
||||
.registry
|
||||
.directives
|
||||
.get(&directive.name)
|
||||
.map(|d| (&d.args, ArgsType::Directive(&directive.name)));
|
||||
}
|
||||
|
||||
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 {
|
||||
fn enter_argument(
|
||||
&mut self,
|
||||
ctx: &mut ValidatorContext<'a>,
|
||||
pos: Pos,
|
||||
name: &str,
|
||||
_value: &'a Value,
|
||||
) {
|
||||
if let Some((args, arg_type)) = &self.current_args {
|
||||
if !args.contains_key(name) {
|
||||
match arg_type {
|
||||
ArgsType::Field {
|
||||
|
@ -43,7 +47,7 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
|
|||
type_name,
|
||||
} => {
|
||||
ctx.report_error(
|
||||
vec![*pos],
|
||||
vec![pos],
|
||||
format!(
|
||||
"Unknown argument \"{}\" on field \"{}\" of type \"{}\"",
|
||||
name, field_name, type_name,
|
||||
|
@ -52,7 +56,7 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
|
|||
}
|
||||
ArgsType::Directive(directive_name) => {
|
||||
ctx.report_error(
|
||||
vec![*pos],
|
||||
vec![pos],
|
||||
format!(
|
||||
"Unknown argument \"{}\" on directive \"{}\"",
|
||||
name, directive_name
|
||||
|
@ -75,7 +79,6 @@ impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
|
|||
field_name: &field.name,
|
||||
type_name: ctx.parent_type().unwrap().name(),
|
||||
},
|
||||
field.position,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ impl<'a> Visitor<'a> for KnownTypeNames {
|
|||
) {
|
||||
validate_type(
|
||||
ctx,
|
||||
TypeName::get_basic_typename(&variable_definition.name),
|
||||
TypeName::get_basic_typename(&variable_definition.var_type.to_string()),
|
||||
variable_definition.position,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,9 @@ mod lone_anonymous_operation;
|
|||
mod no_fragment_cycles;
|
||||
mod no_undefined_variables;
|
||||
mod no_unused_fragments;
|
||||
mod no_unused_variables;
|
||||
mod unique_argument_names;
|
||||
mod unique_fragment_names;
|
||||
|
||||
pub use arguments_of_correct_type::ArgumentsOfCorrectType;
|
||||
pub use default_values_of_correct_type::DefaultValuesOfCorrectType;
|
||||
|
@ -21,3 +24,6 @@ pub use lone_anonymous_operation::LoneAnonymousOperation;
|
|||
pub use no_fragment_cycles::NoFragmentCycles;
|
||||
pub use no_undefined_variables::NoUndefinedVariables;
|
||||
pub use no_unused_fragments::NoUnusedFragments;
|
||||
pub use no_unused_variables::NoUnusedVariables;
|
||||
pub use unique_argument_names::UniqueArgumentNames;
|
||||
pub use unique_fragment_names::UniqueFragmentNames;
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use crate::validation::context::ValidatorContext;
|
||||
use crate::validation::visitor::Visitor;
|
||||
use graphql_parser::query::{Field, OperationDefinition, VariableDefinition};
|
||||
use graphql_parser::schema::{Directive, Value};
|
||||
use graphql_parser::query::{OperationDefinition, VariableDefinition};
|
||||
use graphql_parser::schema::Value;
|
||||
use graphql_parser::Pos;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NoUndefinedVariables<'a> {
|
||||
vars: HashSet<&'a str>,
|
||||
pos_stack: Vec<Pos>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for NoUndefinedVariables<'a> {
|
||||
|
@ -28,30 +27,20 @@ impl<'a> Visitor<'a> for NoUndefinedVariables<'a> {
|
|||
self.vars.insert(&variable_definition.name);
|
||||
}
|
||||
|
||||
fn enter_directive(&mut self, _ctx: &mut ValidatorContext<'a>, directive: &'a Directive) {
|
||||
self.pos_stack.push(directive.position);
|
||||
}
|
||||
|
||||
fn exit_directive(&mut self, _ctx: &mut ValidatorContext<'a>, _directive: &'a Directive) {
|
||||
self.pos_stack.pop();
|
||||
}
|
||||
|
||||
fn enter_argument(&mut self, ctx: &mut ValidatorContext<'a>, _name: &str, value: &'a Value) {
|
||||
fn enter_argument(
|
||||
&mut self,
|
||||
ctx: &mut ValidatorContext<'a>,
|
||||
pos: Pos,
|
||||
_name: &str,
|
||||
value: &'a Value,
|
||||
) {
|
||||
if let Value::Variable(var_name) = value {
|
||||
if !self.vars.contains(var_name.as_str()) {
|
||||
ctx.report_error(
|
||||
vec![self.pos_stack.last().cloned().unwrap()],
|
||||
vec![pos],
|
||||
format!("Variable \"${}\" is not defined", var_name),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enter_field(&mut self, _ctx: &mut ValidatorContext<'a>, field: &'a Field) {
|
||||
self.pos_stack.push(field.position);
|
||||
}
|
||||
|
||||
fn exit_field(&mut self, _ctx: &mut ValidatorContext<'a>, _field: &'a Field) {
|
||||
self.pos_stack.pop();
|
||||
}
|
||||
}
|
||||
|
|
56
src/validation/rules/no_unused_variables.rs
Normal file
56
src/validation/rules/no_unused_variables.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use crate::validation::context::ValidatorContext;
|
||||
use crate::validation::visitor::Visitor;
|
||||
use graphql_parser::query::{OperationDefinition, VariableDefinition};
|
||||
use graphql_parser::schema::Value;
|
||||
use graphql_parser::Pos;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NoUnusedVariables<'a> {
|
||||
vars: HashSet<(&'a str, Pos)>,
|
||||
used_vars: HashSet<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for NoUnusedVariables<'a> {
|
||||
fn enter_operation_definition(
|
||||
&mut self,
|
||||
_ctx: &mut ValidatorContext<'a>,
|
||||
_operation_definition: &'a OperationDefinition,
|
||||
) {
|
||||
self.used_vars.clear();
|
||||
self.vars.clear();
|
||||
}
|
||||
|
||||
fn exit_operation_definition(
|
||||
&mut self,
|
||||
ctx: &mut ValidatorContext<'a>,
|
||||
_operation_definition: &'a OperationDefinition,
|
||||
) {
|
||||
for (name, pos) in &self.vars {
|
||||
if !self.used_vars.contains(name) {
|
||||
ctx.report_error(vec![*pos], format!("Variable \"${}\" is not used", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enter_variable_definition(
|
||||
&mut self,
|
||||
_ctx: &mut ValidatorContext<'a>,
|
||||
variable_definition: &'a VariableDefinition,
|
||||
) {
|
||||
self.vars
|
||||
.insert((&variable_definition.name, variable_definition.position));
|
||||
}
|
||||
|
||||
fn enter_argument(
|
||||
&mut self,
|
||||
_ctx: &mut ValidatorContext<'a>,
|
||||
_pos: Pos,
|
||||
_name: &'a str,
|
||||
value: &'a Value,
|
||||
) {
|
||||
if let Value::Variable(var) = value {
|
||||
self.used_vars.insert(var);
|
||||
}
|
||||
}
|
||||
}
|
36
src/validation/rules/unique_argument_names.rs
Normal file
36
src/validation/rules/unique_argument_names.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use crate::validation::context::ValidatorContext;
|
||||
use crate::validation::visitor::Visitor;
|
||||
use graphql_parser::query::Field;
|
||||
use graphql_parser::schema::{Directive, Value};
|
||||
use graphql_parser::Pos;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UniqueArgumentNames<'a> {
|
||||
names: HashSet<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for UniqueArgumentNames<'a> {
|
||||
fn enter_directive(&mut self, _ctx: &mut ValidatorContext<'a>, _directive: &'a Directive) {
|
||||
self.names.clear();
|
||||
}
|
||||
|
||||
fn enter_argument(
|
||||
&mut self,
|
||||
ctx: &mut ValidatorContext<'a>,
|
||||
pos: Pos,
|
||||
name: &'a str,
|
||||
_value: &'a Value,
|
||||
) {
|
||||
if !self.names.insert(name) {
|
||||
ctx.report_error(
|
||||
vec![pos],
|
||||
format!("There can only be one argument named \"{}\"", name),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn enter_field(&mut self, _ctx: &mut ValidatorContext<'a>, _field: &'a Field) {
|
||||
self.names.clear();
|
||||
}
|
||||
}
|
27
src/validation/rules/unique_fragment_names.rs
Normal file
27
src/validation/rules/unique_fragment_names.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use crate::validation::context::ValidatorContext;
|
||||
use crate::validation::visitor::Visitor;
|
||||
use graphql_parser::query::FragmentDefinition;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UniqueFragmentNames<'a> {
|
||||
names: HashSet<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for UniqueFragmentNames<'a> {
|
||||
fn enter_fragment_definition(
|
||||
&mut self,
|
||||
ctx: &mut ValidatorContext<'a>,
|
||||
fragment_definition: &'a FragmentDefinition,
|
||||
) {
|
||||
if !self.names.insert(&fragment_definition.name) {
|
||||
ctx.report_error(
|
||||
vec![fragment_definition.position],
|
||||
format!(
|
||||
"There can only be one fragment named {}",
|
||||
fragment_definition.name
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ use graphql_parser::query::{
|
|||
Definition, Directive, Document, Field, FragmentDefinition, FragmentSpread, InlineFragment,
|
||||
Name, OperationDefinition, Selection, SelectionSet, TypeCondition, Value, VariableDefinition,
|
||||
};
|
||||
use graphql_parser::Pos;
|
||||
|
||||
pub trait Visitor<'a> {
|
||||
fn enter_document(&mut self, _ctx: &mut ValidatorContext<'a>, _doc: &'a Document) {}
|
||||
|
@ -50,8 +51,22 @@ pub trait Visitor<'a> {
|
|||
fn enter_directive(&mut self, _ctx: &mut ValidatorContext<'a>, _directive: &'a Directive) {}
|
||||
fn exit_directive(&mut self, _ctx: &mut ValidatorContext<'a>, _directive: &'a Directive) {}
|
||||
|
||||
fn enter_argument(&mut self, _ctx: &mut ValidatorContext<'a>, _name: &str, _value: &'a Value) {}
|
||||
fn exit_argument(&mut self, _ctx: &mut ValidatorContext<'a>, _name: &str, _value: &'a Value) {}
|
||||
fn enter_argument(
|
||||
&mut self,
|
||||
_ctx: &mut ValidatorContext<'a>,
|
||||
_pos: Pos,
|
||||
_name: &'a str,
|
||||
_value: &'a Value,
|
||||
) {
|
||||
}
|
||||
fn exit_argument(
|
||||
&mut self,
|
||||
_ctx: &mut ValidatorContext<'a>,
|
||||
_pos: Pos,
|
||||
_name: &'a str,
|
||||
_value: &'a Value,
|
||||
) {
|
||||
}
|
||||
|
||||
fn enter_selection(&mut self, _ctx: &mut ValidatorContext<'a>, _selection: &'a Selection) {}
|
||||
fn exit_selection(&mut self, _ctx: &mut ValidatorContext<'a>, _selection: &'a Selection) {}
|
||||
|
@ -183,14 +198,26 @@ where
|
|||
self.1.exit_directive(ctx, directive);
|
||||
}
|
||||
|
||||
fn enter_argument(&mut self, ctx: &mut ValidatorContext<'a>, name: &str, value: &'a Value) {
|
||||
self.0.enter_argument(ctx, name, value);
|
||||
self.1.enter_argument(ctx, name, value);
|
||||
fn enter_argument(
|
||||
&mut self,
|
||||
ctx: &mut ValidatorContext<'a>,
|
||||
pos: Pos,
|
||||
name: &'a str,
|
||||
value: &'a Value,
|
||||
) {
|
||||
self.0.enter_argument(ctx, pos, name, value);
|
||||
self.1.enter_argument(ctx, pos, name, value);
|
||||
}
|
||||
|
||||
fn exit_argument(&mut self, ctx: &mut ValidatorContext<'a>, name: &str, value: &'a Value) {
|
||||
self.0.exit_argument(ctx, name, value);
|
||||
self.1.exit_argument(ctx, name, value);
|
||||
fn exit_argument(
|
||||
&mut self,
|
||||
ctx: &mut ValidatorContext<'a>,
|
||||
pos: Pos,
|
||||
name: &'a str,
|
||||
value: &'a Value,
|
||||
) {
|
||||
self.0.exit_argument(ctx, pos, name, value);
|
||||
self.1.exit_argument(ctx, pos, name, value);
|
||||
}
|
||||
|
||||
fn enter_selection(&mut self, ctx: &mut ValidatorContext<'a>, selection: &'a Selection) {
|
||||
|
@ -381,7 +408,7 @@ fn visit_selection<'a, V: Visitor<'a>>(
|
|||
|
||||
fn visit_field<'a, V: Visitor<'a>>(v: &mut V, ctx: &mut ValidatorContext<'a>, field: &'a Field) {
|
||||
v.enter_field(ctx, field);
|
||||
visit_arguments(v, ctx, &field.arguments);
|
||||
visit_arguments(v, ctx, field.position, &field.arguments);
|
||||
visit_directives(v, ctx, &field.directives);
|
||||
visit_selection_set(v, ctx, &field.selection_set);
|
||||
v.exit_field(ctx, field);
|
||||
|
@ -390,11 +417,12 @@ fn visit_field<'a, V: Visitor<'a>>(v: &mut V, ctx: &mut ValidatorContext<'a>, fi
|
|||
fn visit_arguments<'a, V: Visitor<'a>>(
|
||||
v: &mut V,
|
||||
ctx: &mut ValidatorContext<'a>,
|
||||
pos: Pos,
|
||||
arguments: &'a Vec<(Name, Value)>,
|
||||
) {
|
||||
for (name, value) in arguments {
|
||||
v.enter_argument(ctx, name, value);
|
||||
v.exit_argument(ctx, name, value);
|
||||
v.enter_argument(ctx, pos, name, value);
|
||||
v.exit_argument(ctx, pos, name, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -416,7 +444,7 @@ fn visit_directives<'a, V: Visitor<'a>>(
|
|||
) {
|
||||
for d in directives {
|
||||
v.enter_directive(ctx, d);
|
||||
visit_arguments(v, ctx, &d.arguments);
|
||||
visit_arguments(v, ctx, d.position, &d.arguments);
|
||||
v.exit_directive(ctx, d);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user