Skip to main content

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;
13
14pub struct FragmentSpreadIsPossible<'a, E: ExecutableDocument, S: SchemaDefinition> {
15    errors: Vec<Error<'a, E, S>>,
16    cache: &'a Cache<'a, E, S>,
17    schema_definition: &'a S,
18}
19
20impl<'a, E: ExecutableDocument, S: SchemaDefinition> Visitor<'a, E, S>
21    for FragmentSpreadIsPossible<'a, E, S>
22{
23    fn new(_: &'a E, schema_definition: &'a S, cache: &'a Cache<'a, E, S>) -> Self {
24        Self {
25            errors: Vec::new(),
26            cache,
27            schema_definition,
28        }
29    }
30
31    fn visit_fragment_spread(
32        &mut self,
33        fragment_spread: &'a <E as ExecutableDocument>::FragmentSpread,
34        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
35        _path: &Path<'a, E>,
36    ) {
37        if let Some(fragment_definition) = self.cache.fragment_definition(fragment_spread.name()) {
38            if let Some(fragment_type) = self
39                .schema_definition
40                .get_type_definition(fragment_definition.type_condition())
41            {
42                if self.spread_is_not_possible(parent_type, fragment_type) {
43                    self.errors.push(Error::FragmentSpreadIsNotPossible {
44                        fragment_spread,
45                        parent_type,
46                    });
47                }
48            }
49        }
50    }
51
52    fn visit_inline_fragment(
53        &mut self,
54        inline_fragment: &'a <E as ExecutableDocument>::InlineFragment,
55        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
56    ) {
57        if let Some(type_condition) = inline_fragment.type_condition() {
58            if let Some(fragment_type) = self.schema_definition.get_type_definition(type_condition)
59            {
60                if self.spread_is_not_possible(parent_type, fragment_type) {
61                    self.errors.push(Error::InlineFragmentSpreadIsNotPossible {
62                        inline_fragment,
63                        parent_type,
64                    });
65                }
66            }
67        }
68    }
69}
70
71impl<'a, E: ExecutableDocument, S: SchemaDefinition> FragmentSpreadIsPossible<'a, E, S> {
72    fn spread_is_not_possible(
73        &self,
74        parent_type: TypeDefinitionReference<'a, S::TypeDefinition>,
75        fragment_type: TypeDefinitionReference<'a, S::TypeDefinition>,
76    ) -> bool {
77        // Fast path: if either type is not a composite type, spread is not applicable
78        if !parent_type.is_composite() || !fragment_type.is_composite() {
79            return false;
80        }
81
82        // Fast path: same type name means definitely possible
83        if parent_type.name() == fragment_type.name() {
84            return false;
85        }
86
87        // Fast path: if both are object types, they can only overlap if they're the same (already checked)
88        if matches!(parent_type, TypeDefinitionReference::Object(_))
89            && matches!(fragment_type, TypeDefinitionReference::Object(_))
90        {
91            return true;
92        }
93
94        // For mixed cases, check intersection of possible types
95        !self.types_have_overlap(parent_type, fragment_type)
96    }
97
98    fn type_contains_name(
99        &self,
100        t: TypeDefinitionReference<'a, S::TypeDefinition>,
101        name: &str,
102    ) -> bool {
103        match t {
104            TypeDefinitionReference::Object(_) => t.name() == name,
105            TypeDefinitionReference::Interface(itd) => self
106                .schema_definition
107                .get_interface_implementors(itd)
108                .any(|otd| ObjectTypeDefinition::name(otd) == name),
109            TypeDefinitionReference::Union(utd) => utd
110                .union_member_types()
111                .iter()
112                .any(|member| member.name() == name),
113            _ => false,
114        }
115    }
116
117    fn types_have_overlap(
118        &self,
119        a: TypeDefinitionReference<'a, S::TypeDefinition>,
120        b: TypeDefinitionReference<'a, S::TypeDefinition>,
121    ) -> bool {
122        match (a, b) {
123            (TypeDefinitionReference::Object(o), other)
124            | (other, TypeDefinitionReference::Object(o)) => {
125                self.type_contains_name(other, ObjectTypeDefinition::name(o))
126            }
127            // Both are abstract — collect b's possible types once, then check a's against it
128            _ => {
129                let b_names: Vec<&str> = self.possible_type_names(b).collect();
130                self.possible_type_names(a)
131                    .any(|name| b_names.contains(&name))
132            }
133        }
134    }
135
136    fn possible_type_names(
137        &self,
138        t: TypeDefinitionReference<'a, S::TypeDefinition>,
139    ) -> impl Iterator<Item = &'a str> + '_ {
140        use itertools::Either;
141        match t {
142            TypeDefinitionReference::Interface(itd) => Either::Left(
143                self.schema_definition
144                    .get_interface_implementors(itd)
145                    .map(ObjectTypeDefinition::name),
146            ),
147            TypeDefinitionReference::Union(utd) => Either::Right(Either::Left(
148                utd.union_member_types().iter().map(|m| m.name()),
149            )),
150            _ => Either::Right(Either::Right(std::iter::empty())),
151        }
152    }
153}
154
155impl<'a, E: ExecutableDocument + 'a, S: SchemaDefinition + 'a> Rule<'a, E, S>
156    for FragmentSpreadIsPossible<'a, E, S>
157{
158    type Error = Error<'a, E, S>;
159    type Errors = std::vec::IntoIter<Error<'a, E, S>>;
160
161    fn into_errors(self) -> Self::Errors {
162        self.errors.into_iter()
163    }
164}