async_graphql/validation/rules/
possible_fragment_spreads.rs

1use std::collections::HashMap;
2
3use crate::{
4    Positioned,
5    parser::types::{ExecutableDocument, FragmentSpread, InlineFragment, TypeCondition},
6    validation::visitor::{Visitor, VisitorContext},
7};
8
9#[derive(Default)]
10pub struct PossibleFragmentSpreads<'a> {
11    fragment_types: HashMap<&'a str, &'a str>,
12}
13
14impl<'a> Visitor<'a> for PossibleFragmentSpreads<'a> {
15    fn enter_document(&mut self, _ctx: &mut VisitorContext<'a>, doc: &'a ExecutableDocument) {
16        for (name, fragment) in doc.fragments.iter() {
17            self.fragment_types
18                .insert(name.as_str(), &fragment.node.type_condition.node.on.node);
19        }
20    }
21
22    fn enter_fragment_spread(
23        &mut self,
24        ctx: &mut VisitorContext<'a>,
25        fragment_spread: &'a Positioned<FragmentSpread>,
26    ) {
27        if let Some(fragment_type) = self
28            .fragment_types
29            .get(&*fragment_spread.node.fragment_name.node)
30            && let Some(current_type) = ctx.current_type()
31            && let Some(on_type) = ctx.registry.types.get(*fragment_type)
32            && !current_type.type_overlap(on_type)
33        {
34            ctx.report_error(
35                            vec![fragment_spread.pos],
36                            format!(
37                                "Fragment \"{}\" cannot be spread here as objects of type \"{}\" can never be of type \"{}\"",
38                                fragment_spread.node.fragment_name.node, current_type.name(), fragment_type
39                            ),
40                        );
41        }
42    }
43
44    fn enter_inline_fragment(
45        &mut self,
46        ctx: &mut VisitorContext<'a>,
47        inline_fragment: &'a Positioned<InlineFragment>,
48    ) {
49        if let Some(parent_type) = ctx.parent_type()
50            && let Some(TypeCondition { on: fragment_type }) = &inline_fragment
51                .node
52                .type_condition
53                .as_ref()
54                .map(|c| &c.node)
55            && let Some(on_type) = ctx.registry.types.get(fragment_type.node.as_str())
56            && !parent_type.type_overlap(&on_type)
57        {
58            ctx.report_error(
59                vec![inline_fragment.pos],
60                format!(
61                    "Fragment cannot be spread here as objects of type \"{}\" \
62             can never be of type \"{}\"",
63                    parent_type.name(),
64                    fragment_type
65                ),
66            )
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    pub fn factory<'a>() -> PossibleFragmentSpreads<'a> {
76        PossibleFragmentSpreads::default()
77    }
78
79    #[test]
80    fn of_the_same_object() {
81        expect_passes_rule!(
82            factory,
83            r#"
84          fragment objectWithinObject on Dog { ...dogFragment }
85          fragment dogFragment on Dog { barkVolume }
86          { __typename }
87        "#,
88        );
89    }
90
91    #[test]
92    fn of_the_same_object_with_inline_fragment() {
93        expect_passes_rule!(
94            factory,
95            r#"
96          fragment objectWithinObjectAnon on Dog { ... on Dog { barkVolume } }
97          { __typename }
98        "#,
99        );
100    }
101
102    #[test]
103    fn object_into_an_implemented_interface() {
104        expect_passes_rule!(
105            factory,
106            r#"
107          fragment objectWithinInterface on Pet { ...dogFragment }
108          fragment dogFragment on Dog { barkVolume }
109          { __typename }
110        "#,
111        );
112    }
113
114    #[test]
115    fn object_into_containing_union() {
116        expect_passes_rule!(
117            factory,
118            r#"
119          fragment objectWithinUnion on CatOrDog { ...dogFragment }
120          fragment dogFragment on Dog { barkVolume }
121          { __typename }
122        "#,
123        );
124    }
125
126    #[test]
127    fn union_into_contained_object() {
128        expect_passes_rule!(
129            factory,
130            r#"
131          fragment unionWithinObject on Dog { ...catOrDogFragment }
132          fragment catOrDogFragment on CatOrDog { __typename }
133          { __typename }
134        "#,
135        );
136    }
137
138    #[test]
139    fn union_into_overlapping_interface() {
140        expect_passes_rule!(
141            factory,
142            r#"
143          fragment unionWithinInterface on Pet { ...catOrDogFragment }
144          fragment catOrDogFragment on CatOrDog { __typename }
145          { __typename }
146        "#,
147        );
148    }
149
150    #[test]
151    fn union_into_overlapping_union() {
152        expect_passes_rule!(
153            factory,
154            r#"
155          fragment unionWithinUnion on DogOrHuman { ...catOrDogFragment }
156          fragment catOrDogFragment on CatOrDog { __typename }
157          { __typename }
158        "#,
159        );
160    }
161
162    #[test]
163    fn interface_into_implemented_object() {
164        expect_passes_rule!(
165            factory,
166            r#"
167          fragment interfaceWithinObject on Dog { ...petFragment }
168          fragment petFragment on Pet { name }
169          { __typename }
170        "#,
171        );
172    }
173
174    #[test]
175    fn interface_into_overlapping_interface() {
176        expect_passes_rule!(
177            factory,
178            r#"
179          fragment interfaceWithinInterface on Pet { ...beingFragment }
180          fragment beingFragment on Being { name }
181          { __typename }
182        "#,
183        );
184    }
185
186    #[test]
187    fn interface_into_overlapping_interface_in_inline_fragment() {
188        expect_passes_rule!(
189            factory,
190            r#"
191          fragment interfaceWithinInterface on Pet { ... on Being { name } }
192          { __typename }
193        "#,
194        );
195    }
196
197    #[test]
198    fn interface_into_overlapping_union() {
199        expect_passes_rule!(
200            factory,
201            r#"
202          fragment interfaceWithinUnion on CatOrDog { ...petFragment }
203          fragment petFragment on Pet { name }
204          { __typename }
205        "#,
206        );
207    }
208
209    #[test]
210    fn different_object_into_object() {
211        expect_fails_rule!(
212            factory,
213            r#"
214          fragment invalidObjectWithinObject on Cat { ...dogFragment }
215          fragment dogFragment on Dog { barkVolume }
216          { __typename }
217        "#,
218        );
219    }
220
221    #[test]
222    fn different_object_into_object_in_inline_fragment() {
223        expect_fails_rule!(
224            factory,
225            r#"
226          fragment invalidObjectWithinObjectAnon on Cat {
227            ... on Dog { barkVolume }
228          }
229          { __typename }
230        "#,
231        );
232    }
233
234    #[test]
235    fn object_into_not_implementing_interface() {
236        expect_fails_rule!(
237            factory,
238            r#"
239          fragment invalidObjectWithinInterface on Pet { ...humanFragment }
240          fragment humanFragment on Human { pets { name } }
241          { __typename }
242        "#,
243        );
244    }
245
246    #[test]
247    fn object_into_not_containing_union() {
248        expect_fails_rule!(
249            factory,
250            r#"
251          fragment invalidObjectWithinUnion on CatOrDog { ...humanFragment }
252          fragment humanFragment on Human { pets { name } }
253          { __typename }
254        "#,
255        );
256    }
257
258    #[test]
259    fn union_into_not_contained_object() {
260        expect_fails_rule!(
261            factory,
262            r#"
263          fragment invalidUnionWithinObject on Human { ...catOrDogFragment }
264          fragment catOrDogFragment on CatOrDog { __typename }
265          { __typename }
266        "#,
267        );
268    }
269
270    #[test]
271    fn union_into_non_overlapping_interface() {
272        expect_fails_rule!(
273            factory,
274            r#"
275          fragment invalidUnionWithinInterface on Pet { ...humanOrAlienFragment }
276          fragment humanOrAlienFragment on HumanOrAlien { __typename }
277          { __typename }
278        "#,
279        );
280    }
281
282    #[test]
283    fn union_into_non_overlapping_union() {
284        expect_fails_rule!(
285            factory,
286            r#"
287          fragment invalidUnionWithinUnion on CatOrDog { ...humanOrAlienFragment }
288          fragment humanOrAlienFragment on HumanOrAlien { __typename }
289          { __typename }
290        "#,
291        );
292    }
293
294    #[test]
295    fn interface_into_non_implementing_object() {
296        expect_fails_rule!(
297            factory,
298            r#"
299          fragment invalidInterfaceWithinObject on Cat { ...intelligentFragment }
300          fragment intelligentFragment on Intelligent { iq }
301          { __typename }
302        "#,
303        );
304    }
305
306    #[test]
307    fn interface_into_non_overlapping_interface() {
308        expect_fails_rule!(
309            factory,
310            r#"
311          fragment invalidInterfaceWithinInterface on Pet {
312            ...intelligentFragment
313          }
314          fragment intelligentFragment on Intelligent { iq }
315          { __typename }
316        "#,
317        );
318    }
319
320    #[test]
321    fn interface_into_non_overlapping_interface_in_inline_fragment() {
322        expect_fails_rule!(
323            factory,
324            r#"
325          fragment invalidInterfaceWithinInterfaceAnon on Pet {
326            ...on Intelligent { iq }
327          }
328          { __typename }
329        "#,
330        );
331    }
332
333    #[test]
334    fn interface_into_non_overlapping_union() {
335        expect_fails_rule!(
336            factory,
337            r#"
338          fragment invalidInterfaceWithinUnion on HumanOrAlien { ...petFragment }
339          fragment petFragment on Pet { name }
340          { __typename }
341        "#,
342        );
343    }
344}