add some validation rules

This commit is contained in:
sunli 2020-03-09 20:39:46 +08:00
parent 5caf8f9c57
commit a1d47aaf90
12 changed files with 212 additions and 58 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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);
}
}
}

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

View 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
),
)
}
}
}

View File

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