juniper/validation/rules/
unique_variable_names.rs1use std::collections::hash_map::{Entry, HashMap};
2
3use crate::{
4    ast::{Operation, VariableDefinition},
5    parser::{SourcePosition, Spanning},
6    validation::{ValidatorContext, Visitor},
7    value::ScalarValue,
8};
9
10pub struct UniqueVariableNames<'a> {
11    names: HashMap<&'a str, SourcePosition>,
12}
13
14pub fn factory<'a>() -> UniqueVariableNames<'a> {
15    UniqueVariableNames {
16        names: HashMap::new(),
17    }
18}
19
20impl<'a, S> Visitor<'a, S> for UniqueVariableNames<'a>
21where
22    S: ScalarValue,
23{
24    fn enter_operation_definition(
25        &mut self,
26        _: &mut ValidatorContext<'a, S>,
27        _: &'a Spanning<Operation<S>>,
28    ) {
29        self.names = HashMap::new();
30    }
31
32    fn enter_variable_definition(
33        &mut self,
34        ctx: &mut ValidatorContext<'a, S>,
35        (var_name, _): &'a (Spanning<&'a str>, VariableDefinition<S>),
36    ) {
37        match self.names.entry(var_name.item) {
38            Entry::Occupied(e) => {
39                ctx.report_error(
40                    &error_message(var_name.item),
41                    &[*e.get(), var_name.span.start],
42                );
43            }
44            Entry::Vacant(e) => {
45                e.insert(var_name.span.start);
46            }
47        }
48    }
49}
50
51fn error_message(var_name: &str) -> String {
52    format!("There can only be one variable named {var_name}")
53}
54
55#[cfg(test)]
56mod tests {
57    use super::{error_message, factory};
58
59    use crate::{
60        parser::SourcePosition,
61        validation::{RuleError, expect_fails_rule, expect_passes_rule},
62        value::DefaultScalarValue,
63    };
64
65    #[test]
66    fn unique_variable_names() {
67        expect_passes_rule::<_, _, DefaultScalarValue>(
68            factory,
69            r#"
70          query A($x: Int, $y: String) { __typename }
71          query B($x: String, $y: Int) { __typename }
72        "#,
73        );
74    }
75
76    #[test]
77    fn duplicate_variable_names() {
78        expect_fails_rule::<_, _, DefaultScalarValue>(
79            factory,
80            r#"
81          query A($x: Int, $x: Int, $x: String) { __typename }
82          query B($x: String, $x: Int) { __typename }
83          query C($x: Int, $x: Int) { __typename }
84        "#,
85            &[
86                RuleError::new(
87                    &error_message("x"),
88                    &[
89                        SourcePosition::new(19, 1, 18),
90                        SourcePosition::new(28, 1, 27),
91                    ],
92                ),
93                RuleError::new(
94                    &error_message("x"),
95                    &[
96                        SourcePosition::new(19, 1, 18),
97                        SourcePosition::new(37, 1, 36),
98                    ],
99                ),
100                RuleError::new(
101                    &error_message("x"),
102                    &[
103                        SourcePosition::new(82, 2, 18),
104                        SourcePosition::new(94, 2, 30),
105                    ],
106                ),
107                RuleError::new(
108                    &error_message("x"),
109                    &[
110                        SourcePosition::new(136, 3, 18),
111                        SourcePosition::new(145, 3, 27),
112                    ],
113                ),
114            ],
115        );
116    }
117}