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