graphql_tools/validation/rules/
unique_variable_names.rs

1use std::collections::hash_map::Entry;
2use std::collections::HashMap;
3
4use crate::parser::Pos;
5
6use super::ValidationRule;
7use crate::ast::{visit_document, OperationVisitor, OperationVisitorContext};
8use crate::static_graphql::query::*;
9use crate::validation::utils::{ValidationError, ValidationErrorContext};
10
11/// Unique variable names
12///
13/// A GraphQL operation is only valid if all its variables are uniquely named.
14///
15/// See https://spec.graphql.org/draft/#sec-Variable-Uniqueness
16#[derive(Default)]
17pub struct UniqueVariableNames<'a> {
18    found_records: HashMap<&'a str, Pos>,
19}
20
21impl<'a> UniqueVariableNames<'a> {
22    pub fn new() -> Self {
23        UniqueVariableNames {
24            found_records: HashMap::new(),
25        }
26    }
27}
28
29impl<'a> OperationVisitor<'a, ValidationErrorContext> for UniqueVariableNames<'a> {
30    fn enter_operation_definition(
31        &mut self,
32        _: &mut OperationVisitorContext,
33        _: &mut ValidationErrorContext,
34        _operation_definition: &OperationDefinition,
35    ) {
36        self.found_records.clear();
37    }
38
39    fn enter_variable_definition(
40        &mut self,
41        _: &mut OperationVisitorContext,
42        user_context: &mut ValidationErrorContext,
43        variable_definition: &'a VariableDefinition,
44    ) {
45        let error_code = self.error_code();
46        match self.found_records.entry(&variable_definition.name) {
47            Entry::Occupied(entry) => user_context.report_error(ValidationError {
48                error_code,
49                locations: vec![*entry.get(), variable_definition.position],
50                message: format!(
51                    "There can only be one variable named \"${}\".",
52                    variable_definition.name
53                ),
54            }),
55            Entry::Vacant(entry) => {
56                entry.insert(variable_definition.position);
57            }
58        };
59    }
60}
61
62impl<'v> ValidationRule for UniqueVariableNames<'v> {
63    fn error_code<'a>(&self) -> &'a str {
64        "UniqueVariableNames"
65    }
66
67    fn validate(
68        &self,
69        ctx: &mut OperationVisitorContext,
70        error_collector: &mut ValidationErrorContext,
71    ) {
72        visit_document(
73            &mut UniqueVariableNames::new(),
74            ctx.operation,
75            ctx,
76            error_collector,
77        );
78    }
79}
80
81#[test]
82fn unique_variable_names() {
83    use crate::validation::test_utils::*;
84
85    let mut plan = create_plan_from_rule(Box::new(UniqueVariableNames::new()));
86    let errors = test_operation_with_schema(
87        "query A($x: Int, $y: String) { __typename }
88        query B($x: String, $y: Int) { __typename }",
89        TEST_SCHEMA,
90        &mut plan,
91    );
92
93    assert_eq!(get_messages(&errors).len(), 0);
94}
95
96#[test]
97fn duplicate_variable_names() {
98    use crate::validation::test_utils::*;
99
100    let mut plan = create_plan_from_rule(Box::new(UniqueVariableNames::new()));
101    let errors = test_operation_with_schema(
102        "query A($x: Int, $x: Int, $x: String) { __typename }
103        query B($y: String, $y: Int) { __typename }
104        query C($z: Int, $z: Int) { __typename }",
105        TEST_SCHEMA,
106        &mut plan,
107    );
108
109    let messages = get_messages(&errors);
110
111    assert_eq!(messages.len(), 4);
112    assert!(messages.contains(&&"There can only be one variable named \"$x\".".to_owned()));
113    assert!(messages.contains(&&"There can only be one variable named \"$y\".".to_owned()));
114    assert!(messages.contains(&&"There can only be one variable named \"$z\".".to_owned()));
115}