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