bluejay_validator/executable/document/rules/
fragment_spread_is_possible.rs

1use crate::executable::{
2    document::{Error, Path, Rule, Visitor},
3    Cache,
4};
5use bluejay_core::definition::{
6    ObjectTypeDefinition, SchemaDefinition, TypeDefinitionReference, UnionMemberType,
7    UnionTypeDefinition,
8};
9use bluejay_core::executable::{
10    ExecutableDocument, FragmentDefinition, FragmentSpread, InlineFragment,
11};
12use bluejay_core::AsIter;
13use std::collections::HashSet;
14
15pub struct FragmentSpreadIsPossible<'a, E: ExecutableDocument, S: SchemaDefinition> {
16    errors: Vec<Error<'a, E, S>>,
17    cache: &'a Cache<'a, E, S>,
18    schema_definition: &'a S,
19}
20
21impl<'a, E: ExecutableDocument, S: SchemaDefinition> Visitor<'a, E, S>
22    for FragmentSpreadIsPossible<'a, E, S>
23{
24    fn new(_: &'a E, schema_definition: &'a S, cache: &'a Cache<'a, E, S>) -> Self {
25        Self {
26            errors: Vec::new(),
27            cache,
28            schema_definition,
29        }
30    }
31
32    fn visit_fragment_spread(
33        &mut self,
34        fragment_spread: &'a <E as ExecutableDocument>::FragmentSpread,
35        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
36        _path: &Path<'a, E>,
37    ) {
38        if let Some(fragment_definition) = self.cache.fragment_definition(fragment_spread.name()) {
39            if let Some(fragment_type) = self
40                .schema_definition
41                .get_type_definition(fragment_definition.type_condition())
42            {
43                if self.spread_is_not_possible(parent_type, fragment_type) {
44                    self.errors.push(Error::FragmentSpreadIsNotPossible {
45                        fragment_spread,
46                        parent_type,
47                    });
48                }
49            }
50        }
51    }
52
53    fn visit_inline_fragment(
54        &mut self,
55        inline_fragment: &'a <E as ExecutableDocument>::InlineFragment,
56        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
57    ) {
58        if let Some(type_condition) = inline_fragment.type_condition() {
59            if let Some(fragment_type) = self.schema_definition.get_type_definition(type_condition)
60            {
61                if self.spread_is_not_possible(parent_type, fragment_type) {
62                    self.errors.push(Error::InlineFragmentSpreadIsNotPossible {
63                        inline_fragment,
64                        parent_type,
65                    });
66                }
67            }
68        }
69    }
70}
71
72impl<'a, E: ExecutableDocument, S: SchemaDefinition> FragmentSpreadIsPossible<'a, E, S> {
73    fn get_possible_types(
74        &self,
75        t: TypeDefinitionReference<'a, S::TypeDefinition>,
76    ) -> Option<HashSet<&'a str>> {
77        match t {
78            TypeDefinitionReference::Object(_) => Some(HashSet::from([t.name()])),
79            TypeDefinitionReference::Interface(itd) => Some(HashSet::from_iter(
80                self.schema_definition
81                    .get_interface_implementors(itd)
82                    .map(ObjectTypeDefinition::name),
83            )),
84            TypeDefinitionReference::Union(utd) => Some(HashSet::from_iter(
85                utd.union_member_types()
86                    .iter()
87                    .map(|union_member| union_member.name()),
88            )),
89            TypeDefinitionReference::BuiltinScalar(_)
90            | TypeDefinitionReference::CustomScalar(_)
91            | TypeDefinitionReference::Enum(_)
92            | TypeDefinitionReference::InputObject(_) => None,
93        }
94    }
95
96    fn spread_is_not_possible(
97        &self,
98        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
99        fragment_type: TypeDefinitionReference<'a, S::TypeDefinition>,
100    ) -> bool {
101        let parent_type_possible_types = self.get_possible_types(parent_type);
102        let fragment_possible_types = self.get_possible_types(fragment_type);
103
104        matches!(
105            (parent_type_possible_types, fragment_possible_types),
106            (Some(parent_type_possible_types), Some(fragment_possible_types)) if parent_type_possible_types
107                .intersection(&fragment_possible_types)
108                .next()
109                .is_none(),
110        )
111    }
112}
113
114impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Rule<'a, E, S>
115    for FragmentSpreadIsPossible<'a, E, S>
116{
117    type Error = Error<'a, E, S>;
118    type Errors = std::vec::IntoIter<Error<'a, E, S>>;
119
120    fn into_errors(self) -> Self::Errors {
121        self.errors.into_iter()
122    }
123}