1use std::collections::{HashMap, HashSet};
2
3use crate::{
4 ast::{visit_document, AstNodeWithName, OperationVisitor, OperationVisitorContext},
5 static_graphql::query::{Type, Value, VariableDefinition},
6 validation::utils::{ValidationError, ValidationErrorContext},
7};
8
9use super::ValidationRule;
10
11#[derive(Default)]
17pub struct VariablesInAllowedPosition<'a> {
18 spreads: HashMap<Scope<'a>, HashSet<&'a str>>,
19 variable_usages: HashMap<Scope<'a>, Vec<(&'a str, &'a Type)>>,
20 variable_defs: HashMap<Scope<'a>, Vec<&'a VariableDefinition>>,
21 current_scope: Option<Scope<'a>>,
22}
23
24impl<'a> VariablesInAllowedPosition<'a> {
25 pub fn new() -> Self {
26 VariablesInAllowedPosition {
27 spreads: HashMap::new(),
28 variable_usages: HashMap::new(),
29 variable_defs: HashMap::new(),
30 current_scope: None,
31 }
32 }
33
34 fn collect_incorrect_usages(
35 &self,
36 from: &Scope<'a>,
37 var_defs: &Vec<&VariableDefinition>,
38 visitor_context: &mut OperationVisitorContext,
39 user_context: &mut ValidationErrorContext,
40 visited: &mut HashSet<Scope<'a>>,
41 ) {
42 if visited.contains(from) {
43 return;
44 }
45
46 visited.insert(from.clone());
47
48 if let Some(usages) = self.variable_usages.get(from) {
49 for (var_name, var_type) in usages {
50 if let Some(var_def) = var_defs.iter().find(|var_def| var_def.name == *var_name) {
51 let expected_type = match (&var_def.default_value, &var_def.var_type) {
52 (Some(_), Type::ListType(inner)) => Type::NonNullType(inner.clone()),
53 (Some(default_value), Type::NamedType(_)) => {
54 if let Value::Null = default_value {
55 var_def.var_type.clone()
56 } else {
57 Type::NonNullType(Box::new(var_def.var_type.clone()))
58 }
59 }
60 (_, t) => t.clone(),
61 };
62
63 if !visitor_context.schema.is_subtype(&expected_type, var_type) {
64 user_context.report_error(ValidationError {
65 error_code: self.error_code(),
66 message: format!("Variable \"${}\" of type \"{}\" used in position expecting type \"{}\".",
67 var_name,
68 expected_type,
69 var_type,
70 ),
71 locations: vec![var_def.position],
72 });
73 }
74 }
75 }
76 }
77
78 if let Some(spreads) = self.spreads.get(from) {
79 for spread in spreads {
80 self.collect_incorrect_usages(
81 &Scope::Fragment(spread),
82 var_defs,
83 visitor_context,
84 user_context,
85 visited,
86 );
87 }
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Hash)]
93pub enum Scope<'a> {
94 Operation(Option<&'a str>),
95 Fragment(&'a str),
96}
97
98impl<'a> OperationVisitor<'a, ValidationErrorContext> for VariablesInAllowedPosition<'a> {
99 fn leave_document(
100 &mut self,
101 visitor_context: &mut OperationVisitorContext<'a>,
102 user_context: &mut ValidationErrorContext,
103 _: &crate::static_graphql::query::Document,
104 ) {
105 for (op_scope, var_defs) in &self.variable_defs {
106 self.collect_incorrect_usages(
107 op_scope,
108 var_defs,
109 visitor_context,
110 user_context,
111 &mut HashSet::new(),
112 );
113 }
114 }
115
116 fn enter_fragment_definition(
117 &mut self,
118 _: &mut OperationVisitorContext<'a>,
119 _: &mut ValidationErrorContext,
120 fragment_definition: &'a crate::static_graphql::query::FragmentDefinition,
121 ) {
122 self.current_scope = Some(Scope::Fragment(&fragment_definition.name));
123 }
124
125 fn enter_operation_definition(
126 &mut self,
127 _: &mut OperationVisitorContext<'a>,
128 _: &mut ValidationErrorContext,
129 operation_definition: &'a crate::static_graphql::query::OperationDefinition,
130 ) {
131 self.current_scope = Some(Scope::Operation(operation_definition.node_name()));
132 }
133
134 fn enter_fragment_spread(
135 &mut self,
136 _: &mut OperationVisitorContext<'a>,
137 _: &mut ValidationErrorContext,
138 fragment_spread: &'a crate::static_graphql::query::FragmentSpread,
139 ) {
140 if let Some(scope) = &self.current_scope {
141 self.spreads
142 .entry(scope.clone())
143 .or_default()
144 .insert(&fragment_spread.fragment_name);
145 }
146 }
147
148 fn enter_variable_definition(
149 &mut self,
150 _: &mut OperationVisitorContext<'a>,
151 _: &mut ValidationErrorContext,
152 variable_definition: &'a VariableDefinition,
153 ) {
154 if let Some(ref scope) = self.current_scope {
155 self.variable_defs
156 .entry(scope.clone())
157 .or_default()
158 .push(variable_definition);
159 }
160 }
161
162 fn enter_variable_value(
163 &mut self,
164 visitor_context: &mut OperationVisitorContext<'a>,
165 _: &mut ValidationErrorContext,
166 variable_name: &'a str,
167 ) {
168 if let (Some(scope), Some(input_type)) = (
169 &self.current_scope,
170 visitor_context.current_input_type_literal(),
171 ) {
172 self.variable_usages
173 .entry(scope.clone())
174 .or_default()
175 .push((variable_name, input_type));
176 }
177 }
178}
179
180impl<'v> ValidationRule for VariablesInAllowedPosition<'v> {
181 fn error_code<'a>(&self) -> &'a str {
182 "VariablesInAllowedPosition"
183 }
184
185 fn validate(
186 &self,
187 ctx: &mut OperationVisitorContext,
188 error_collector: &mut ValidationErrorContext,
189 ) {
190 visit_document(
191 &mut VariablesInAllowedPosition::new(),
192 ctx.operation,
193 ctx,
194 error_collector,
195 );
196 }
197}
198
199#[test]
200fn boolean_to_boolean() {
201 use crate::validation::test_utils::*;
202
203 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
204 let errors = test_operation_with_schema(
205 "query Query($booleanArg: Boolean)
206 {
207 complicatedArgs {
208 booleanArgField(booleanArg: $booleanArg)
209 }
210 }",
211 TEST_SCHEMA,
212 &mut plan,
213 );
214
215 assert_eq!(get_messages(&errors).len(), 0);
216}
217
218#[test]
219fn boolean_to_boolean_within_fragment() {
220 use crate::validation::test_utils::*;
221
222 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
223 let errors = test_operation_with_schema(
224 "fragment booleanArgFrag on ComplicatedArgs {
225 booleanArgField(booleanArg: $booleanArg)
226 }
227 query Query($booleanArg: Boolean)
228 {
229 complicatedArgs {
230 ...booleanArgFrag
231 }
232 }",
233 TEST_SCHEMA,
234 &mut plan,
235 );
236
237 assert_eq!(get_messages(&errors).len(), 0);
238
239 let errors = test_operation_with_schema(
240 "query Query($booleanArg: Boolean)
241 {
242 complicatedArgs {
243 ...booleanArgFrag
244 }
245 }
246 fragment booleanArgFrag on ComplicatedArgs {
247 booleanArgField(booleanArg: $booleanArg)
248 }",
249 TEST_SCHEMA,
250 &mut plan,
251 );
252
253 assert_eq!(get_messages(&errors).len(), 0);
254}
255
256#[test]
257fn boolean_nonnull_to_boolean() {
258 use crate::validation::test_utils::*;
259
260 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
261 let errors = test_operation_with_schema(
262 "query Query($nonNullBooleanArg: Boolean!)
263 {
264 complicatedArgs {
265 booleanArgField(booleanArg: $nonNullBooleanArg)
266 }
267 }",
268 TEST_SCHEMA,
269 &mut plan,
270 );
271
272 assert_eq!(get_messages(&errors).len(), 0);
273}
274
275#[test]
276fn string_list_to_string_list() {
277 use crate::validation::test_utils::*;
278
279 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
280 let errors = test_operation_with_schema(
281 "query Query($stringListVar: [String])
282 {
283 complicatedArgs {
284 stringListArgField(stringListArg: $stringListVar)
285 }
286 }",
287 TEST_SCHEMA,
288 &mut plan,
289 );
290
291 assert_eq!(get_messages(&errors).len(), 0);
292}
293
294#[test]
295fn string_list_nonnull_to_string_list() {
296 use crate::validation::test_utils::*;
297
298 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
299 let errors = test_operation_with_schema(
300 "query Query($stringListVar: [String!])
301 {
302 complicatedArgs {
303 stringListArgField(stringListArg: $stringListVar)
304 }
305 }",
306 TEST_SCHEMA,
307 &mut plan,
308 );
309
310 assert_eq!(get_messages(&errors).len(), 0);
311}
312
313#[test]
314fn string_to_string_list_in_item_position() {
315 use crate::validation::test_utils::*;
316
317 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
318 let errors = test_operation_with_schema(
319 "query Query($stringVar: String)
320 {
321 complicatedArgs {
322 stringListArgField(stringListArg: [$stringVar])
323 }
324 }",
325 TEST_SCHEMA,
326 &mut plan,
327 );
328
329 assert_eq!(get_messages(&errors).len(), 0);
330}
331
332#[test]
333fn string_nonnull_to_string_list_in_item_position() {
334 use crate::validation::test_utils::*;
335
336 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
337 let errors = test_operation_with_schema(
338 "query Query($stringVar: String!)
339 {
340 complicatedArgs {
341 stringListArgField(stringListArg: [$stringVar])
342 }
343 }",
344 TEST_SCHEMA,
345 &mut plan,
346 );
347
348 assert_eq!(get_messages(&errors).len(), 0);
349}
350
351#[test]
352fn complexinput_to_complexinput() {
353 use crate::validation::test_utils::*;
354
355 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
356 let errors = test_operation_with_schema(
357 "query Query($complexVar: ComplexInput)
358 {
359 complicatedArgs {
360 complexArgField(complexArg: $complexVar)
361 }
362 }",
363 TEST_SCHEMA,
364 &mut plan,
365 );
366
367 assert_eq!(get_messages(&errors).len(), 0);
368}
369
370#[test]
371fn complexinput_to_complexinput_in_field_position() {
372 use crate::validation::test_utils::*;
373
374 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
375 let errors = test_operation_with_schema(
376 "query Query($boolVar: Boolean = false)
377 {
378 complicatedArgs {
379 complexArgField(complexArg: { requiredArg: $boolVar })
380 }
381 }",
382 TEST_SCHEMA,
383 &mut plan,
384 );
385
386 let messages = get_messages(&errors);
387 assert_eq!(messages.len(), 0);
388}
389
390#[test]
391fn boolean_nonnull_to_boolean_nonnull_in_directive() {
392 use crate::validation::test_utils::*;
393
394 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
395 let errors = test_operation_with_schema(
396 "query Query($boolVar: Boolean!)
397 {
398 dog @include(if: $boolVar)
399 }",
400 TEST_SCHEMA,
401 &mut plan,
402 );
403
404 assert_eq!(get_messages(&errors).len(), 0);
405}
406
407#[test]
408fn int_to_int_nonnull() {
409 use crate::validation::test_utils::*;
410
411 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
412 let errors = test_operation_with_schema(
413 "query Query($intArg: Int) {
414 complicatedArgs {
415 nonNullIntArgField(nonNullIntArg: $intArg)
416 }
417 }",
418 TEST_SCHEMA,
419 &mut plan,
420 );
421
422 let messages = get_messages(&errors);
423 assert_eq!(messages.len(), 1);
424 assert_eq!(
425 messages,
426 vec!["Variable \"$intArg\" of type \"Int\" used in position expecting type \"Int!\"."]
427 )
428}
429
430#[test]
431fn int_to_int_nonnull_within_fragment() {
432 use crate::validation::test_utils::*;
433
434 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
435 let errors = test_operation_with_schema(
436 "fragment nonNullIntArgFieldFrag on ComplicatedArgs {
437 nonNullIntArgField(nonNullIntArg: $intArg)
438 }
439 query Query($intArg: Int) {
440 complicatedArgs {
441 ...nonNullIntArgFieldFrag
442 }
443 }",
444 TEST_SCHEMA,
445 &mut plan,
446 );
447
448 let messages = get_messages(&errors);
449 assert_eq!(messages.len(), 1);
450 assert_eq!(
451 messages,
452 vec!["Variable \"$intArg\" of type \"Int\" used in position expecting type \"Int!\"."]
453 )
454}
455
456#[test]
457fn int_to_int_nonnull_within_nested_fragment() {
458 use crate::validation::test_utils::*;
459
460 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
461 let errors = test_operation_with_schema(
462 "fragment outerFrag on ComplicatedArgs {
463 ...nonNullIntArgFieldFrag
464 }
465 fragment nonNullIntArgFieldFrag on ComplicatedArgs {
466 nonNullIntArgField(nonNullIntArg: $intArg)
467 }
468 query Query($intArg: Int) {
469 complicatedArgs {
470 ...outerFrag
471 }
472 }",
473 TEST_SCHEMA,
474 &mut plan,
475 );
476
477 let messages = get_messages(&errors);
478 assert_eq!(messages.len(), 1);
479 assert_eq!(
480 messages,
481 vec!["Variable \"$intArg\" of type \"Int\" used in position expecting type \"Int!\"."]
482 )
483}
484
485#[test]
486fn string_over_boolean() {
487 use crate::validation::test_utils::*;
488
489 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
490 let errors = test_operation_with_schema(
491 "query Query($stringVar: String) {
492 complicatedArgs {
493 booleanArgField(booleanArg: $stringVar)
494 }
495 }",
496 TEST_SCHEMA,
497 &mut plan,
498 );
499
500 let messages = get_messages(&errors);
501 assert_eq!(messages.len(), 1);
502 assert_eq!(
503 messages,
504 vec![
505 "Variable \"$stringVar\" of type \"String\" used in position expecting type \"Boolean\"."
506 ]
507 )
508}
509
510#[test]
511fn string_over_string_list() {
512 use crate::validation::test_utils::*;
513
514 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
515 let errors = test_operation_with_schema(
516 "query Query($stringVar: String) {
517 complicatedArgs {
518 stringListArgField(stringListArg: $stringVar)
519 }
520 }",
521 TEST_SCHEMA,
522 &mut plan,
523 );
524
525 let messages = get_messages(&errors);
526 assert_eq!(messages.len(), 1);
527 assert_eq!(
528 messages,
529 vec![
530 "Variable \"$stringVar\" of type \"String\" used in position expecting type \"[String]\"."
531 ]
532 )
533}
534
535#[test]
536fn boolean_to_boolean_nonnull_in_directive() {
537 use crate::validation::test_utils::*;
538
539 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
540 let errors = test_operation_with_schema(
541 "query Query($boolVar: Boolean) {
542 dog @include(if: $boolVar)
543 }",
544 TEST_SCHEMA,
545 &mut plan,
546 );
547
548 let messages = get_messages(&errors);
549 assert_eq!(messages.len(), 1);
550 assert_eq!(
551 messages,
552 vec![
553 "Variable \"$boolVar\" of type \"Boolean\" used in position expecting type \"Boolean!\"."
554 ]
555 )
556}
557
558#[test]
559fn string_to_boolean_nonnull_in_directive() {
560 use crate::validation::test_utils::*;
561
562 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
563 let errors = test_operation_with_schema(
564 "query Query($stringVar: String) {
565 dog @include(if: $stringVar)
566 }",
567 TEST_SCHEMA,
568 &mut plan,
569 );
570
571 let messages = get_messages(&errors);
572 assert_eq!(messages.len(), 1);
573 assert_eq!(
574 messages,
575 vec![
576 "Variable \"$stringVar\" of type \"String\" used in position expecting type \"Boolean!\"."
577 ]
578 )
579}
580
581#[test]
582fn string_list_to_string_nonnull_list() {
583 use crate::validation::test_utils::*;
584
585 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
586 let errors = test_operation_with_schema(
587 "query Query($stringListVar: [String])
588 {
589 complicatedArgs {
590 stringListNonNullArgField(stringListNonNullArg: $stringListVar)
591 }
592 }",
593 TEST_SCHEMA,
594 &mut plan,
595 );
596
597 let messages = get_messages(&errors);
598 assert_eq!(messages.len(), 1);
599 assert_eq!(messages, vec![
600 "Variable \"$stringListVar\" of type \"[String]\" used in position expecting type \"[String!]\"."
601 ])
602}
603
604#[test]
605fn int_to_int_non_null_with_null_default_value() {
606 use crate::validation::test_utils::*;
607
608 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
609 let errors = test_operation_with_schema(
610 "query Query($intVar: Int = null) {
611 complicatedArgs {
612 nonNullIntArgField(nonNullIntArg: $intVar)
613 }
614 }",
615 TEST_SCHEMA,
616 &mut plan,
617 );
618
619 let messages = get_messages(&errors);
620 assert_eq!(messages.len(), 1);
621 assert_eq!(
622 messages,
623 vec!["Variable \"$intVar\" of type \"Int\" used in position expecting type \"Int!\"."]
624 )
625}
626
627#[test]
628fn int_to_int_non_null_with_default_value() {
629 use crate::validation::test_utils::*;
630
631 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
632 let errors = test_operation_with_schema(
633 "query Query($intVar: Int = 1) {
634 complicatedArgs {
635 nonNullIntArgField(nonNullIntArg: $intVar)
636 }
637 }",
638 TEST_SCHEMA,
639 &mut plan,
640 );
641
642 let messages = get_messages(&errors);
643 assert_eq!(messages.len(), 0);
644}
645
646#[test]
647fn int_to_int_non_null_where_argument_with_default_value() {
648 use crate::validation::test_utils::*;
649
650 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
651 let errors = test_operation_with_schema(
652 "query Query($intVar: Int) {
653 complicatedArgs {
654 nonNullFieldWithDefault(nonNullIntArg: $intVar)
655 }
656 }",
657 TEST_SCHEMA,
658 &mut plan,
659 );
660
661 let messages = get_messages(&errors);
662 assert_eq!(messages.len(), 0);
663}
664
665#[test]
666fn boolean_to_boolean_non_null_with_default_value() {
667 use crate::validation::test_utils::*;
668
669 let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
670 let errors = test_operation_with_schema(
671 "query Query($boolVar: Boolean = false) {
672 dog @include(if: $boolVar)
673 }",
674 TEST_SCHEMA,
675 &mut plan,
676 );
677
678 let messages = get_messages(&errors);
679 assert_eq!(messages.len(), 0);
680}