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()));
}