Add DirectivesUnique rule
This commit is contained in:
parent
eb9cda4c80
commit
e2c6ead1a3
|
@ -103,4 +103,9 @@ impl<'a> __Directive<'a> {
|
|||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn is_repeatable(&self) -> bool {
|
||||
self.directive.is_repeatable
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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> {
|
||||
|
|
176
src/validation/rules/directives_unique.rs
Normal file
176
src/validation/rules/directives_unique.rs
Normal 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
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Reference in New Issue
Block a user