add PossibleFragmentSpreads rule
This commit is contained in:
parent
78faced65f
commit
de96634f1f
|
@ -102,7 +102,7 @@ Open `http://localhost:8000` in browser
|
|||
- [X] NoUnusedFragments
|
||||
- [X] NoUnusedVariables
|
||||
- [ ] OverlappingFieldsCanBeMerged
|
||||
- [ ] PossibleFragmentSpreads
|
||||
- [X] PossibleFragmentSpreads
|
||||
- [ ] ProvidedNonNullArguments
|
||||
- [X] ScalarLeafs
|
||||
- [X] UniqueArgumentNames
|
||||
|
|
|
@ -55,7 +55,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
|||
registry.add_implements(&<#p as #crate_name::GQLType>::type_name(), #gql_typename);
|
||||
});
|
||||
possible_types.push(quote! {
|
||||
<#p as #crate_name::GQLType>::type_name().to_string()
|
||||
possible_types.insert(<#p as #crate_name::GQLType>::type_name().to_string());
|
||||
});
|
||||
inline_fragment_resolvers.push(quote! {
|
||||
if name == <#p as #crate_name::GQLType>::type_name() {
|
||||
|
@ -235,7 +235,11 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
|||
#(#schema_fields)*
|
||||
fields
|
||||
},
|
||||
possible_types: vec![#(#possible_types),*],
|
||||
possible_types: {
|
||||
let mut possible_types = std::collections::HashSet::new();
|
||||
#(#possible_types)*
|
||||
possible_types
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
|||
<#p as #crate_name::GQLType>::create_type_info(registry);
|
||||
});
|
||||
possible_types.push(quote! {
|
||||
<#p as #crate_name::GQLType>::type_name().to_string()
|
||||
possible_types.insert(<#p as #crate_name::GQLType>::type_name().to_string());
|
||||
});
|
||||
inline_fragment_resolvers.push(quote! {
|
||||
if name == <#p as #crate_name::GQLType>::type_name() {
|
||||
|
@ -83,7 +83,11 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
|||
#crate_name::registry::Type::Union {
|
||||
name: #gql_typename,
|
||||
description: #desc,
|
||||
possible_types: vec![#(#possible_types),*],
|
||||
possible_types: {
|
||||
let mut possible_types = std::collections::HashSet::new();
|
||||
#(#possible_types)*
|
||||
possible_types
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -79,12 +79,12 @@ pub enum Type {
|
|||
name: &'static str,
|
||||
description: Option<&'static str>,
|
||||
fields: HashMap<&'static str, Field>,
|
||||
possible_types: Vec<String>,
|
||||
possible_types: HashSet<String>,
|
||||
},
|
||||
Union {
|
||||
name: &'static str,
|
||||
description: Option<&'static str>,
|
||||
possible_types: Vec<String>,
|
||||
possible_types: HashSet<String>,
|
||||
},
|
||||
Enum {
|
||||
name: &'static str,
|
||||
|
@ -147,6 +147,14 @@ impl Type {
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_possible_type(&self, type_name: &str) -> bool {
|
||||
match self {
|
||||
Type::Interface { possible_types, .. } => possible_types.contains(type_name),
|
||||
Type::Union { possible_types, .. } => possible_types.contains(type_name),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Directive {
|
||||
|
|
|
@ -31,7 +31,8 @@ pub fn check_rules(registry: &Registry, doc: &Document) -> Result<()> {
|
|||
.with(rules::UniqueVariableNames::default())
|
||||
.with(rules::VariablesAreInputTypes)
|
||||
.with(rules::VariableInAllowedPosition::default())
|
||||
.with(rules::ScalarLeafs);
|
||||
.with(rules::ScalarLeafs)
|
||||
.with(rules::PossibleFragmentSpreads::default());
|
||||
|
||||
visit(&mut visitor, &mut ctx, doc);
|
||||
if !ctx.errors.is_empty() {
|
||||
|
|
|
@ -10,6 +10,7 @@ mod no_fragment_cycles;
|
|||
mod no_undefined_variables;
|
||||
mod no_unused_fragments;
|
||||
mod no_unused_variables;
|
||||
mod possible_fragment_spreads;
|
||||
mod scalar_leafs;
|
||||
mod unique_argument_names;
|
||||
mod unique_fragment_names;
|
||||
|
@ -30,6 +31,7 @@ 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 possible_fragment_spreads::PossibleFragmentSpreads;
|
||||
pub use scalar_leafs::ScalarLeafs;
|
||||
pub use unique_argument_names::UniqueArgumentNames;
|
||||
pub use unique_fragment_names::UniqueFragmentNames;
|
||||
|
|
64
src/validation/rules/possible_fragment_spreads.rs
Normal file
64
src/validation/rules/possible_fragment_spreads.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use crate::validation::context::ValidatorContext;
|
||||
use crate::validation::visitor::Visitor;
|
||||
use graphql_parser::query::{Definition, Document, FragmentSpread, InlineFragment, TypeCondition};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PossibleFragmentSpreads<'a> {
|
||||
fragment_types: HashMap<&'a str, &'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for PossibleFragmentSpreads<'a> {
|
||||
fn enter_document(&mut self, _ctx: &mut ValidatorContext<'a>, doc: &'a Document) {
|
||||
for d in &doc.definitions {
|
||||
if let Definition::Fragment(fragment) = d {
|
||||
let TypeCondition::On(type_name) = &fragment.type_condition;
|
||||
self.fragment_types
|
||||
.insert(fragment.name.as_str(), type_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enter_fragment_spread(
|
||||
&mut self,
|
||||
ctx: &mut ValidatorContext<'a>,
|
||||
fragment_spread: &'a FragmentSpread,
|
||||
) {
|
||||
if let Some(fragment_type) = self
|
||||
.fragment_types
|
||||
.get(fragment_spread.fragment_name.as_str())
|
||||
{
|
||||
if ctx.current_type().name() != *fragment_type {
|
||||
ctx.report_error(
|
||||
vec![fragment_spread.position],
|
||||
format!(
|
||||
"Fragment \"{}\" cannot be spread here as objects of type \"{}\" can never be of type \"{}\"",
|
||||
&fragment_spread.fragment_name, ctx.current_type().name(), fragment_type
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enter_inline_fragment(
|
||||
&mut self,
|
||||
ctx: &mut ValidatorContext<'a>,
|
||||
inline_fragment: &'a InlineFragment,
|
||||
) {
|
||||
if let Some(parent_type) = ctx.parent_type() {
|
||||
if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition {
|
||||
if !parent_type.is_possible_type(&name) {
|
||||
ctx.report_error(
|
||||
vec![inline_fragment.position],
|
||||
format!(
|
||||
"Fragment cannot be spread here as objects of type \"{}\" \
|
||||
can never be of type \"{}\"",
|
||||
parent_type.name(),
|
||||
name
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user