async-graphql/src/validation/rules/possible_fragment_spreads.rs

348 lines
9.4 KiB
Rust
Raw Normal View History

use crate::parser::types::{ExecutableDocument, FragmentSpread, InlineFragment, TypeCondition};
2020-03-24 10:54:22 +00:00
use crate::validation::visitor::{Visitor, VisitorContext};
2020-05-10 02:59:51 +00:00
use crate::Positioned;
2020-03-10 10:07:47 +00:00
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 VisitorContext<'a>, doc: &'a ExecutableDocument) {
for (name, fragment) in doc.fragments.iter() {
self.fragment_types
.insert(name.as_str(), &fragment.node.type_condition.node.on.node);
2020-03-10 10:07:47 +00:00
}
}
fn enter_fragment_spread(
&mut self,
2020-03-22 08:45:59 +00:00
ctx: &mut VisitorContext<'a>,
2020-05-10 02:59:51 +00:00
fragment_spread: &'a Positioned<FragmentSpread>,
2020-03-10 10:07:47 +00:00
) {
2020-05-16 13:14:26 +00:00
if let Some(fragment_type) = self
.fragment_types
2020-09-06 05:38:31 +00:00
.get(&*fragment_spread.node.fragment_name.node)
2020-05-16 13:14:26 +00:00
{
2020-04-05 08:00:26 +00:00
if let Some(current_type) = ctx.current_type() {
if let Some(on_type) = ctx.registry.types.get(*fragment_type) {
if !current_type.type_overlap(on_type) {
ctx.report_error(
2020-09-06 05:38:31 +00:00
vec![fragment_spread.pos],
2020-04-05 08:00:26 +00:00
format!(
"Fragment \"{}\" cannot be spread here as objects of type \"{}\" can never be of type \"{}\"",
2020-09-06 05:38:31 +00:00
fragment_spread.node.fragment_name.node, current_type.name(), fragment_type
2020-04-05 08:00:26 +00:00
),
);
}
}
2020-03-10 10:07:47 +00:00
}
}
}
fn enter_inline_fragment(
&mut self,
2020-03-22 08:45:59 +00:00
ctx: &mut VisitorContext<'a>,
2020-05-10 02:59:51 +00:00
inline_fragment: &'a Positioned<InlineFragment>,
2020-03-10 10:07:47 +00:00
) {
if let Some(parent_type) = ctx.parent_type() {
2020-09-06 06:16:36 +00:00
if let Some(TypeCondition { on: fragment_type }) = &inline_fragment
.node
.type_condition
.as_ref()
.map(|c| &c.node)
{
if let Some(on_type) = ctx.registry.types.get(fragment_type.node.as_str()) {
2020-04-05 08:00:26 +00:00
if !parent_type.type_overlap(&on_type) {
ctx.report_error(
2020-09-06 05:38:31 +00:00
vec![inline_fragment.pos],
2020-04-05 08:00:26 +00:00
format!(
"Fragment cannot be spread here as objects of type \"{}\" \
2020-03-10 10:07:47 +00:00
can never be of type \"{}\"",
2020-04-05 08:00:26 +00:00
parent_type.name(),
fragment_type
),
)
}
2020-03-10 10:07:47 +00:00
}
}
}
}
}
2020-04-05 08:00:26 +00:00
#[cfg(test)]
mod tests {
use super::*;
pub fn factory<'a>() -> PossibleFragmentSpreads<'a> {
PossibleFragmentSpreads::default()
}
#[test]
fn of_the_same_object() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment objectWithinObject on Dog { ...dogFragment }
fragment dogFragment on Dog { barkVolume }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn of_the_same_object_with_inline_fragment() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment objectWithinObjectAnon on Dog { ... on Dog { barkVolume } }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn object_into_an_implemented_interface() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment objectWithinInterface on Pet { ...dogFragment }
fragment dogFragment on Dog { barkVolume }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn object_into_containing_union() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment objectWithinUnion on CatOrDog { ...dogFragment }
fragment dogFragment on Dog { barkVolume }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn union_into_contained_object() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment unionWithinObject on Dog { ...catOrDogFragment }
fragment catOrDogFragment on CatOrDog { __typename }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn union_into_overlapping_interface() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment unionWithinInterface on Pet { ...catOrDogFragment }
fragment catOrDogFragment on CatOrDog { __typename }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn union_into_overlapping_union() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment unionWithinUnion on DogOrHuman { ...catOrDogFragment }
fragment catOrDogFragment on CatOrDog { __typename }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn interface_into_implemented_object() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment interfaceWithinObject on Dog { ...petFragment }
fragment petFragment on Pet { name }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn interface_into_overlapping_interface() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment interfaceWithinInterface on Pet { ...beingFragment }
fragment beingFragment on Being { name }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn interface_into_overlapping_interface_in_inline_fragment() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment interfaceWithinInterface on Pet { ... on Being { name } }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn interface_into_overlapping_union() {
2020-05-29 09:29:15 +00:00
expect_passes_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment interfaceWithinUnion on CatOrDog { ...petFragment }
fragment petFragment on Pet { name }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn different_object_into_object() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment invalidObjectWithinObject on Cat { ...dogFragment }
fragment dogFragment on Dog { barkVolume }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn different_object_into_object_in_inline_fragment() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment invalidObjectWithinObjectAnon on Cat {
... on Dog { barkVolume }
}
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn object_into_not_implementing_interface() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment invalidObjectWithinInterface on Pet { ...humanFragment }
fragment humanFragment on Human { pets { name } }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn object_into_not_containing_union() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment invalidObjectWithinUnion on CatOrDog { ...humanFragment }
fragment humanFragment on Human { pets { name } }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn union_into_not_contained_object() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment invalidUnionWithinObject on Human { ...catOrDogFragment }
fragment catOrDogFragment on CatOrDog { __typename }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn union_into_non_overlapping_interface() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment invalidUnionWithinInterface on Pet { ...humanOrAlienFragment }
fragment humanOrAlienFragment on HumanOrAlien { __typename }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn union_into_non_overlapping_union() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment invalidUnionWithinUnion on CatOrDog { ...humanOrAlienFragment }
fragment humanOrAlienFragment on HumanOrAlien { __typename }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn interface_into_non_implementing_object() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment invalidInterfaceWithinObject on Cat { ...intelligentFragment }
fragment intelligentFragment on Intelligent { iq }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn interface_into_non_overlapping_interface() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment invalidInterfaceWithinInterface on Pet {
...intelligentFragment
}
fragment intelligentFragment on Intelligent { iq }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn interface_into_non_overlapping_interface_in_inline_fragment() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment invalidInterfaceWithinInterfaceAnon on Pet {
...on Intelligent { iq }
}
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
#[test]
fn interface_into_non_overlapping_union() {
2020-05-29 09:29:15 +00:00
expect_fails_rule!(
2020-04-05 08:00:26 +00:00
factory,
r#"
fragment invalidInterfaceWithinUnion on HumanOrAlien { ...petFragment }
fragment petFragment on Pet { name }
{ __typename }
2020-04-05 08:00:26 +00:00
"#,
);
}
}