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}