add PossibleFragmentSpreads rule

This commit is contained in:
sunli 2020-03-10 18:07:47 +08:00
parent 78faced65f
commit de96634f1f
7 changed files with 91 additions and 8 deletions

View File

@ -102,7 +102,7 @@ Open `http://localhost:8000` in browser
- [X] NoUnusedFragments
- [X] NoUnusedVariables
- [ ] OverlappingFieldsCanBeMerged
- [ ] PossibleFragmentSpreads
- [X] PossibleFragmentSpreads
- [ ] ProvidedNonNullArguments
- [X] ScalarLeafs
- [X] UniqueArgumentNames

View File

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

View File

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

View File

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

View File

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

View File

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

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