Skip to main content

graphql_tools/validation/rules/
variables_in_allowed_position.rs

1use std::collections::{HashMap, HashSet};
2
3use crate::{
4    ast::{AstNodeWithName, OperationVisitor, OperationVisitorContext},
5    static_graphql::query::{Type, Value, VariableDefinition},
6    validation::utils::{ValidationError, ValidationErrorContext},
7};
8
9use super::ValidationRule;
10
11/// Variables in allowed position
12///
13/// Variable usages must be compatible with the arguments they are passed to.
14///
15/// See https://spec.graphql.org/draft/#sec-All-Variable-Usages-are-Allowed
16#[derive(Default)]
17pub struct VariablesInAllowedPosition<'doc> {
18    spreads: HashMap<Scope<'doc>, HashSet<&'doc str>>,
19    variable_usages: HashMap<Scope<'doc>, Vec<VariableUsage<'doc>>>,
20    variable_defs: HashMap<Scope<'doc>, Vec<&'doc VariableDefinition>>,
21    current_scope: Option<Scope<'doc>>,
22}
23
24impl<'doc> VariablesInAllowedPosition<'doc> {
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<'doc>,
37        var_defs: &[&VariableDefinition],
38        visitor_context: &mut OperationVisitorContext,
39        user_context: &mut ValidationErrorContext,
40        visited: &mut HashSet<Scope<'doc>>,
41    ) {
42        if visited.contains(from) {
43            return;
44        }
45
46        visited.insert(from.clone());
47
48        let usages = match self.variable_usages.get(from) {
49            Some(usages) => usages.as_slice(),
50            None => &[],
51        };
52        for (var_name, location_type, has_default, one_of_parent) in usages {
53            let Some(var_def) = var_defs.iter().find(|var_def| var_def.name == *var_name) else {
54                continue;
55            };
56
57            let has_non_null_default = var_def
58                .default_value
59                .as_ref()
60                .is_some_and(|v| !matches!(v, Value::Null));
61
62            // https://spec.graphql.org/draft/#sec-All-Variable-Usages-are-Allowed
63            // If a variable definition has a non-null default value (e.g., $foo: String = "hi"),
64            // the variable can be used where a non-null type is expected,
65            // because when the variable is omitted, the default kicks in.
66            // An explicit null default ($foo: String = null) doesn't get this treatment,
67            // since null doesn't satisfy a non-null position.
68            let variable_type = match &var_def.var_type {
69                Type::NonNullType(_) => var_def.var_type.clone(),
70                t if has_non_null_default => Type::NonNullType(Box::new(t.clone())),
71                t => t.clone(),
72            };
73
74            // A default at the usage location permits a nullable variable in a
75            // non-null position, so ignore the location's outer nullability.
76            let effective_location_type = match (has_default, location_type) {
77                (true, Type::NonNullType(inner)) => inner.as_ref(),
78                _ => location_type,
79            };
80
81            if !visitor_context
82                .schema
83                .is_subtype(&variable_type, effective_location_type)
84            {
85                user_context.report_error(ValidationError {
86                    error_code: self.error_code(),
87                    message: format!(
88                        "Variable \"${}\" of type \"{}\" used in position expecting type \"{}\".",
89                        var_name, variable_type, location_type,
90                    ),
91                    locations: vec![var_def.position],
92                });
93            }
94
95            // https://spec.graphql.org/draft/#sec-All-Variable-Usages-Are-Allowed
96            // A OneOf field must never be null, so its variable must be non-nullable,
97            // even with a default value.
98            if let Some(one_of_type) = one_of_parent {
99                if !matches!(var_def.var_type, Type::NonNullType(_)) {
100                    user_context.report_error(ValidationError {
101                        error_code: self.error_code(),
102                        message: format!(
103                            "Variable \"${}\" is of type \"{}\" but must be non-nullable to be used for OneOf Input Object \"{}\".",
104                            var_name, var_def.var_type, one_of_type,
105                        ),
106                        locations: vec![var_def.position],
107                    });
108                }
109            }
110        }
111
112        if let Some(spreads) = self.spreads.get(from) {
113            for spread in spreads {
114                self.collect_incorrect_usages(
115                    &Scope::Fragment(spread),
116                    var_defs,
117                    visitor_context,
118                    user_context,
119                    visited,
120                );
121            }
122        }
123    }
124}
125
126/// A variable used as a value: its name, the type expected there, whether that location has a
127/// default, and the OneOf input object it is a field of (if any).
128type VariableUsage<'doc> = (&'doc str, &'doc Type, bool, Option<&'doc str>);
129
130#[derive(Debug, Clone, PartialEq, Eq, Hash)]
131pub enum Scope<'doc> {
132    Operation(Option<&'doc str>),
133    Fragment(&'doc str),
134}
135
136impl<'doc> OperationVisitor<'doc, ValidationErrorContext> for VariablesInAllowedPosition<'doc> {
137    fn leave_document(
138        &mut self,
139        visitor_context: &mut OperationVisitorContext<'doc>,
140        user_context: &mut ValidationErrorContext,
141        _: &crate::static_graphql::query::Document,
142    ) {
143        for (op_scope, var_defs) in &self.variable_defs {
144            self.collect_incorrect_usages(
145                op_scope,
146                var_defs,
147                visitor_context,
148                user_context,
149                &mut HashSet::new(),
150            );
151        }
152    }
153
154    fn enter_fragment_definition(
155        &mut self,
156        _: &mut OperationVisitorContext<'doc>,
157        _: &mut ValidationErrorContext,
158        fragment_definition: &'doc crate::static_graphql::query::FragmentDefinition,
159    ) {
160        self.current_scope = Some(Scope::Fragment(&fragment_definition.name));
161    }
162
163    fn enter_operation_definition(
164        &mut self,
165        _: &mut OperationVisitorContext<'doc>,
166        _: &mut ValidationErrorContext,
167        operation_definition: &'doc crate::static_graphql::query::OperationDefinition,
168    ) {
169        self.current_scope = Some(Scope::Operation(operation_definition.node_name()));
170    }
171
172    fn enter_fragment_spread(
173        &mut self,
174        _: &mut OperationVisitorContext<'doc>,
175        _: &mut ValidationErrorContext,
176        fragment_spread: &'doc crate::static_graphql::query::FragmentSpread,
177    ) {
178        if let Some(scope) = &self.current_scope {
179            self.spreads
180                .entry(scope.clone())
181                .or_default()
182                .insert(&fragment_spread.fragment_name);
183        }
184    }
185
186    fn enter_variable_definition(
187        &mut self,
188        _: &mut OperationVisitorContext<'doc>,
189        _: &mut ValidationErrorContext,
190        variable_definition: &'doc VariableDefinition,
191    ) {
192        if let Some(ref scope) = self.current_scope {
193            self.variable_defs
194                .entry(scope.clone())
195                .or_default()
196                .push(variable_definition);
197        }
198    }
199
200    fn enter_variable_value(
201        &mut self,
202        visitor_context: &mut OperationVisitorContext<'doc>,
203        _: &mut ValidationErrorContext,
204        variable_name: &'doc str,
205    ) {
206        if let (Some(scope), Some(input_type)) = (
207            &self.current_scope,
208            visitor_context.current_input_type_literal(),
209        ) {
210            let has_default = visitor_context.current_input_type_has_default();
211            let one_of_parent = visitor_context
212                .parent_input_type()
213                .filter(|parent| parent.is_one_of())
214                .map(|parent| parent.name());
215            self.variable_usages
216                .entry(scope.clone())
217                .or_default()
218                .push((variable_name, input_type, has_default, one_of_parent));
219        }
220    }
221}
222
223impl ValidationRule for VariablesInAllowedPosition<'_> {
224    fn error_code(&self) -> &'static str {
225        "VariablesInAllowedPosition"
226    }
227
228    fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
229        Box::new(VariablesInAllowedPosition::new())
230    }
231}
232
233#[test]
234fn boolean_to_boolean() {
235    use crate::validation::test_utils::*;
236
237    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
238    let errors = test_operation_with_schema(
239        "query Query($booleanArg: Boolean)
240        {
241          complicatedArgs {
242            booleanArgField(booleanArg: $booleanArg)
243          }
244        }",
245        TEST_SCHEMA,
246        &mut plan,
247    );
248
249    assert_eq!(get_messages(&errors).len(), 0);
250}
251
252#[test]
253fn boolean_to_boolean_within_fragment() {
254    use crate::validation::test_utils::*;
255
256    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
257    let errors = test_operation_with_schema(
258        "fragment booleanArgFrag on ComplicatedArgs {
259          booleanArgField(booleanArg: $booleanArg)
260        }
261        query Query($booleanArg: Boolean)
262        {
263          complicatedArgs {
264            ...booleanArgFrag
265          }
266        }",
267        TEST_SCHEMA,
268        &mut plan,
269    );
270
271    assert_eq!(get_messages(&errors).len(), 0);
272
273    let errors = test_operation_with_schema(
274        "query Query($booleanArg: Boolean)
275      {
276        complicatedArgs {
277          ...booleanArgFrag
278        }
279      }
280      fragment booleanArgFrag on ComplicatedArgs {
281        booleanArgField(booleanArg: $booleanArg)
282      }",
283        TEST_SCHEMA,
284        &mut plan,
285    );
286
287    assert_eq!(get_messages(&errors).len(), 0);
288}
289
290#[test]
291fn boolean_nonnull_to_boolean() {
292    use crate::validation::test_utils::*;
293
294    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
295    let errors = test_operation_with_schema(
296        "query Query($nonNullBooleanArg: Boolean!)
297        {
298          complicatedArgs {
299            booleanArgField(booleanArg: $nonNullBooleanArg)
300          }
301        }",
302        TEST_SCHEMA,
303        &mut plan,
304    );
305
306    assert_eq!(get_messages(&errors).len(), 0);
307}
308
309#[test]
310fn string_list_to_string_list() {
311    use crate::validation::test_utils::*;
312
313    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
314    let errors = test_operation_with_schema(
315        "query Query($stringListVar: [String])
316        {
317          complicatedArgs {
318            stringListArgField(stringListArg: $stringListVar)
319          }
320        }",
321        TEST_SCHEMA,
322        &mut plan,
323    );
324
325    assert_eq!(get_messages(&errors).len(), 0);
326}
327
328#[test]
329fn string_list_nonnull_to_string_list() {
330    use crate::validation::test_utils::*;
331
332    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
333    let errors = test_operation_with_schema(
334        "query Query($stringListVar: [String!])
335        {
336          complicatedArgs {
337            stringListArgField(stringListArg: $stringListVar)
338          }
339        }",
340        TEST_SCHEMA,
341        &mut plan,
342    );
343
344    assert_eq!(get_messages(&errors).len(), 0);
345}
346
347#[test]
348fn string_to_string_list_in_item_position() {
349    use crate::validation::test_utils::*;
350
351    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
352    let errors = test_operation_with_schema(
353        "query Query($stringVar: String)
354        {
355          complicatedArgs {
356            stringListArgField(stringListArg: [$stringVar])
357          }
358        }",
359        TEST_SCHEMA,
360        &mut plan,
361    );
362
363    assert_eq!(get_messages(&errors).len(), 0);
364}
365
366#[test]
367fn string_nonnull_to_string_list_in_item_position() {
368    use crate::validation::test_utils::*;
369
370    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
371    let errors = test_operation_with_schema(
372        "query Query($stringVar: String!)
373        {
374          complicatedArgs {
375            stringListArgField(stringListArg: [$stringVar])
376          }
377        }",
378        TEST_SCHEMA,
379        &mut plan,
380    );
381
382    assert_eq!(get_messages(&errors).len(), 0);
383}
384
385#[test]
386fn complexinput_to_complexinput() {
387    use crate::validation::test_utils::*;
388
389    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
390    let errors = test_operation_with_schema(
391        "query Query($complexVar: ComplexInput)
392        {
393          complicatedArgs {
394            complexArgField(complexArg: $complexVar)
395          }
396        }",
397        TEST_SCHEMA,
398        &mut plan,
399    );
400
401    assert_eq!(get_messages(&errors).len(), 0);
402}
403
404#[test]
405fn complexinput_to_complexinput_in_field_position() {
406    use crate::validation::test_utils::*;
407
408    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
409    let errors = test_operation_with_schema(
410        "query Query($boolVar: Boolean = false)
411        {
412          complicatedArgs {
413            complexArgField(complexArg: { requiredArg: $boolVar })
414          }
415        }",
416        TEST_SCHEMA,
417        &mut plan,
418    );
419
420    let messages = get_messages(&errors);
421    assert_eq!(messages.len(), 0);
422}
423
424#[test]
425fn boolean_nonnull_to_boolean_nonnull_in_directive() {
426    use crate::validation::test_utils::*;
427
428    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
429    let errors = test_operation_with_schema(
430        "query Query($boolVar: Boolean!)
431        {
432          dog @include(if: $boolVar)
433        }",
434        TEST_SCHEMA,
435        &mut plan,
436    );
437
438    assert_eq!(get_messages(&errors).len(), 0);
439}
440
441#[test]
442fn int_to_int_nonnull() {
443    use crate::validation::test_utils::*;
444
445    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
446    let errors = test_operation_with_schema(
447        "query Query($intArg: Int) {
448          complicatedArgs {
449            nonNullIntArgField(nonNullIntArg: $intArg)
450          }
451        }",
452        TEST_SCHEMA,
453        &mut plan,
454    );
455
456    let messages = get_messages(&errors);
457    assert_eq!(messages.len(), 1);
458    assert_eq!(
459        messages,
460        vec!["Variable \"$intArg\" of type \"Int\" used in position expecting type \"Int!\"."]
461    )
462}
463
464#[test]
465fn int_to_int_nonnull_within_fragment() {
466    use crate::validation::test_utils::*;
467
468    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
469    let errors = test_operation_with_schema(
470        "fragment nonNullIntArgFieldFrag on ComplicatedArgs {
471          nonNullIntArgField(nonNullIntArg: $intArg)
472        }
473        query Query($intArg: Int) {
474          complicatedArgs {
475            ...nonNullIntArgFieldFrag
476          }
477        }",
478        TEST_SCHEMA,
479        &mut plan,
480    );
481
482    let messages = get_messages(&errors);
483    assert_eq!(messages.len(), 1);
484    assert_eq!(
485        messages,
486        vec!["Variable \"$intArg\" of type \"Int\" used in position expecting type \"Int!\"."]
487    )
488}
489
490#[test]
491fn int_to_int_nonnull_within_nested_fragment() {
492    use crate::validation::test_utils::*;
493
494    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
495    let errors = test_operation_with_schema(
496        "fragment outerFrag on ComplicatedArgs {
497          ...nonNullIntArgFieldFrag
498        }
499        fragment nonNullIntArgFieldFrag on ComplicatedArgs {
500          nonNullIntArgField(nonNullIntArg: $intArg)
501        }
502        query Query($intArg: Int) {
503          complicatedArgs {
504            ...outerFrag
505          }
506        }",
507        TEST_SCHEMA,
508        &mut plan,
509    );
510
511    let messages = get_messages(&errors);
512    assert_eq!(messages.len(), 1);
513    assert_eq!(
514        messages,
515        vec!["Variable \"$intArg\" of type \"Int\" used in position expecting type \"Int!\"."]
516    )
517}
518
519#[test]
520fn string_over_boolean() {
521    use crate::validation::test_utils::*;
522
523    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
524    let errors = test_operation_with_schema(
525        "query Query($stringVar: String) {
526          complicatedArgs {
527            booleanArgField(booleanArg: $stringVar)
528          }
529        }",
530        TEST_SCHEMA,
531        &mut plan,
532    );
533
534    let messages = get_messages(&errors);
535    assert_eq!(messages.len(), 1);
536    assert_eq!(
537        messages,
538        vec![
539      "Variable \"$stringVar\" of type \"String\" used in position expecting type \"Boolean\"."
540    ]
541    )
542}
543
544#[test]
545fn string_over_string_list() {
546    use crate::validation::test_utils::*;
547
548    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
549    let errors = test_operation_with_schema(
550        "query Query($stringVar: String) {
551          complicatedArgs {
552            stringListArgField(stringListArg: $stringVar)
553          }
554        }",
555        TEST_SCHEMA,
556        &mut plan,
557    );
558
559    let messages = get_messages(&errors);
560    assert_eq!(messages.len(), 1);
561    assert_eq!(
562        messages,
563        vec![
564      "Variable \"$stringVar\" of type \"String\" used in position expecting type \"[String]\"."
565    ]
566    )
567}
568
569#[test]
570fn boolean_to_boolean_nonnull_in_directive() {
571    use crate::validation::test_utils::*;
572
573    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
574    let errors = test_operation_with_schema(
575        "query Query($boolVar: Boolean) {
576          dog @include(if: $boolVar)
577        }",
578        TEST_SCHEMA,
579        &mut plan,
580    );
581
582    let messages = get_messages(&errors);
583    assert_eq!(messages.len(), 1);
584    assert_eq!(
585        messages,
586        vec![
587      "Variable \"$boolVar\" of type \"Boolean\" used in position expecting type \"Boolean!\"."
588    ]
589    )
590}
591
592#[test]
593fn string_to_boolean_nonnull_in_directive() {
594    use crate::validation::test_utils::*;
595
596    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
597    let errors = test_operation_with_schema(
598        "query Query($stringVar: String) {
599          dog @include(if: $stringVar)
600        }",
601        TEST_SCHEMA,
602        &mut plan,
603    );
604
605    let messages = get_messages(&errors);
606    assert_eq!(messages.len(), 1);
607    assert_eq!(
608        messages,
609        vec![
610      "Variable \"$stringVar\" of type \"String\" used in position expecting type \"Boolean!\"."
611    ]
612    )
613}
614
615#[test]
616fn string_list_to_string_nonnull_list() {
617    use crate::validation::test_utils::*;
618
619    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
620    let errors = test_operation_with_schema(
621        "query Query($stringListVar: [String])
622        {
623          complicatedArgs {
624            stringListNonNullArgField(stringListNonNullArg: $stringListVar)
625          }
626        }",
627        TEST_SCHEMA,
628        &mut plan,
629    );
630
631    let messages = get_messages(&errors);
632    assert_eq!(messages.len(), 1);
633    assert_eq!(messages, vec![
634      "Variable \"$stringListVar\" of type \"[String]\" used in position expecting type \"[String!]\"."
635    ])
636}
637
638#[test]
639fn int_to_int_non_null_with_null_default_value() {
640    use crate::validation::test_utils::*;
641
642    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
643    let errors = test_operation_with_schema(
644        "query Query($intVar: Int = null) {
645          complicatedArgs {
646            nonNullIntArgField(nonNullIntArg: $intVar)
647          }
648        }",
649        TEST_SCHEMA,
650        &mut plan,
651    );
652
653    let messages = get_messages(&errors);
654    assert_eq!(messages.len(), 1);
655    assert_eq!(
656        messages,
657        vec!["Variable \"$intVar\" of type \"Int\" used in position expecting type \"Int!\"."]
658    )
659}
660
661#[test]
662fn int_to_int_non_null_with_default_value() {
663    use crate::validation::test_utils::*;
664
665    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
666    let errors = test_operation_with_schema(
667        "query Query($intVar: Int = 1) {
668          complicatedArgs {
669            nonNullIntArgField(nonNullIntArg: $intVar)
670          }
671        }",
672        TEST_SCHEMA,
673        &mut plan,
674    );
675
676    let messages = get_messages(&errors);
677    assert_eq!(messages.len(), 0);
678}
679
680#[test]
681fn int_to_int_non_null_where_argument_with_default_value() {
682    use crate::validation::test_utils::*;
683
684    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
685    let errors = test_operation_with_schema(
686        "query Query($intVar: Int) {
687          complicatedArgs {
688            nonNullFieldWithDefault(arg: $intVar)
689          }
690        }",
691        TEST_SCHEMA,
692        &mut plan,
693    );
694
695    let messages = get_messages(&errors);
696    assert_eq!(messages.len(), 0);
697}
698
699#[test]
700fn list_of_non_null_enum_with_single_enum_default_value() {
701    use crate::validation::test_utils::*;
702
703    // A single-value default on a list-typed variable is valid per
704    // GraphQL spec (single values coerce to a one-item list). The router used to
705    // reject this with the malformed type "T!!" because the expected-type
706    // computation dropped the list wrapper and double-wrapped NonNull.
707    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
708    let errors = test_operation_with_schema(
709        "query Query($enumListVar: [FurColor!] = BROWN) {
710          complicatedArgs {
711            enumListArgField(enumListArg: $enumListVar)
712          }
713        }",
714        // Augment the schema with a list-of-non-null-enum argument.
715        &(TEST_SCHEMA.replace(
716            "enumArgField(enumArg: FurColor): String",
717            "enumArgField(enumArg: FurColor): String\n  enumListArgField(enumListArg: [FurColor!]): String",
718        )),
719        &mut plan,
720    );
721
722    let messages = get_messages(&errors);
723    assert_eq!(messages, Vec::<&String>::new());
724}
725
726#[test]
727fn list_of_non_null_with_default_value_used_in_non_null_list_position() {
728    use crate::validation::test_utils::*;
729
730    // A list variable with a non-null default value should be usable in a
731    // non-null list position. Before the fix, the expected type for the
732    // subtype check was "T!!" (invalid) instead of "[T!]!".
733    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
734    let errors = test_operation_with_schema(
735        "query Query($enumListVar: [FurColor!] = BROWN) {
736          complicatedArgs {
737            requiredEnumListArgField(enumListArg: $enumListVar)
738          }
739        }",
740        &(TEST_SCHEMA.replace(
741            "enumArgField(enumArg: FurColor): String",
742            "enumArgField(enumArg: FurColor): String\n  requiredEnumListArgField(enumListArg: [FurColor!]!): String",
743        )),
744        &mut plan,
745    );
746
747    let messages = get_messages(&errors);
748    assert_eq!(messages, Vec::<&String>::new());
749}
750
751#[test]
752fn boolean_to_boolean_non_null_with_default_value() {
753    use crate::validation::test_utils::*;
754
755    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
756    let errors = test_operation_with_schema(
757        "query Query($boolVar: Boolean = false) {
758          dog @include(if: $boolVar)
759        }",
760        TEST_SCHEMA,
761        &mut plan,
762    );
763
764    let messages = get_messages(&errors);
765    assert_eq!(messages.len(), 0);
766}
767
768#[test]
769fn nullable_enum_to_non_null_enum_with_default_on_argument() {
770    use crate::validation::test_utils::*;
771
772    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
773    let errors = test_operation_with_schema(
774        "query Query($currency: FurColor) {
775          complicatedArgs {
776            enumArgFieldWithDefault(enumArg: $currency)
777          }
778        }",
779        &(TEST_SCHEMA.replace(
780            "enumArgField(enumArg: FurColor): String",
781            "enumArgField(enumArg: FurColor): String\n  enumArgFieldWithDefault(enumArg: FurColor! = BROWN): String",
782        )),
783        &mut plan,
784    );
785
786    let messages = get_messages(&errors);
787    assert_eq!(messages.len(), 0);
788}
789
790#[test]
791fn nullable_int_to_non_null_int_with_default_on_argument() {
792    use crate::validation::test_utils::*;
793
794    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
795    let errors = test_operation_with_schema(
796        "query Query($intVar: Int) {
797          complicatedArgs {
798            nonNullFieldWithDefault(arg: $intVar)
799          }
800        }",
801        TEST_SCHEMA,
802        &mut plan,
803    );
804
805    let messages = get_messages(&errors);
806    assert_eq!(messages.len(), 0);
807}
808
809#[test]
810fn nullable_int_to_non_null_input_field_with_default() {
811    use crate::validation::test_utils::*;
812
813    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
814    let errors = test_operation_with_schema(
815        "query Query($intVar: Int) {
816          set(input: { value: $intVar })
817        }",
818        "input Input {
819          value: Int! = 1
820        }
821        type Query {
822          set(input: Input): String
823        }",
824        &mut plan,
825    );
826
827    let messages = get_messages(&errors);
828    assert_eq!(messages.len(), 0);
829}
830
831#[test]
832fn string_to_non_null_int_with_default_on_argument() {
833    use crate::validation::test_utils::*;
834
835    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
836    let errors = test_operation_with_schema(
837        "query Query($stringVar: String) {
838          complicatedArgs {
839            nonNullFieldWithDefault(arg: $stringVar)
840          }
841        }",
842        TEST_SCHEMA,
843        &mut plan,
844    );
845
846    let messages = get_messages(&errors);
847    assert_eq!(messages.len(), 1);
848    assert_eq!(
849        messages,
850        vec![
851            "Variable \"$stringVar\" of type \"String\" used in position expecting type \"Int!\"."
852        ]
853    )
854}
855
856#[cfg(test)]
857static ONE_OF_SCHEMA: &str = "
858    directive @oneOf on INPUT_OBJECT
859    input OneOfInput @oneOf { a: String b: Int }
860    input Holder { one: OneOfInput name: String }
861    type Query { oneOf(input: OneOfInput): String holder(input: Holder): String }";
862
863#[test]
864fn non_null_variable_for_oneof_field() {
865    use crate::validation::test_utils::*;
866
867    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
868    let errors = test_operation_with_schema(
869        "query ($a: String!, $h: String) { oneOf(input: { a: $a }) holder(input: { name: $h }) }",
870        ONE_OF_SCHEMA,
871        &mut plan,
872    );
873    assert_eq!(get_messages(&errors).len(), 0);
874}
875
876#[test]
877fn nullable_variable_for_oneof_field() {
878    use crate::validation::test_utils::*;
879
880    let mut plan = create_plan_from_rule(Box::new(VariablesInAllowedPosition::new()));
881    let errors = test_operation_with_schema(
882        r#"query ($a: String, $b: Int = 1) { a: oneOf(input: { a: $a }) b: holder(input: { one: { b: $b } }) }"#,
883        ONE_OF_SCHEMA,
884        &mut plan,
885    );
886    let mut messages = get_messages(&errors);
887    messages.sort();
888    assert_eq!(
889        messages,
890        vec![
891            "Variable \"$a\" is of type \"String\" but must be non-nullable to be used for OneOf Input Object \"OneOfInput\".",
892            "Variable \"$b\" is of type \"Int\" but must be non-nullable to be used for OneOf Input Object \"OneOfInput\".",
893        ]
894    );
895}