Add DirectivesUnique rule

This commit is contained in:
Sunli 2021-11-18 20:14:56 +08:00
parent eb9cda4c80
commit e2c6ead1a3
11 changed files with 198 additions and 7 deletions

View File

@ -103,4 +103,9 @@ impl<'a> __Directive<'a> {
})
.collect()
}
#[inline]
async fn is_repeatable(&self) -> bool {
self.directive.is_repeatable
}
}

View File

@ -349,6 +349,7 @@ pub struct MetaDirective {
pub description: Option<&'static str>,
pub locations: Vec<model::__DirectiveLocation>,
pub args: IndexMap<&'static str, MetaInputValue>,
pub is_repeatable: bool,
}
#[derive(Default)]

View File

@ -287,7 +287,8 @@ where
is_secret: false,
});
args
}
},
is_repeatable: false,
});
registry.add_directive(MetaDirective {
@ -309,7 +310,8 @@ where
is_secret: false,
});
args
}
},
is_repeatable: false,
});
// register scalars

View File

@ -71,6 +71,7 @@ pub fn check_rules(
.with(rules::PossibleFragmentSpreads::default())
.with(rules::ProvidedNonNullArguments)
.with(rules::KnownDirectives::default())
.with(rules::DirectivesUnique::default())
.with(rules::OverlappingFieldsCanBeMerged)
.with(rules::UploadFile)
.with(visitors::CacheControlCalculate {

View File

@ -1,12 +1,13 @@
use indexmap::map::IndexMap;
use async_graphql_value::Value;
use crate::context::QueryPathNode;
use crate::parser::types::{Directive, Field};
use crate::registry::MetaInputValue;
use crate::validation::utils::is_valid_input_value;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Name, Positioned, QueryPathSegment};
use async_graphql_value::Value;
#[derive(Default)]
pub struct ArgumentsOfCorrectType<'a> {

View File

@ -0,0 +1,176 @@
use crate::parser::types::{
Directive, Field, FragmentDefinition, FragmentSpread, InlineFragment, OperationDefinition,
VariableDefinition,
};
use crate::validation::visitor::Visitor;
use crate::VisitorContext;
use crate::{Name, Positioned};
use std::collections::HashSet;
#[derive(Default)]
pub struct DirectivesUnique;
impl<'a> Visitor<'a> for DirectivesUnique {
fn enter_operation_definition(
&mut self,
ctx: &mut VisitorContext<'a>,
_name: Option<&'a Name>,
operation_definition: &'a Positioned<OperationDefinition>,
) {
check_duplicate_directive(ctx, &operation_definition.node.directives);
}
fn enter_fragment_definition(
&mut self,
ctx: &mut VisitorContext<'a>,
_name: &'a Name,
fragment_definition: &'a Positioned<FragmentDefinition>,
) {
check_duplicate_directive(ctx, &fragment_definition.node.directives);
}
fn enter_variable_definition(
&mut self,
ctx: &mut VisitorContext<'a>,
variable_definition: &'a Positioned<VariableDefinition>,
) {
check_duplicate_directive(ctx, &variable_definition.node.directives);
}
fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned<Field>) {
check_duplicate_directive(ctx, &field.node.directives);
}
fn enter_fragment_spread(
&mut self,
ctx: &mut VisitorContext<'a>,
fragment_spread: &'a Positioned<FragmentSpread>,
) {
check_duplicate_directive(ctx, &fragment_spread.node.directives);
}
fn enter_inline_fragment(
&mut self,
ctx: &mut VisitorContext<'a>,
inline_fragment: &'a Positioned<InlineFragment>,
) {
check_duplicate_directive(ctx, &inline_fragment.node.directives);
}
}
fn check_duplicate_directive(ctx: &mut VisitorContext<'_>, directives: &[Positioned<Directive>]) {
let mut exists = HashSet::new();
for directive in directives {
let name = &directive.node.name.node;
if let Some(meta_directive) = ctx.registry.directives.get(name.as_str()) {
if !meta_directive.is_repeatable {
if exists.contains(name) {
ctx.report_error(
vec![directive.pos],
format!("Duplicate directive \"{}\"", name),
);
continue;
}
exists.insert(name);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
pub fn factory() -> DirectivesUnique {
DirectivesUnique
}
#[test]
fn skip_on_field() {
expect_passes_rule!(
factory,
r#"
{
dog {
name @skip(if: true)
}
}
"#,
);
}
#[test]
fn duplicate_skip_on_field() {
expect_fails_rule!(
factory,
r#"
{
dog {
name @skip(if: true) @skip(if: false)
}
}
"#,
);
}
#[test]
fn skip_on_fragment_spread() {
expect_passes_rule!(
factory,
r#"
fragment A on Dog {
name
}
query {
dog ... A @skip(if: true)
}
"#,
);
}
#[test]
fn duplicate_skip_on_fragment_spread() {
expect_fails_rule!(
factory,
r#"
fragment A on Dog {
name
}
query {
dog ... A @skip(if: true) @skip(if: false)
}
"#,
);
}
#[test]
fn skip_on_inline_fragment() {
expect_passes_rule!(
factory,
r#"
query {
dog ... @skip(if: true) {
name
}
}
"#,
);
}
#[test]
fn duplicate_skip_on_inline_fragment() {
expect_fails_rule!(
factory,
r#"
query {
dog ... @skip(if: true) @skip(if: false) {
name
}
}
"#,
);
}
}

View File

@ -1,3 +1,4 @@
use async_graphql_value::Value;
use indexmap::map::IndexMap;
use crate::parser::types::{Directive, Field};
@ -5,7 +6,6 @@ use crate::registry::MetaInputValue;
use crate::validation::suggestion::make_suggestion;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Name, Positioned};
use async_graphql_value::Value;
enum ArgsType<'a> {
Directive(&'a str),

View File

@ -1,5 +1,6 @@
mod arguments_of_correct_type;
mod default_values_of_correct_type;
mod directives_unique;
mod fields_on_correct_type;
mod fragments_on_composite_types;
mod known_argument_names;
@ -22,6 +23,7 @@ mod variables_in_allowed_position;
pub use arguments_of_correct_type::ArgumentsOfCorrectType;
pub use default_values_of_correct_type::DefaultValuesOfCorrectType;
pub use directives_unique::DirectivesUnique;
pub use fields_on_correct_type::FieldsOnCorrectType;
pub use fragments_on_composite_types::FragmentsOnCompositeTypes;
pub use known_argument_names::KnownArgumentNames;

View File

@ -1,12 +1,13 @@
use std::collections::{HashMap, HashSet};
use async_graphql_value::Value;
use crate::parser::types::{
ExecutableDocument, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition,
};
use crate::validation::utils::{referenced_variables, Scope};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Name, Pos, Positioned};
use async_graphql_value::Value;
#[derive(Default)]
pub struct NoUnusedVariables<'a> {

View File

@ -1,9 +1,10 @@
use std::collections::HashSet;
use async_graphql_value::Value;
use crate::parser::types::{Directive, Field};
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Name, Positioned};
use async_graphql_value::Value;
#[derive(Default)]
pub struct UniqueArgumentNames<'a> {

View File

@ -1,5 +1,7 @@
use std::collections::{HashMap, HashSet};
use async_graphql_value::Value;
use crate::parser::types::{
ExecutableDocument, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition,
};
@ -7,7 +9,6 @@ use crate::registry::MetaTypeName;
use crate::validation::utils::Scope;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{Name, Pos, Positioned};
use async_graphql_value::Value;
#[derive(Default)]
pub struct VariableInAllowedPosition<'a> {