async_graphql/validation/rules/
known_argument_names.rs1use async_graphql_value::Value;
2use indexmap::map::IndexMap;
3
4use crate::{
5 Name, Positioned,
6 parser::types::{Directive, Field},
7 registry::MetaInputValue,
8 validation::{
9 suggestion::make_suggestion,
10 visitor::{Visitor, VisitorContext},
11 },
12};
13
14enum ArgsType<'a> {
15 Directive(&'a str),
16 Field {
17 field_name: &'a str,
18 type_name: &'a str,
19 },
20}
21
22#[derive(Default)]
23pub struct KnownArgumentNames<'a> {
24 current_args: Option<(&'a IndexMap<String, MetaInputValue>, ArgsType<'a>)>,
25}
26
27impl KnownArgumentNames<'_> {
28 fn get_suggestion(&self, name: &str) -> String {
29 make_suggestion(
30 " Did you mean",
31 self.current_args
32 .iter()
33 .map(|(args, _)| args.iter().map(|arg| arg.0.as_str()))
34 .flatten(),
35 name,
36 )
37 .unwrap_or_default()
38 }
39}
40
41impl<'a> Visitor<'a> for KnownArgumentNames<'a> {
42 fn enter_directive(
43 &mut self,
44 ctx: &mut VisitorContext<'a>,
45 directive: &'a Positioned<Directive>,
46 ) {
47 self.current_args = ctx
48 .registry
49 .directives
50 .get(directive.node.name.node.as_str())
51 .map(|d| (&d.args, ArgsType::Directive(&directive.node.name.node)));
52 }
53
54 fn exit_directive(
55 &mut self,
56 _ctx: &mut VisitorContext<'a>,
57 _directive: &'a Positioned<Directive>,
58 ) {
59 self.current_args = None;
60 }
61
62 fn enter_argument(
63 &mut self,
64 ctx: &mut VisitorContext<'a>,
65 name: &'a Positioned<Name>,
66 _value: &'a Positioned<Value>,
67 ) {
68 if let Some((args, arg_type)) = &self.current_args {
69 if !args.contains_key(name.node.as_str()) {
70 match arg_type {
71 ArgsType::Field {
72 field_name,
73 type_name,
74 } => {
75 ctx.report_error(
76 vec![name.pos],
77 format!(
78 "Unknown argument \"{}\" on field \"{}\" of type \"{}\".{}",
79 name,
80 field_name,
81 type_name,
82 if ctx.registry.enable_suggestions {
83 self.get_suggestion(name.node.as_str())
84 } else {
85 String::new()
86 }
87 ),
88 );
89 }
90 ArgsType::Directive(directive_name) => {
91 ctx.report_error(
92 vec![name.pos],
93 format!(
94 "Unknown argument \"{}\" on directive \"{}\".{}",
95 name,
96 directive_name,
97 self.get_suggestion(name.node.as_str())
98 ),
99 );
100 }
101 }
102 }
103 }
104 }
105
106 fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned<Field>) {
107 if let Some(parent_type) = ctx.parent_type() {
108 if let Some(schema_field) = parent_type.field_by_name(&field.node.name.node) {
109 self.current_args = Some((
110 &schema_field.args,
111 ArgsType::Field {
112 field_name: &field.node.name.node,
113 type_name: ctx.parent_type().unwrap().name(),
114 },
115 ));
116 }
117 }
118 }
119
120 fn exit_field(&mut self, _ctx: &mut VisitorContext<'a>, _field: &'a Positioned<Field>) {
121 self.current_args = None;
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 pub fn factory<'a>() -> KnownArgumentNames<'a> {
130 KnownArgumentNames::default()
131 }
132
133 #[test]
134 fn single_arg_is_known() {
135 expect_passes_rule!(
136 factory,
137 r#"
138 fragment argOnRequiredArg on Dog {
139 doesKnowCommand(dogCommand: SIT)
140 }
141 { __typename }
142 "#,
143 );
144 }
145
146 #[test]
147 fn multiple_args_are_known() {
148 expect_passes_rule!(
149 factory,
150 r#"
151 fragment multipleArgs on ComplicatedArgs {
152 multipleReqs(req1: 1, req2: 2)
153 }
154 { __typename }
155 "#,
156 );
157 }
158
159 #[test]
160 fn ignores_args_of_unknown_fields() {
161 expect_passes_rule!(
162 factory,
163 r#"
164 fragment argOnUnknownField on Dog {
165 unknownField(unknownArg: SIT)
166 }
167 { __typename }
168 "#,
169 );
170 }
171
172 #[test]
173 fn multiple_args_in_reverse_order_are_known() {
174 expect_passes_rule!(
175 factory,
176 r#"
177 fragment multipleArgsReverseOrder on ComplicatedArgs {
178 multipleReqs(req2: 2, req1: 1)
179 }
180 { __typename }
181 "#,
182 );
183 }
184
185 #[test]
186 fn no_args_on_optional_arg() {
187 expect_passes_rule!(
188 factory,
189 r#"
190 fragment noArgOnOptionalArg on Dog {
191 isHousetrained
192 }
193 { __typename }
194 "#,
195 );
196 }
197
198 #[test]
199 fn args_are_known_deeply() {
200 expect_passes_rule!(
201 factory,
202 r#"
203 {
204 dog {
205 doesKnowCommand(dogCommand: SIT)
206 }
207 human {
208 pet {
209 ... on Dog {
210 doesKnowCommand(dogCommand: SIT)
211 }
212 }
213 }
214 }
215 "#,
216 );
217 }
218
219 #[test]
220 fn directive_args_are_known() {
221 expect_passes_rule!(
222 factory,
223 r#"
224 {
225 dog @skip(if: true)
226 }
227 "#,
228 );
229 }
230
231 #[test]
232 fn undirective_args_are_invalid() {
233 expect_fails_rule!(
234 factory,
235 r#"
236 {
237 dog @skip(unless: true)
238 }
239 "#,
240 );
241 }
242
243 #[test]
244 fn invalid_arg_name() {
245 expect_fails_rule!(
246 factory,
247 r#"
248 fragment invalidArgName on Dog {
249 doesKnowCommand(unknown: true)
250 }
251 { __typename }
252 "#,
253 );
254 }
255
256 #[test]
257 fn unknown_args_amongst_known_args() {
258 expect_fails_rule!(
259 factory,
260 r#"
261 fragment oneGoodArgOneInvalidArg on Dog {
262 doesKnowCommand(whoknows: 1, dogCommand: SIT, unknown: true)
263 }
264 { __typename }
265 "#,
266 );
267 }
268
269 #[test]
270 fn unknown_args_deeply() {
271 expect_fails_rule!(
272 factory,
273 r#"
274 {
275 dog {
276 doesKnowCommand(unknown: true)
277 }
278 human {
279 pet {
280 ... on Dog {
281 doesKnowCommand(unknown: true)
282 }
283 }
284 }
285 }
286 "#,
287 );
288 }
289}