async_graphql/validation/rules/
fields_on_correct_type.rs

1use crate::{
2    Positioned,
3    parser::types::Field,
4    registry,
5    validation::{
6        suggestion::make_suggestion,
7        visitor::{Visitor, VisitorContext},
8    },
9};
10
11#[derive(Default)]
12pub struct FieldsOnCorrectType;
13
14impl<'a> Visitor<'a> for FieldsOnCorrectType {
15    fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned<Field>) {
16        if let Some(parent_type) = ctx.parent_type() {
17            if let Some(registry::MetaType::Union { .. })
18            | Some(registry::MetaType::Interface { .. }) = ctx.parent_type()
19            {
20                if field.node.name.node == "__typename" {
21                    return;
22                }
23            }
24
25            if parent_type
26                .fields()
27                .and_then(|fields| fields.get(field.node.name.node.as_str()))
28                .is_none()
29                && !field
30                    .node
31                    .directives
32                    .iter()
33                    .any(|directive| directive.node.name.node == "ifdef")
34            {
35                ctx.report_error(
36                    vec![field.pos],
37                    format!(
38                        "Unknown field \"{}\" on type \"{}\".{}",
39                        field.node.name,
40                        parent_type.name(),
41                        if ctx.registry.enable_suggestions {
42                            make_suggestion(
43                                " Did you mean",
44                                parent_type
45                                    .fields()
46                                    .iter()
47                                    .map(|fields| fields.keys())
48                                    .flatten()
49                                    .map(String::as_str),
50                                &field.node.name.node,
51                            )
52                            .unwrap_or_default()
53                        } else {
54                            String::new()
55                        }
56                    ),
57                );
58            }
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    pub fn factory() -> FieldsOnCorrectType {
68        FieldsOnCorrectType
69    }
70
71    #[test]
72    fn selection_on_object() {
73        expect_passes_rule!(
74            factory,
75            r#"
76          fragment objectFieldSelection on Dog {
77            __typename
78            name
79          }
80          { __typename }
81        "#,
82        );
83    }
84
85    #[test]
86    fn aliased_selection_on_object() {
87        expect_passes_rule!(
88            factory,
89            r#"
90          fragment aliasedObjectFieldSelection on Dog {
91            tn : __typename
92            otherName : name
93          }
94          { __typename }
95        "#,
96        );
97    }
98
99    #[test]
100    fn selection_on_interface() {
101        expect_passes_rule!(
102            factory,
103            r#"
104          fragment interfaceFieldSelection on Pet {
105            __typename
106            name
107          }
108          { __typename }
109        "#,
110        );
111    }
112
113    #[test]
114    fn aliased_selection_on_interface() {
115        expect_passes_rule!(
116            factory,
117            r#"
118          fragment interfaceFieldSelection on Pet {
119            otherName : name
120          }
121          { __typename }
122        "#,
123        );
124    }
125
126    #[test]
127    fn lying_alias_selection() {
128        expect_passes_rule!(
129            factory,
130            r#"
131          fragment lyingAliasSelection on Dog {
132            name : nickname
133          }
134          { __typename }
135        "#,
136        );
137    }
138
139    #[test]
140    fn ignores_unknown_type() {
141        expect_passes_rule!(
142            factory,
143            r#"
144          fragment unknownSelection on UnknownType {
145            unknownField
146          }
147          { __typename }
148        "#,
149        );
150    }
151
152    #[test]
153    fn nested_unknown_fields() {
154        expect_fails_rule!(
155            factory,
156            r#"
157          fragment typeKnownAgain on Pet {
158            unknown_pet_field {
159              ... on Cat {
160                unknown_cat_field
161              }
162            }
163          }
164          { __typename }
165        "#,
166        );
167    }
168
169    #[test]
170    fn unknown_field_on_fragment() {
171        expect_fails_rule!(
172            factory,
173            r#"
174          fragment fieldNotDefined on Dog {
175            meowVolume
176          }
177          { __typename }
178        "#,
179        );
180    }
181
182    #[test]
183    fn ignores_deeply_unknown_field() {
184        expect_fails_rule!(
185            factory,
186            r#"
187          fragment deepFieldNotDefined on Dog {
188            unknown_field {
189              deeper_unknown_field
190            }
191          }
192          { __typename }
193        "#,
194        );
195    }
196
197    #[test]
198    fn unknown_subfield() {
199        expect_fails_rule!(
200            factory,
201            r#"
202          fragment subFieldNotDefined on Human {
203            pets {
204              unknown_field
205            }
206          }
207          { __typename }
208        "#,
209        );
210    }
211
212    #[test]
213    fn unknown_field_on_inline_fragment() {
214        expect_fails_rule!(
215            factory,
216            r#"
217          fragment fieldNotDefined on Pet {
218            ... on Dog {
219              meowVolume
220            }
221          }
222          { __typename }
223        "#,
224        );
225    }
226
227    #[test]
228    fn unknown_aliased_target() {
229        expect_fails_rule!(
230            factory,
231            r#"
232          fragment aliasedFieldTargetNotDefined on Dog {
233            volume : mooVolume
234          }
235          { __typename }
236        "#,
237        );
238    }
239
240    #[test]
241    fn unknown_aliased_lying_field_target() {
242        expect_fails_rule!(
243            factory,
244            r#"
245          fragment aliasedLyingFieldTargetNotDefined on Dog {
246            barkVolume : kawVolume
247          }
248          { __typename }
249        "#,
250        );
251    }
252
253    #[test]
254    fn not_defined_on_interface() {
255        expect_fails_rule!(
256            factory,
257            r#"
258          fragment notDefinedOnInterface on Pet {
259            tailLength
260          }
261          { __typename }
262        "#,
263        );
264    }
265
266    #[test]
267    fn defined_in_concrete_types_but_not_interface() {
268        expect_fails_rule!(
269            factory,
270            r#"
271          fragment definedOnImplementorsButNotInterface on Pet {
272            nickname
273          }
274          { __typename }
275        "#,
276        );
277    }
278
279    #[test]
280    fn meta_field_on_union() {
281        expect_passes_rule!(
282            factory,
283            r#"
284          fragment definedOnImplementorsButNotInterface on Pet {
285            __typename
286          }
287          { __typename }
288        "#,
289        );
290    }
291
292    #[test]
293    fn fields_on_union() {
294        expect_fails_rule!(
295            factory,
296            r#"
297          fragment definedOnImplementorsQueriedOnUnion on CatOrDog {
298            name
299          }
300          { __typename }
301        "#,
302        );
303    }
304
305    #[test]
306    fn typename_on_union() {
307        expect_passes_rule!(
308            factory,
309            r#"
310          fragment objectFieldSelection on Pet {
311            __typename
312            ... on Dog {
313              name
314            }
315            ... on Cat {
316              name
317            }
318          }
319          { __typename }
320        "#,
321        );
322    }
323
324    #[test]
325    fn valid_field_in_inline_fragment() {
326        expect_passes_rule!(
327            factory,
328            r#"
329          fragment objectFieldSelection on Pet {
330            ... on Dog {
331              name
332            }
333            ... {
334              name
335            }
336          }
337          { __typename }
338        "#,
339        );
340    }
341
342    #[test]
343    fn typename_in_subscription_root() {
344        expect_fails_rule!(factory, "subscription { __typename }");
345    }
346}