juniper_puff 0.16.0-dev

GraphQL server library. Release for Puff
Documentation
use std::fmt;

use crate::{
    ast::VariableDefinition,
    parser::Spanning,
    types::utilities::is_valid_literal_value,
    validation::{ValidatorContext, Visitor},
    value::ScalarValue,
};

pub struct DefaultValuesOfCorrectType;

pub fn factory() -> DefaultValuesOfCorrectType {
    DefaultValuesOfCorrectType
}

impl<'a, S> Visitor<'a, S> for DefaultValuesOfCorrectType
where
    S: ScalarValue,
{
    fn enter_variable_definition(
        &mut self,
        ctx: &mut ValidatorContext<'a, S>,
        &(ref var_name, ref var_def): &'a (Spanning<&'a str>, VariableDefinition<S>),
    ) {
        if let Some(Spanning {
            item: ref var_value,
            ref start,
            ..
        }) = var_def.default_value
        {
            if var_def.var_type.item.is_non_null() {
                ctx.report_error(
                    &non_null_error_message(var_name.item, &var_def.var_type.item),
                    &[*start],
                )
            } else {
                let meta_type = ctx.schema.make_type(&var_def.var_type.item);

                if !is_valid_literal_value(ctx.schema, &meta_type, var_value) {
                    ctx.report_error(
                        &type_error_message(var_name.item, &var_def.var_type.item),
                        &[*start],
                    );
                }
            }
        }
    }
}

fn type_error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String {
    format!("Invalid default value for argument \"{arg_name}\", expected type \"{type_name}\"")
}

fn non_null_error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String {
    format!(
        "Argument \"{arg_name}\" has type \"{type_name}\" and is not nullable, \
         so it can't have a default value",
    )
}

#[cfg(test)]
mod tests {
    use super::{factory, non_null_error_message, type_error_message};

    use crate::{
        parser::SourcePosition,
        validation::{expect_fails_rule, expect_passes_rule, RuleError},
        value::DefaultScalarValue,
    };

    #[test]
    fn variables_with_no_default_values() {
        expect_passes_rule::<_, _, DefaultScalarValue>(
            factory,
            r#"
          query NullableValues($a: Int, $b: String, $c: ComplexInput) {
            dog { name }
          }
        "#,
        );
    }

    #[test]
    fn required_variables_without_default_values() {
        expect_passes_rule::<_, _, DefaultScalarValue>(
            factory,
            r#"
          query RequiredValues($a: Int!, $b: String!) {
            dog { name }
          }
        "#,
        );
    }

    #[test]
    fn variables_with_valid_default_values() {
        expect_passes_rule::<_, _, DefaultScalarValue>(
            factory,
            r#"
          query WithDefaultValues(
            $a: Int = 1,
            $b: String = "ok",
            $c: ComplexInput = { requiredField: true, intField: 3 }
          ) {
            dog { name }
          }
        "#,
        );
    }

    #[test]
    fn no_required_variables_with_default_values() {
        expect_fails_rule::<_, _, DefaultScalarValue>(
            factory,
            r#"
          query UnreachableDefaultValues($a: Int! = 3, $b: String! = "default") {
            dog { name }
          }
        "#,
            &[
                RuleError::new(
                    &non_null_error_message("a", "Int!"),
                    &[SourcePosition::new(53, 1, 52)],
                ),
                RuleError::new(
                    &non_null_error_message("b", "String!"),
                    &[SourcePosition::new(70, 1, 69)],
                ),
            ],
        );
    }

    #[test]
    fn variables_with_invalid_default_values() {
        expect_fails_rule::<_, _, DefaultScalarValue>(
            factory,
            r#"
          query InvalidDefaultValues(
            $a: Int = "one",
            $b: String = 4,
            $c: ComplexInput = "notverycomplex"
          ) {
            dog { name }
          }
        "#,
            &[
                RuleError::new(
                    &type_error_message("a", "Int"),
                    &[SourcePosition::new(61, 2, 22)],
                ),
                RuleError::new(
                    &type_error_message("b", "String"),
                    &[SourcePosition::new(93, 3, 25)],
                ),
                RuleError::new(
                    &type_error_message("c", "ComplexInput"),
                    &[SourcePosition::new(127, 4, 31)],
                ),
            ],
        );
    }

    #[test]
    fn complex_variables_missing_required_field() {
        expect_fails_rule::<_, _, DefaultScalarValue>(
            factory,
            r#"
          query MissingRequiredField($a: ComplexInput = {intField: 3}) {
            dog { name }
          }
        "#,
            &[RuleError::new(
                &type_error_message("a", "ComplexInput"),
                &[SourcePosition::new(57, 1, 56)],
            )],
        );
    }

    #[test]
    fn list_variables_with_invalid_item() {
        expect_fails_rule::<_, _, DefaultScalarValue>(
            factory,
            r#"
          query InvalidItem($a: [String] = ["one", 2]) {
            dog { name }
          }
        "#,
            &[RuleError::new(
                &type_error_message("a", "[String]"),
                &[SourcePosition::new(44, 1, 43)],
            )],
        );
    }
}