async_graphql/validation/rules/
possible_fragment_spreads.rs1use 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}