Co-authored-by: Kai Ren <tyranron@gmail.com>
This commit is contained in:
parent
7c03f922e0
commit
ace693585d
8 changed files with 352 additions and 221 deletions
|
@ -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
|
||||
|
|
|
@ -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)],
|
||||
)])
|
||||
);
|
||||
|
|
|
@ -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)],
|
||||
)]),
|
||||
);
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -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)],
|
||||
)],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)],
|
||||
)]))
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue