Improve validation errors for input values (#811, #693)

Co-authored-by: Kai Ren <tyranron@gmail.com>
This commit is contained in:
Zak 2023-11-30 14:51:08 -06:00 committed by GitHub
parent 7c03f922e0
commit ace693585d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 352 additions and 221 deletions

View file

@ -78,6 +78,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Made `GraphQLRequest` fields public. ([#750])
- Relaxed [object safety] requirement for `GraphQLValue` and `GraphQLValueAsync` traits. ([ba1ed85b])
- Updated [GraphQL Playground] to 1.7.28 version. ([#1190])
- Improve validation errors for input values. ([#811], [#693])
## Fixed
@ -94,8 +95,10 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#456]: /../../issues/456
[#503]: /../../issues/503
[#528]: /../../issues/528
[#693]: /../../issues/693
[#750]: /../../issues/750
[#798]: /../../issues/798
[#811]: /../../pull/811
[#918]: /../../issues/918
[#965]: /../../pull/965
[#966]: /../../pull/966

View file

@ -100,7 +100,7 @@ async fn does_not_accept_string_literals() {
assert_eq!(
error,
ValidationError(vec![RuleError::new(
r#"Invalid value for argument "color", expected type "Color!""#,
r#"Invalid value for argument "color", reason: Invalid value ""RED"" for enum "Color""#,
&[SourcePosition::new(18, 0, 18)],
)])
);

View file

@ -916,7 +916,8 @@ async fn does_not_allow_missing_required_field() {
assert_eq!(
error,
ValidationError(vec![RuleError::new(
r#"Invalid value for argument "arg", expected type "ExampleInputObject!""#,
"Invalid value for argument \"arg\", \
reason: \"ExampleInputObject\" is missing fields: \"b\"",
&[SourcePosition::new(20, 0, 20)],
)]),
);
@ -940,7 +941,9 @@ async fn does_not_allow_null_in_required_field() {
assert_eq!(
error,
ValidationError(vec![RuleError::new(
r#"Invalid value for argument "arg", expected type "ExampleInputObject!""#,
"Invalid value for argument \"arg\", \
reason: Error on \"ExampleInputObject\" field \"b\": \
\"null\" specified for not nullable type \"Int!\"",
&[SourcePosition::new(20, 0, 20)],
)]),
);

View file

@ -1,48 +1,126 @@
use std::{collections::HashSet, iter::Iterator};
use crate::{
ast::InputValue,
schema::{
meta::{EnumMeta, InputObjectMeta, MetaType},
meta::{Argument, EnumMeta, InputObjectMeta, MetaType},
model::{SchemaType, TypeType},
},
value::ScalarValue,
};
use std::collections::HashSet;
pub fn is_valid_literal_value<S>(
/// Common error messages used in validation and execution of GraphQL operations
pub(crate) mod error {
use std::fmt::Display;
pub(crate) fn non_null(arg_type: impl Display) -> String {
format!("\"null\" specified for not nullable type \"{arg_type}\"")
}
pub(crate) fn enum_value(arg_value: impl Display, arg_type: impl Display) -> String {
format!("Invalid value \"{arg_value}\" for enum \"{arg_type}\"")
}
pub(crate) fn type_value(arg_value: impl Display, arg_type: impl Display) -> String {
format!("Invalid value \"{arg_value}\" for type \"{arg_type}\"")
}
pub(crate) fn parser(arg_type: impl Display, msg: impl Display) -> String {
format!("Parser error for \"{arg_type}\": {msg}")
}
pub(crate) fn not_input_object(arg_type: impl Display) -> String {
format!("\"{arg_type}\" is not an input object")
}
pub(crate) fn field(
arg_type: impl Display,
field_name: impl Display,
error_message: impl Display,
) -> String {
format!("Error on \"{arg_type}\" field \"{field_name}\": {error_message}")
}
pub(crate) fn missing_fields(arg_type: impl Display, missing_fields: impl Display) -> String {
format!("\"{arg_type}\" is missing fields: {missing_fields}")
}
pub(crate) fn unknown_field(arg_type: impl Display, field_name: impl Display) -> String {
format!("Field \"{field_name}\" does not exist on type \"{arg_type}\"")
}
pub(crate) fn invalid_list_length(
arg_value: impl Display,
actual: usize,
expected: usize,
) -> String {
format!("Expected list of length {expected}, but \"{arg_value}\" has length {actual}")
}
}
/// Validates the specified field of a GraphQL object and returns an error message if the field is
/// invalid.
fn validate_object_field<S>(
schema: &SchemaType<S>,
object_type: &TypeType<S>,
object_fields: &[Argument<S>],
field_value: &InputValue<S>,
field_key: &str,
) -> Option<String>
where
S: ScalarValue,
{
let field_type = object_fields
.iter()
.filter(|f| f.name == field_key)
.map(|f| schema.make_type(&f.arg_type))
.next();
if let Some(field_arg_type) = field_type {
let error_message = validate_literal_value(schema, &field_arg_type, field_value);
error_message.map(|m| error::field(object_type, field_key, m))
} else {
Some(error::unknown_field(object_type, field_key))
}
}
/// Validates the specified GraphQL literal and returns an error message if the it's invalid.
pub fn validate_literal_value<S>(
schema: &SchemaType<S>,
arg_type: &TypeType<S>,
arg_value: &InputValue<S>,
) -> bool
) -> Option<String>
where
S: ScalarValue,
{
match *arg_type {
TypeType::NonNull(ref inner) => {
if arg_value.is_null() {
false
Some(error::non_null(arg_type))
} else {
is_valid_literal_value(schema, inner, arg_value)
validate_literal_value(schema, inner, arg_value)
}
}
TypeType::List(ref inner, expected_size) => match *arg_value {
InputValue::Null | InputValue::Variable(_) => true,
InputValue::Null | InputValue::Variable(_) => None,
InputValue::List(ref items) => {
if let Some(expected) = expected_size {
if items.len() != expected {
return false;
return Some(error::invalid_list_length(arg_value, items.len(), expected));
}
}
items
.iter()
.all(|i| is_valid_literal_value(schema, inner, &i.item))
.find_map(|i| validate_literal_value(schema, inner, &i.item))
}
ref v => {
if let Some(expected) = expected_size {
if expected != 1 {
return false;
return Some(error::invalid_list_length(arg_value, 1, expected));
}
}
is_valid_literal_value(schema, inner, v)
validate_literal_value(schema, inner, v)
}
},
TypeType::Concrete(t) => {
@ -51,19 +129,23 @@ where
if let (&InputValue::Scalar(_), Some(&MetaType::Enum(EnumMeta { .. }))) =
(arg_value, arg_type.to_concrete())
{
return false;
return Some(error::enum_value(arg_value, arg_type));
}
match *arg_value {
InputValue::Null | InputValue::Variable(_) => true,
InputValue::Null | InputValue::Variable(_) => None,
ref v @ InputValue::Scalar(_) | ref v @ InputValue::Enum(_) => {
if let Some(parse_fn) = t.input_value_parse_fn() {
parse_fn(v).is_ok()
if parse_fn(v).is_ok() {
None
} else {
false
Some(error::type_value(arg_value, arg_type))
}
} else {
Some(error::parser(arg_type, "no parser present"))
}
}
InputValue::List(_) => false,
InputValue::List(_) => Some("Input lists are not literals".to_owned()),
InputValue::Object(ref obj) => {
if let MetaType::InputObject(InputObjectMeta {
ref input_fields, ..
@ -77,23 +159,33 @@ where
})
.collect::<HashSet<_>>();
let all_types_ok = obj.iter().all(|(key, value)| {
let error_message = obj.iter().find_map(|(key, value)| {
remaining_required_fields.remove(&key.item);
if let Some(ref arg_type) = input_fields
.iter()
.filter(|f| f.name == key.item)
.map(|f| schema.make_type(&f.arg_type))
.next()
{
is_valid_literal_value(schema, arg_type, &value.item)
} else {
false
}
validate_object_field(
schema,
arg_type,
input_fields,
&value.item,
&key.item,
)
});
all_types_ok && remaining_required_fields.is_empty()
if error_message.is_some() {
return error_message;
}
if remaining_required_fields.is_empty() {
None
} else {
false
let missing_fields = remaining_required_fields
.into_iter()
.map(|s| format!("\"{}\"", &**s))
.collect::<Vec<_>>()
.join(", ");
Some(error::missing_fields(arg_type, missing_fields))
}
} else {
Some(error::not_input_object(arg_type))
}
}
}

View file

@ -4,7 +4,7 @@ use crate::{
ast::{Directive, Field, InputValue},
parser::Spanning,
schema::meta::Argument,
types::utilities::is_valid_literal_value,
types::utilities::validate_literal_value,
validation::{ValidatorContext, Visitor},
value::ScalarValue,
};
@ -58,18 +58,15 @@ where
{
let meta_type = ctx.schema.make_type(&argument_meta.arg_type);
if !is_valid_literal_value(ctx.schema, &meta_type, &arg_value.item) {
ctx.report_error(
&error_message(arg_name.item, &argument_meta.arg_type),
&[arg_value.span.start],
);
if let Some(err) = validate_literal_value(ctx.schema, &meta_type, &arg_value.item) {
ctx.report_error(&error_message(arg_name.item, err), &[arg_value.span.start]);
}
}
}
}
fn error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String {
format!("Invalid value for argument \"{arg_name}\", expected type \"{type_name}\"",)
fn error_message(arg_name: impl fmt::Display, msg: impl fmt::Display) -> String {
format!("Invalid value for argument \"{arg_name}\", reason: {msg}")
}
#[cfg(test)]
@ -78,6 +75,7 @@ mod tests {
use crate::{
parser::SourcePosition,
types::utilities::error,
validation::{expect_fails_rule, expect_passes_rule, RuleError},
value::DefaultScalarValue,
};
@ -122,7 +120,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("nonNullIntArg", "Int!"),
&error_message("nonNullIntArg", error::non_null("Int!")),
&[SourcePosition::new(97, 3, 50)],
)],
);
@ -140,7 +138,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("nonNullStringListArg", "[String!]!"),
&error_message("nonNullStringListArg", error::non_null("[String!]!")),
&[SourcePosition::new(111, 3, 64)],
)],
);
@ -270,7 +268,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("stringArg", "String"),
&error_message("stringArg", error::type_value("1", "String")),
&[SourcePosition::new(89, 3, 42)],
)],
);
@ -288,7 +286,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("stringArg", "String"),
&error_message("stringArg", error::type_value("1", "String")),
&[SourcePosition::new(89, 3, 42)],
)],
);
@ -306,7 +304,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("stringArg", "String"),
&error_message("stringArg", error::type_value("true", "String")),
&[SourcePosition::new(89, 3, 42)],
)],
);
@ -324,7 +322,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("stringArg", "String"),
&error_message("stringArg", error::type_value("BAR", "String")),
&[SourcePosition::new(89, 3, 42)],
)],
);
@ -342,7 +340,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("intArg", "Int"),
&error_message("intArg", error::type_value("\"3\"", "Int")),
&[SourcePosition::new(83, 3, 36)],
)],
);
@ -360,7 +358,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("intArg", "Int"),
&error_message("intArg", error::type_value("FOO", "Int")),
&[SourcePosition::new(83, 3, 36)],
)],
);
@ -378,7 +376,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("intArg", "Int"),
&error_message("intArg", error::type_value("3", "Int")),
&[SourcePosition::new(83, 3, 36)],
)],
);
@ -396,7 +394,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("intArg", "Int"),
&error_message("intArg", error::type_value("3.333", "Int")),
&[SourcePosition::new(83, 3, 36)],
)],
);
@ -414,7 +412,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("floatArg", "Float"),
&error_message("floatArg", error::type_value("\"3.333\"", "Float")),
&[SourcePosition::new(87, 3, 40)],
)],
);
@ -432,7 +430,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("floatArg", "Float"),
&error_message("floatArg", error::type_value("true", "Float")),
&[SourcePosition::new(87, 3, 40)],
)],
);
@ -450,7 +448,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("floatArg", "Float"),
&error_message("floatArg", error::type_value("FOO", "Float")),
&[SourcePosition::new(87, 3, 40)],
)],
);
@ -468,7 +466,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("booleanArg", "Boolean"),
&error_message("booleanArg", error::type_value("2", "Boolean")),
&[SourcePosition::new(91, 3, 44)],
)],
);
@ -486,7 +484,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("booleanArg", "Boolean"),
&error_message("booleanArg", error::type_value("1", "Boolean")),
&[SourcePosition::new(91, 3, 44)],
)],
);
@ -504,7 +502,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("booleanArg", "Boolean"),
&error_message("booleanArg", error::type_value("\"true\"", "Boolean")),
&[SourcePosition::new(91, 3, 44)],
)],
);
@ -522,7 +520,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("booleanArg", "Boolean"),
&error_message("booleanArg", error::type_value("TRUE", "Boolean")),
&[SourcePosition::new(91, 3, 44)],
)],
);
@ -540,7 +538,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("idArg", "ID"),
&error_message("idArg", error::type_value("1", "ID")),
&[SourcePosition::new(81, 3, 34)],
)],
);
@ -558,7 +556,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("idArg", "ID"),
&error_message("idArg", error::type_value("true", "ID")),
&[SourcePosition::new(81, 3, 34)],
)],
);
@ -576,7 +574,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("idArg", "ID"),
&error_message("idArg", error::type_value("SOMETHING", "ID")),
&[SourcePosition::new(81, 3, 34)],
)],
);
@ -594,7 +592,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("dogCommand", "DogCommand"),
&error_message("dogCommand", error::enum_value("2", "DogCommand")),
&[SourcePosition::new(79, 3, 44)],
)],
);
@ -612,7 +610,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("dogCommand", "DogCommand"),
&error_message("dogCommand", error::enum_value("1", "DogCommand")),
&[SourcePosition::new(79, 3, 44)],
)],
);
@ -630,7 +628,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("dogCommand", "DogCommand"),
&error_message("dogCommand", error::enum_value("\"SIT\"", "DogCommand")),
&[SourcePosition::new(79, 3, 44)],
)],
);
@ -648,7 +646,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("dogCommand", "DogCommand"),
&error_message("dogCommand", error::enum_value("true", "DogCommand")),
&[SourcePosition::new(79, 3, 44)],
)],
);
@ -666,7 +664,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("dogCommand", "DogCommand"),
&error_message("dogCommand", error::type_value("JUGGLE", "DogCommand")),
&[SourcePosition::new(79, 3, 44)],
)],
);
@ -684,7 +682,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("dogCommand", "DogCommand"),
&error_message("dogCommand", error::type_value("sit", "DogCommand")),
&[SourcePosition::new(79, 3, 44)],
)],
);
@ -744,7 +742,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("stringListArg", "[String]"),
&error_message("stringListArg", error::type_value("2", "String")),
&[SourcePosition::new(97, 3, 50)],
)],
);
@ -762,7 +760,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("stringListArg", "[String]"),
&error_message("stringListArg", error::type_value("1", "String")),
&[SourcePosition::new(97, 3, 50)],
)],
);
@ -921,11 +919,11 @@ mod tests {
"#,
&[
RuleError::new(
&error_message("req2", "Int!"),
&error_message("req2", error::type_value("\"two\"", "Int")),
&[SourcePosition::new(82, 3, 35)],
),
RuleError::new(
&error_message("req1", "Int!"),
&error_message("req1", error::type_value("\"one\"", "Int")),
&[SourcePosition::new(95, 3, 48)],
),
],
@ -944,7 +942,7 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("req1", "Int!"),
&error_message("req1", error::type_value("\"one\"", "Int")),
&[SourcePosition::new(82, 3, 35)],
)],
);
@ -1058,7 +1056,10 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("complexArg", "ComplexInput"),
&error_message(
"complexArg",
error::missing_fields("ComplexInput", "\"requiredField\""),
),
&[SourcePosition::new(91, 3, 44)],
)],
);
@ -1079,7 +1080,14 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("complexArg", "ComplexInput"),
&error_message(
"complexArg",
error::field(
"ComplexInput",
"stringListField",
error::type_value("2", "String"),
),
),
&[SourcePosition::new(91, 3, 44)],
)],
);
@ -1100,7 +1108,10 @@ mod tests {
}
"#,
&[RuleError::new(
&error_message("complexArg", "ComplexInput"),
&error_message(
"complexArg",
error::unknown_field("ComplexInput", "unknownField"),
),
&[SourcePosition::new(91, 3, 44)],
)],
);
@ -1136,12 +1147,12 @@ mod tests {
"#,
&[
RuleError::new(
&error_message("if", "Boolean!"),
&[SourcePosition::new(38, 2, 27)],
&error_message("if", error::type_value("\"yes\"", "Boolean")),
&[SourcePosition::new(46, 2, 31)],
),
RuleError::new(
&error_message("if", "Boolean!"),
&[SourcePosition::new(74, 3, 27)],
&error_message("if", error::type_value("ENUM", "Boolean")),
&[SourcePosition::new(86, 3, 31)],
),
],
);

View file

@ -3,7 +3,7 @@ use std::fmt;
use crate::{
ast::VariableDefinition,
parser::Spanning,
types::utilities::is_valid_literal_value,
types::utilities::validate_literal_value,
validation::{ValidatorContext, Visitor},
value::ScalarValue,
};
@ -36,9 +36,9 @@ where
} else {
let meta_type = ctx.schema.make_type(&var_def.var_type.item);
if !is_valid_literal_value(ctx.schema, &meta_type, var_value) {
if let Some(err) = validate_literal_value(ctx.schema, &meta_type, var_value) {
ctx.report_error(
&type_error_message(var_name.item, &var_def.var_type.item),
&type_error_message(var_name.item, &var_def.var_type.item, err),
&[span.start],
);
}
@ -47,8 +47,15 @@ where
}
}
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 type_error_message(
arg_name: impl fmt::Display,
type_name: impl fmt::Display,
reason: impl fmt::Display,
) -> String {
format!(
"Invalid default value for argument \"{arg_name}\", expected type \"{type_name}\", \
reason: {reason}",
)
}
fn non_null_error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String {
@ -64,6 +71,7 @@ mod tests {
use crate::{
parser::SourcePosition,
types::utilities::error,
validation::{expect_fails_rule, expect_passes_rule, RuleError},
value::DefaultScalarValue,
};
@ -120,11 +128,11 @@ mod tests {
&[
RuleError::new(
&non_null_error_message("a", "Int!"),
&[SourcePosition::new(53, 1, 52)],
&[SourcePosition::new(55, 1, 54)],
),
RuleError::new(
&non_null_error_message("b", "String!"),
&[SourcePosition::new(70, 1, 69)],
&[SourcePosition::new(72, 1, 71)],
),
],
);
@ -145,16 +153,20 @@ mod tests {
"#,
&[
RuleError::new(
&type_error_message("a", "Int"),
&[SourcePosition::new(61, 2, 22)],
&type_error_message("a", "Int", error::type_value("\"one\"", "Int")),
&[SourcePosition::new(67, 2, 26)],
),
RuleError::new(
&type_error_message("b", "String"),
&[SourcePosition::new(93, 3, 25)],
&type_error_message("b", "String", error::type_value("4", "String")),
&[SourcePosition::new(103, 3, 29)],
),
RuleError::new(
&type_error_message("c", "ComplexInput"),
&[SourcePosition::new(127, 4, 31)],
&type_error_message(
"c",
"ComplexInput",
error::type_value("\"notverycomplex\"", "ComplexInput"),
),
&[SourcePosition::new(141, 4, 35)],
),
],
);
@ -170,8 +182,12 @@ mod tests {
}
"#,
&[RuleError::new(
&type_error_message("a", "ComplexInput"),
&[SourcePosition::new(57, 1, 56)],
&type_error_message(
"a",
"ComplexInput",
error::missing_fields("ComplexInput", "\"requiredField\""),
),
&[SourcePosition::new(59, 1, 58)],
)],
);
}
@ -186,8 +202,8 @@ mod tests {
}
"#,
&[RuleError::new(
&type_error_message("a", "[String]"),
&[SourcePosition::new(44, 1, 43)],
&type_error_message("a", "[String]", error::type_value("2", "String")),
&[SourcePosition::new(46, 1, 45)],
)],
);
}

View file

@ -87,11 +87,12 @@ mod as_input_field {
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let res = juniper::execute(query, None, &schema, &graphql_vars! {}, &()).await;
assert!(res.is_err());
assert!(res
.unwrap_err()
.to_string()
.contains(r#"Invalid value for argument "input", expected type "Input!""#));
assert!(res.is_err(), "result succeeded: {res:#?}");
assert_eq!(
res.unwrap_err().to_string(),
"Invalid value for argument \"input\", reason: Error on \"Input\" field \"two\": \
Expected list of length 2, but \"[true, true, false]\" has length 3. At 2:30\n",
);
}
#[tokio::test]
@ -105,11 +106,12 @@ mod as_input_field {
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let res = juniper::execute(query, None, &schema, &graphql_vars! {}, &()).await;
assert!(res.is_err());
assert!(res
.unwrap_err()
.to_string()
.contains(r#"Invalid value for argument "input", expected type "Input!""#));
assert!(res.is_err(), "result succeeded: {res:#?}");
assert_eq!(
res.unwrap_err().to_string(),
"Invalid value for argument \"input\", reason: Error on \"Input\" field \"two\": \
Expected list of length 2, but \"true\" has length 1. At 2:30\n",
);
}
#[tokio::test]
@ -178,11 +180,12 @@ mod as_input_argument {
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let res = juniper::execute(query, None, &schema, &graphql_vars! {}, &()).await;
assert!(res.is_err());
assert!(res
.unwrap_err()
.to_string()
.contains(r#"Invalid value for argument "input", expected type "[Boolean!]!""#));
assert!(res.is_err(), "result succeeded: {res:#?}");
assert_eq!(
res.unwrap_err().to_string(),
"Invalid value for argument \"input\", reason: Expected list of length 2, \
but \"[true, true, false]\" has length 3. At 2:30\n",
);
}
#[tokio::test]
@ -196,11 +199,13 @@ mod as_input_argument {
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let res = juniper::execute(query, None, &schema, &graphql_vars! {}, &()).await;
assert!(res.is_err());
assert!(res
.unwrap_err()
.to_string()
.contains(r#"Invalid value for argument "input", expected type "[Boolean!]!""#));
assert!(res.is_err(), "result succeeded: {res:#?}");
assert_eq!(
res.unwrap_err().to_string(),
"Invalid value for argument \"input\", reason: Expected list of length 2, \
but \"true\" has length 1. At 2:30\n",
"invalid error returned",
);
}
#[tokio::test]

View file

@ -189,7 +189,8 @@ mod default_value {
assert_eq!(
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
Err(GraphQLError::ValidationError(vec![RuleError::new(
"Invalid value for argument \"point\", expected type \"Point2D!\"",
"Invalid value for argument \"point\", reason: Error on \"Point2D\" field \"y\": \
\"null\" specified for not nullable type \"Float!\"",
&[SourcePosition::new(11, 0, 11)],
)]))
);