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