2020-05-15 02:38:48 +00:00
|
|
|
use crate::parser::query::{Definition, Document, 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> {
|
2020-03-22 08:45:59 +00:00
|
|
|
fn enter_document(&mut self, _ctx: &mut VisitorContext<'a>, doc: &'a Document) {
|
2020-05-12 08:27:06 +00:00
|
|
|
for d in doc.definitions() {
|
2020-05-09 09:55:04 +00:00
|
|
|
if let Definition::Fragment(fragment) = &d.node {
|
|
|
|
let TypeCondition::On(type_name) = &fragment.type_condition.node;
|
2020-05-16 13:14:26 +00:00
|
|
|
self.fragment_types
|
|
|
|
.insert(fragment.name.as_str(), type_name);
|
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
|
|
|
|
.get(fragment_spread.fragment_name.as_str())
|
|
|
|
{
|
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-05-09 09:55:04 +00:00
|
|
|
vec![fragment_spread.position()],
|
2020-04-05 08:00:26 +00:00
|
|
|
format!(
|
|
|
|
"Fragment \"{}\" cannot be spread here as objects of type \"{}\" can never be of type \"{}\"",
|
|
|
|
&fragment_spread.fragment_name, current_type.name(), fragment_type
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
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-05-09 09:55:04 +00:00
|
|
|
if let Some(TypeCondition::On(fragment_type)) =
|
|
|
|
&inline_fragment.type_condition.as_ref().map(|c| &c.node)
|
|
|
|
{
|
2020-05-16 13:14:26 +00:00
|
|
|
if let Some(on_type) = ctx.registry.types.get(fragment_type.as_str()) {
|
2020-04-05 08:00:26 +00:00
|
|
|
if !parent_type.type_overlap(&on_type) {
|
|
|
|
ctx.report_error(
|
2020-05-09 09:55:04 +00:00
|
|
|
vec![inline_fragment.position()],
|
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::*;
|
2020-05-29 09:29:15 +00:00
|
|
|
use crate::{expect_fails_rule, expect_passes_rule};
|
2020-04-05 08:00:26 +00:00
|
|
|
|
|
|
|
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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 } }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 } }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 } }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 } }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 }
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|