2020-09-06 05:38:31 +00:00
|
|
|
use crate::parser::types::{
|
2020-10-11 12:24:31 +00:00
|
|
|
ExecutableDocument, FragmentDefinition, FragmentSpread, OperationDefinition, VariableDefinition,
|
2020-05-09 09:55:04 +00:00
|
|
|
};
|
2020-05-15 02:08:37 +00:00
|
|
|
use crate::registry::MetaTypeName;
|
2020-09-06 05:38:31 +00:00
|
|
|
use crate::validation::utils::Scope;
|
2020-03-24 10:54:22 +00:00
|
|
|
use crate::validation::visitor::{Visitor, VisitorContext};
|
2020-10-11 12:24:31 +00:00
|
|
|
use crate::{Name, Pos, Positioned};
|
|
|
|
use async_graphql_value::Value;
|
2020-04-05 08:00:26 +00:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2020-03-10 06:14:09 +00:00
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct VariableInAllowedPosition<'a> {
|
2020-04-05 08:00:26 +00:00
|
|
|
spreads: HashMap<Scope<'a>, HashSet<&'a str>>,
|
2020-05-15 02:08:37 +00:00
|
|
|
variable_usages: HashMap<Scope<'a>, Vec<(&'a str, Pos, MetaTypeName<'a>)>>,
|
2020-05-10 02:59:51 +00:00
|
|
|
variable_defs: HashMap<Scope<'a>, Vec<&'a Positioned<VariableDefinition>>>,
|
2020-04-05 08:00:26 +00:00
|
|
|
current_scope: Option<Scope<'a>>,
|
2020-03-10 06:14:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> VariableInAllowedPosition<'a> {
|
2020-04-05 08:00:26 +00:00
|
|
|
fn collect_incorrect_usages(
|
|
|
|
&self,
|
|
|
|
from: &Scope<'a>,
|
2020-05-10 02:59:51 +00:00
|
|
|
var_defs: &[&'a Positioned<VariableDefinition>],
|
2020-03-22 08:45:59 +00:00
|
|
|
ctx: &mut VisitorContext<'a>,
|
2020-04-05 08:00:26 +00:00
|
|
|
visited: &mut HashSet<Scope<'a>>,
|
2020-03-10 06:14:09 +00:00
|
|
|
) {
|
2020-04-05 08:00:26 +00:00
|
|
|
if visited.contains(from) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
visited.insert(from.clone());
|
|
|
|
|
|
|
|
if let Some(usages) = self.variable_usages.get(from) {
|
|
|
|
for (var_name, usage_pos, var_type) in usages {
|
2020-09-06 05:38:31 +00:00
|
|
|
if let Some(def) = var_defs.iter().find(|def| def.node.name.node == *var_name) {
|
2020-09-06 06:16:36 +00:00
|
|
|
let expected_type =
|
|
|
|
if def.node.var_type.node.nullable && def.node.default_value.is_some() {
|
|
|
|
// A nullable type with a default value functions as a non-nullable
|
|
|
|
format!("{}!", def.node.var_type.node)
|
|
|
|
} else {
|
|
|
|
def.node.var_type.node.to_string()
|
|
|
|
};
|
2020-04-05 08:00:26 +00:00
|
|
|
|
2020-05-15 02:08:37 +00:00
|
|
|
if !var_type.is_subtype(&MetaTypeName::create(&expected_type)) {
|
2020-03-10 06:14:09 +00:00
|
|
|
ctx.report_error(
|
2020-09-06 05:38:31 +00:00
|
|
|
vec![def.pos, *usage_pos],
|
2020-03-10 06:14:09 +00:00
|
|
|
format!(
|
2020-09-06 05:38:31 +00:00
|
|
|
"Variable \"{}\" of type \"{}\" used in position expecting type \"{}\"",
|
|
|
|
var_name, var_type, expected_type
|
|
|
|
),
|
2020-03-10 06:14:09 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-05 08:00:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(spreads) = self.spreads.get(from) {
|
|
|
|
for spread in spreads {
|
|
|
|
self.collect_incorrect_usages(&Scope::Fragment(spread), var_defs, ctx, visited);
|
2020-03-10 06:14:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Visitor<'a> for VariableInAllowedPosition<'a> {
|
2020-09-08 08:21:27 +00:00
|
|
|
fn exit_document(&mut self, ctx: &mut VisitorContext<'a>, _doc: &'a ExecutableDocument) {
|
2020-04-05 08:00:26 +00:00
|
|
|
for (op_scope, var_defs) in &self.variable_defs {
|
|
|
|
self.collect_incorrect_usages(op_scope, var_defs, ctx, &mut HashSet::new());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-10 06:14:09 +00:00
|
|
|
fn enter_operation_definition(
|
|
|
|
&mut self,
|
2020-03-22 08:45:59 +00:00
|
|
|
_ctx: &mut VisitorContext<'a>,
|
2020-09-22 18:59:48 +00:00
|
|
|
name: Option<&'a Name>,
|
|
|
|
_operation_definition: &'a Positioned<OperationDefinition>,
|
2020-03-10 06:14:09 +00:00
|
|
|
) {
|
2020-09-22 18:59:48 +00:00
|
|
|
self.current_scope = Some(Scope::Operation(name.map(|name| name.as_str())));
|
2020-04-05 08:00:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn enter_fragment_definition(
|
|
|
|
&mut self,
|
|
|
|
_ctx: &mut VisitorContext<'a>,
|
2020-09-22 18:59:48 +00:00
|
|
|
name: &'a Name,
|
|
|
|
_fragment_definition: &'a Positioned<FragmentDefinition>,
|
2020-04-05 08:00:26 +00:00
|
|
|
) {
|
2020-09-22 18:59:48 +00:00
|
|
|
self.current_scope = Some(Scope::Fragment(name));
|
2020-03-10 06:14:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn enter_variable_definition(
|
|
|
|
&mut self,
|
2020-03-22 08:45:59 +00:00
|
|
|
_ctx: &mut VisitorContext<'a>,
|
2020-05-10 02:59:51 +00:00
|
|
|
variable_definition: &'a Positioned<VariableDefinition>,
|
2020-03-10 06:14:09 +00:00
|
|
|
) {
|
2020-04-05 08:00:26 +00:00
|
|
|
if let Some(ref scope) = self.current_scope {
|
|
|
|
self.variable_defs
|
|
|
|
.entry(scope.clone())
|
|
|
|
.or_insert_with(Vec::new)
|
2020-05-09 09:55:04 +00:00
|
|
|
.push(variable_definition);
|
2020-04-05 08:00:26 +00:00
|
|
|
}
|
2020-03-10 06:14:09 +00:00
|
|
|
}
|
|
|
|
|
2020-04-05 08:00:26 +00:00
|
|
|
fn enter_fragment_spread(
|
|
|
|
&mut self,
|
|
|
|
_ctx: &mut VisitorContext<'a>,
|
2020-05-10 02:59:51 +00:00
|
|
|
fragment_spread: &'a Positioned<FragmentSpread>,
|
2020-04-05 08:00:26 +00:00
|
|
|
) {
|
|
|
|
if let Some(ref scope) = self.current_scope {
|
|
|
|
self.spreads
|
|
|
|
.entry(scope.clone())
|
|
|
|
.or_insert_with(HashSet::new)
|
2020-09-06 05:38:31 +00:00
|
|
|
.insert(&fragment_spread.node.fragment_name.node);
|
2020-03-10 06:14:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-05 08:00:26 +00:00
|
|
|
fn enter_input_value(
|
|
|
|
&mut self,
|
|
|
|
_ctx: &mut VisitorContext<'a>,
|
|
|
|
pos: Pos,
|
2020-05-15 02:08:37 +00:00
|
|
|
expected_type: &Option<MetaTypeName<'a>>,
|
2020-04-05 08:00:26 +00:00
|
|
|
value: &'a Value,
|
|
|
|
) {
|
|
|
|
if let Value::Variable(name) = value {
|
|
|
|
if let Some(expected_type) = expected_type {
|
|
|
|
if let Some(scope) = &self.current_scope {
|
|
|
|
self.variable_usages
|
|
|
|
.entry(scope.clone())
|
|
|
|
.or_insert_with(Vec::new)
|
2020-05-12 08:27:06 +00:00
|
|
|
.push((name, pos, *expected_type));
|
2020-03-10 06:14:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-05 08:00:26 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
pub fn factory<'a>() -> VariableInAllowedPosition<'a> {
|
|
|
|
VariableInAllowedPosition::default()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn boolean_into_boolean() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($booleanArg: Boolean)
|
|
|
|
{
|
|
|
|
complicatedArgs {
|
|
|
|
booleanArgField(booleanArg: $booleanArg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn boolean_into_boolean_within_fragment() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
fragment booleanArgFrag on ComplicatedArgs {
|
|
|
|
booleanArgField(booleanArg: $booleanArg)
|
|
|
|
}
|
|
|
|
query Query($booleanArg: Boolean)
|
|
|
|
{
|
|
|
|
complicatedArgs {
|
|
|
|
...booleanArgFrag
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($booleanArg: Boolean)
|
|
|
|
{
|
|
|
|
complicatedArgs {
|
|
|
|
...booleanArgFrag
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fragment booleanArgFrag on ComplicatedArgs {
|
|
|
|
booleanArgField(booleanArg: $booleanArg)
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn non_null_boolean_into_boolean() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($nonNullBooleanArg: Boolean!)
|
|
|
|
{
|
|
|
|
complicatedArgs {
|
|
|
|
booleanArgField(booleanArg: $nonNullBooleanArg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn non_null_boolean_into_boolean_within_fragment() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
fragment booleanArgFrag on ComplicatedArgs {
|
|
|
|
booleanArgField(booleanArg: $nonNullBooleanArg)
|
|
|
|
}
|
|
|
|
query Query($nonNullBooleanArg: Boolean!)
|
|
|
|
{
|
|
|
|
complicatedArgs {
|
|
|
|
...booleanArgFrag
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn int_into_non_null_int_with_default() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($intArg: Int = 1)
|
|
|
|
{
|
|
|
|
complicatedArgs {
|
|
|
|
nonNullIntArgField(nonNullIntArg: $intArg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn string_list_into_string_list() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($stringListVar: [String])
|
|
|
|
{
|
|
|
|
complicatedArgs {
|
|
|
|
stringListArgField(stringListArg: $stringListVar)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn non_null_string_list_into_string_list() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($stringListVar: [String!])
|
|
|
|
{
|
|
|
|
complicatedArgs {
|
|
|
|
stringListArgField(stringListArg: $stringListVar)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn string_into_string_list_in_item_position() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($stringVar: String)
|
|
|
|
{
|
|
|
|
complicatedArgs {
|
|
|
|
stringListArgField(stringListArg: [$stringVar])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn non_null_string_into_string_list_in_item_position() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($stringVar: String!)
|
|
|
|
{
|
|
|
|
complicatedArgs {
|
|
|
|
stringListArgField(stringListArg: [$stringVar])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn complex_input_into_complex_input() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($complexVar: ComplexInput)
|
|
|
|
{
|
|
|
|
complicatedArgs {
|
|
|
|
complexArgField(complexArg: $complexVar)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn complex_input_into_complex_input_in_field_position() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($boolVar: Boolean = false)
|
|
|
|
{
|
|
|
|
complicatedArgs {
|
|
|
|
complexArgField(complexArg: {requiredArg: $boolVar})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn non_null_boolean_into_non_null_boolean_in_directive() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($boolVar: Boolean!)
|
|
|
|
{
|
|
|
|
dog @include(if: $boolVar)
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn boolean_in_non_null_in_directive_with_default() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_passes_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($boolVar: Boolean = false)
|
|
|
|
{
|
|
|
|
dog @include(if: $boolVar)
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn int_into_non_null_int() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_fails_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($intArg: Int) {
|
|
|
|
complicatedArgs {
|
|
|
|
nonNullIntArgField(nonNullIntArg: $intArg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn int_into_non_null_int_within_fragment() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_fails_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
fragment nonNullIntArgFieldFrag on ComplicatedArgs {
|
|
|
|
nonNullIntArgField(nonNullIntArg: $intArg)
|
|
|
|
}
|
|
|
|
query Query($intArg: Int) {
|
|
|
|
complicatedArgs {
|
|
|
|
...nonNullIntArgFieldFrag
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn int_into_non_null_int_within_nested_fragment() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_fails_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
fragment outerFrag on ComplicatedArgs {
|
|
|
|
...nonNullIntArgFieldFrag
|
|
|
|
}
|
|
|
|
fragment nonNullIntArgFieldFrag on ComplicatedArgs {
|
|
|
|
nonNullIntArgField(nonNullIntArg: $intArg)
|
|
|
|
}
|
|
|
|
query Query($intArg: Int) {
|
|
|
|
complicatedArgs {
|
|
|
|
...outerFrag
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn string_over_boolean() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_fails_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($stringVar: String) {
|
|
|
|
complicatedArgs {
|
|
|
|
booleanArgField(booleanArg: $stringVar)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn string_into_string_list() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_fails_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($stringVar: String) {
|
|
|
|
complicatedArgs {
|
|
|
|
stringListArgField(stringListArg: $stringVar)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn boolean_into_non_null_boolean_in_directive() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_fails_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($boolVar: Boolean) {
|
|
|
|
dog @include(if: $boolVar)
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn string_into_non_null_boolean_in_directive() {
|
2020-05-29 09:29:15 +00:00
|
|
|
expect_fails_rule!(
|
2020-04-05 08:00:26 +00:00
|
|
|
factory,
|
|
|
|
r#"
|
|
|
|
query Query($stringVar: String) {
|
|
|
|
dog @include(if: $stringVar)
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|