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