Skip to main content

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::{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<'doc> {
18    found_records: HashMap<&'doc str, Pos>,
19}
20
21impl UniqueVariableNames<'_> {
22    pub fn new() -> Self {
23        UniqueVariableNames {
24            found_records: HashMap::new(),
25        }
26    }
27}
28
29impl<'doc> OperationVisitor<'doc, ValidationErrorContext> for UniqueVariableNames<'doc> {
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: &'doc 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 ValidationRule for UniqueVariableNames<'_> {
63    fn error_code(&self) -> &'static str {
64        "UniqueVariableNames"
65    }
66
67    fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
68        Box::new(UniqueVariableNames::new())
69    }
70}
71
72#[test]
73fn unique_variable_names() {
74    use crate::validation::test_utils::*;
75
76    let mut plan = create_plan_from_rule(Box::new(UniqueVariableNames::new()));
77    let errors = test_operation_with_schema(
78        "query A($x: Int, $y: String) { __typename }
79        query B($x: String, $y: Int) { __typename }",
80        TEST_SCHEMA,
81        &mut plan,
82    );
83
84    assert_eq!(get_messages(&errors).len(), 0);
85}
86
87#[test]
88fn duplicate_variable_names() {
89    use crate::validation::test_utils::*;
90
91    let mut plan = create_plan_from_rule(Box::new(UniqueVariableNames::new()));
92    let errors = test_operation_with_schema(
93        "query A($x: Int, $x: Int, $x: String) { __typename }
94        query B($y: String, $y: Int) { __typename }
95        query C($z: Int, $z: Int) { __typename }",
96        TEST_SCHEMA,
97        &mut plan,
98    );
99
100    let messages = get_messages(&errors);
101
102    assert_eq!(messages.len(), 4);
103    assert!(messages.contains(&&"There can only be one variable named \"$x\".".to_owned()));
104    assert!(messages.contains(&&"There can only be one variable named \"$y\".".to_owned()));
105    assert!(messages.contains(&&"There can only be one variable named \"$z\".".to_owned()));
106}