1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use std::collections::hash_map::Entry;
use std::collections::HashMap;

use graphql_parser::Pos;

use super::ValidationRule;
use crate::ast::{visit_document, OperationVisitor, OperationVisitorContext};
use crate::static_graphql::query::*;
use crate::validation::utils::{ValidationError, ValidationErrorContext};

/// Unique variable names
///
/// A GraphQL operation is only valid if all its variables are uniquely named.
///
/// See https://spec.graphql.org/draft/#sec-Variable-Uniqueness
pub struct UniqueVariableNames {
    found_records: HashMap<String, Pos>,
}

impl UniqueVariableNames {
    pub fn new() -> Self {
        UniqueVariableNames {
            found_records: HashMap::new(),
        }
    }
}

impl<'a> OperationVisitor<'a, ValidationErrorContext> for UniqueVariableNames {
    fn enter_operation_definition(
        &mut self,
        _: &mut OperationVisitorContext,
        _: &mut ValidationErrorContext,
        _operation_definition: &OperationDefinition,
    ) {
        self.found_records.clear();
    }

    fn enter_variable_definition(
        &mut self,
        _: &mut OperationVisitorContext,
        user_context: &mut ValidationErrorContext,
        variable_definition: &VariableDefinition,
    ) {
        match self.found_records.entry(variable_definition.name.clone()) {
            Entry::Occupied(entry) => user_context.report_error(ValidationError {
                locations: vec![*entry.get(), variable_definition.position],
                message: format!(
                    "There can only be one variable named \"${}\".",
                    variable_definition.name
                ),
            }),
            Entry::Vacant(entry) => {
                entry.insert(variable_definition.position);
            }
        };
    }
}

impl ValidationRule for UniqueVariableNames {
    fn validate<'a>(
        &self,
        ctx: &'a mut OperationVisitorContext,
        error_collector: &mut ValidationErrorContext,
    ) {
        visit_document(
            &mut UniqueVariableNames::new(),
            &ctx.operation,
            ctx,
            error_collector,
        );
    }
}

#[test]
fn unique_variable_names() {
    use crate::validation::test_utils::*;

    let mut plan = create_plan_from_rule(Box::new(UniqueVariableNames::new()));
    let errors = test_operation_with_schema(
        "query A($x: Int, $y: String) { __typename }
        query B($x: String, $y: Int) { __typename }",
        TEST_SCHEMA,
        &mut plan,
    );

    assert_eq!(get_messages(&errors).len(), 0);
}

#[test]
fn duplicate_variable_names() {
    use crate::validation::test_utils::*;

    let mut plan = create_plan_from_rule(Box::new(UniqueVariableNames::new()));
    let errors = test_operation_with_schema(
        "query A($x: Int, $x: Int, $x: String) { __typename }
        query B($y: String, $y: Int) { __typename }
        query C($z: Int, $z: Int) { __typename }",
        TEST_SCHEMA,
        &mut plan,
    );

    let messages = get_messages(&errors);

    assert_eq!(messages.len(), 4);
    assert!(messages.contains(&&"There can only be one variable named \"$x\".".to_owned()));
    assert!(messages.contains(&&"There can only be one variable named \"$y\".".to_owned()));
    assert!(messages.contains(&&"There can only be one variable named \"$z\".".to_owned()));
}