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 {
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}