From a1d47aaf902d02fb2c6e4810f240baeeb87b6cf1 Mon Sep 17 00:00:00 2001 From: sunli Date: Mon, 9 Mar 2020 20:39:46 +0800 Subject: [PATCH] add some validation rules --- README.md | 6 +- src/validation/mod.rs | 5 +- .../rules/arguments_of_correct_type.rs | 18 ++++-- .../rules/default_values_of_correct_type.rs | 2 +- src/validation/rules/known_argument_names.rs | 29 +++++----- src/validation/rules/known_type_names.rs | 2 +- src/validation/rules/mod.rs | 6 ++ .../rules/no_undefined_variables.rs | 31 ++++------ src/validation/rules/no_unused_variables.rs | 56 +++++++++++++++++++ src/validation/rules/unique_argument_names.rs | 36 ++++++++++++ src/validation/rules/unique_fragment_names.rs | 27 +++++++++ src/validation/visitor.rs | 52 +++++++++++++---- 12 files changed, 212 insertions(+), 58 deletions(-) create mode 100644 src/validation/rules/no_unused_variables.rs create mode 100644 src/validation/rules/unique_argument_names.rs create mode 100644 src/validation/rules/unique_fragment_names.rs diff --git a/README.md b/README.md index de667cae..e71894ca 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/validation/mod.rs b/src/validation/mod.rs index 4164d545..b82390d6 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -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() { diff --git a/src/validation/rules/arguments_of_correct_type.rs b/src/validation/rules/arguments_of_correct_type.rs index babec71c..9748825e 100644 --- a/src/validation/rules/arguments_of_correct_type.rs +++ b/src/validation/rules/arguments_of_correct_type.rs @@ -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) { diff --git a/src/validation/rules/default_values_of_correct_type.rs b/src/validation/rules/default_values_of_correct_type.rs index 2c5a08d7..e2d1d77c 100644 --- a/src/validation/rules/default_values_of_correct_type.rs +++ b/src/validation/rules/default_values_of_correct_type.rs @@ -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 { diff --git a/src/validation/rules/known_argument_names.rs b/src/validation/rules/known_argument_names.rs index c983e9d3..0d31a716 100644 --- a/src/validation/rules/known_argument_names.rs +++ b/src/validation/rules/known_argument_names.rs @@ -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, ) }); } diff --git a/src/validation/rules/known_type_names.rs b/src/validation/rules/known_type_names.rs index 05aecb6e..85f5aafe 100644 --- a/src/validation/rules/known_type_names.rs +++ b/src/validation/rules/known_type_names.rs @@ -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, ); } diff --git a/src/validation/rules/mod.rs b/src/validation/rules/mod.rs index 4c456caa..7245b1fd 100644 --- a/src/validation/rules/mod.rs +++ b/src/validation/rules/mod.rs @@ -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; diff --git a/src/validation/rules/no_undefined_variables.rs b/src/validation/rules/no_undefined_variables.rs index 50b3a7cb..1c52fbfc 100644 --- a/src/validation/rules/no_undefined_variables.rs +++ b/src/validation/rules/no_undefined_variables.rs @@ -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, } 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(); - } } diff --git a/src/validation/rules/no_unused_variables.rs b/src/validation/rules/no_unused_variables.rs new file mode 100644 index 00000000..8bd977ff --- /dev/null +++ b/src/validation/rules/no_unused_variables.rs @@ -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); + } + } +} diff --git a/src/validation/rules/unique_argument_names.rs b/src/validation/rules/unique_argument_names.rs new file mode 100644 index 00000000..4700cca2 --- /dev/null +++ b/src/validation/rules/unique_argument_names.rs @@ -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(); + } +} diff --git a/src/validation/rules/unique_fragment_names.rs b/src/validation/rules/unique_fragment_names.rs new file mode 100644 index 00000000..078a2fa0 --- /dev/null +++ b/src/validation/rules/unique_fragment_names.rs @@ -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 + ), + ) + } + } +} diff --git a/src/validation/visitor.rs b/src/validation/visitor.rs index 674aaaa8..fdb8fa58 100644 --- a/src/validation/visitor.rs +++ b/src/validation/visitor.rs @@ -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); } }