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])
|
- Made `GraphQLRequest` fields public. ([#750])
|
||||||
- Relaxed [object safety] requirement for `GraphQLValue` and `GraphQLValueAsync` traits. ([ba1ed85b])
|
- Relaxed [object safety] requirement for `GraphQLValue` and `GraphQLValueAsync` traits. ([ba1ed85b])
|
||||||
- Updated [GraphQL Playground] to 1.7.28 version. ([#1190])
|
- Updated [GraphQL Playground] to 1.7.28 version. ([#1190])
|
||||||
|
- Improve validation errors for input values. ([#811], [#693])
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
|
@ -94,8 +95,10 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
|
||||||
[#456]: /../../issues/456
|
[#456]: /../../issues/456
|
||||||
[#503]: /../../issues/503
|
[#503]: /../../issues/503
|
||||||
[#528]: /../../issues/528
|
[#528]: /../../issues/528
|
||||||
|
[#693]: /../../issues/693
|
||||||
[#750]: /../../issues/750
|
[#750]: /../../issues/750
|
||||||
[#798]: /../../issues/798
|
[#798]: /../../issues/798
|
||||||
|
[#811]: /../../pull/811
|
||||||
[#918]: /../../issues/918
|
[#918]: /../../issues/918
|
||||||
[#965]: /../../pull/965
|
[#965]: /../../pull/965
|
||||||
[#966]: /../../pull/966
|
[#966]: /../../pull/966
|
||||||
|
|
|
@ -100,7 +100,7 @@ async fn does_not_accept_string_literals() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error,
|
error,
|
||||||
ValidationError(vec![RuleError::new(
|
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)],
|
&[SourcePosition::new(18, 0, 18)],
|
||||||
)])
|
)])
|
||||||
);
|
);
|
||||||
|
|
|
@ -916,7 +916,8 @@ async fn does_not_allow_missing_required_field() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error,
|
error,
|
||||||
ValidationError(vec![RuleError::new(
|
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)],
|
&[SourcePosition::new(20, 0, 20)],
|
||||||
)]),
|
)]),
|
||||||
);
|
);
|
||||||
|
@ -940,7 +941,9 @@ async fn does_not_allow_null_in_required_field() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error,
|
error,
|
||||||
ValidationError(vec![RuleError::new(
|
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)],
|
&[SourcePosition::new(20, 0, 20)],
|
||||||
)]),
|
)]),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,48 +1,126 @@
|
||||||
|
use std::{collections::HashSet, iter::Iterator};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::InputValue,
|
ast::InputValue,
|
||||||
schema::{
|
schema::{
|
||||||
meta::{EnumMeta, InputObjectMeta, MetaType},
|
meta::{Argument, EnumMeta, InputObjectMeta, MetaType},
|
||||||
model::{SchemaType, TypeType},
|
model::{SchemaType, TypeType},
|
||||||
},
|
},
|
||||||
value::ScalarValue,
|
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>,
|
schema: &SchemaType<S>,
|
||||||
arg_type: &TypeType<S>,
|
arg_type: &TypeType<S>,
|
||||||
arg_value: &InputValue<S>,
|
arg_value: &InputValue<S>,
|
||||||
) -> bool
|
) -> Option<String>
|
||||||
where
|
where
|
||||||
S: ScalarValue,
|
S: ScalarValue,
|
||||||
{
|
{
|
||||||
match *arg_type {
|
match *arg_type {
|
||||||
TypeType::NonNull(ref inner) => {
|
TypeType::NonNull(ref inner) => {
|
||||||
if arg_value.is_null() {
|
if arg_value.is_null() {
|
||||||
false
|
Some(error::non_null(arg_type))
|
||||||
} else {
|
} 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 {
|
TypeType::List(ref inner, expected_size) => match *arg_value {
|
||||||
InputValue::Null | InputValue::Variable(_) => true,
|
InputValue::Null | InputValue::Variable(_) => None,
|
||||||
InputValue::List(ref items) => {
|
InputValue::List(ref items) => {
|
||||||
if let Some(expected) = expected_size {
|
if let Some(expected) = expected_size {
|
||||||
if items.len() != expected {
|
if items.len() != expected {
|
||||||
return false;
|
return Some(error::invalid_list_length(arg_value, items.len(), expected));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items
|
items
|
||||||
.iter()
|
.iter()
|
||||||
.all(|i| is_valid_literal_value(schema, inner, &i.item))
|
.find_map(|i| validate_literal_value(schema, inner, &i.item))
|
||||||
}
|
}
|
||||||
ref v => {
|
ref v => {
|
||||||
if let Some(expected) = expected_size {
|
if let Some(expected) = expected_size {
|
||||||
if expected != 1 {
|
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) => {
|
TypeType::Concrete(t) => {
|
||||||
|
@ -51,19 +129,23 @@ where
|
||||||
if let (&InputValue::Scalar(_), Some(&MetaType::Enum(EnumMeta { .. }))) =
|
if let (&InputValue::Scalar(_), Some(&MetaType::Enum(EnumMeta { .. }))) =
|
||||||
(arg_value, arg_type.to_concrete())
|
(arg_value, arg_type.to_concrete())
|
||||||
{
|
{
|
||||||
return false;
|
return Some(error::enum_value(arg_value, arg_type));
|
||||||
}
|
}
|
||||||
|
|
||||||
match *arg_value {
|
match *arg_value {
|
||||||
InputValue::Null | InputValue::Variable(_) => true,
|
InputValue::Null | InputValue::Variable(_) => None,
|
||||||
ref v @ InputValue::Scalar(_) | ref v @ InputValue::Enum(_) => {
|
ref v @ InputValue::Scalar(_) | ref v @ InputValue::Enum(_) => {
|
||||||
if let Some(parse_fn) = t.input_value_parse_fn() {
|
if let Some(parse_fn) = t.input_value_parse_fn() {
|
||||||
parse_fn(v).is_ok()
|
if parse_fn(v).is_ok() {
|
||||||
|
None
|
||||||
} else {
|
} 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) => {
|
InputValue::Object(ref obj) => {
|
||||||
if let MetaType::InputObject(InputObjectMeta {
|
if let MetaType::InputObject(InputObjectMeta {
|
||||||
ref input_fields, ..
|
ref input_fields, ..
|
||||||
|
@ -77,23 +159,33 @@ where
|
||||||
})
|
})
|
||||||
.collect::<HashSet<_>>();
|
.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);
|
remaining_required_fields.remove(&key.item);
|
||||||
if let Some(ref arg_type) = input_fields
|
validate_object_field(
|
||||||
.iter()
|
schema,
|
||||||
.filter(|f| f.name == key.item)
|
arg_type,
|
||||||
.map(|f| schema.make_type(&f.arg_type))
|
input_fields,
|
||||||
.next()
|
&value.item,
|
||||||
{
|
&key.item,
|
||||||
is_valid_literal_value(schema, arg_type, &value.item)
|
)
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
all_types_ok && remaining_required_fields.is_empty()
|
if error_message.is_some() {
|
||||||
|
return error_message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if remaining_required_fields.is_empty() {
|
||||||
|
None
|
||||||
} else {
|
} 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},
|
ast::{Directive, Field, InputValue},
|
||||||
parser::Spanning,
|
parser::Spanning,
|
||||||
schema::meta::Argument,
|
schema::meta::Argument,
|
||||||
types::utilities::is_valid_literal_value,
|
types::utilities::validate_literal_value,
|
||||||
validation::{ValidatorContext, Visitor},
|
validation::{ValidatorContext, Visitor},
|
||||||
value::ScalarValue,
|
value::ScalarValue,
|
||||||
};
|
};
|
||||||
|
@ -58,18 +58,15 @@ where
|
||||||
{
|
{
|
||||||
let meta_type = ctx.schema.make_type(&argument_meta.arg_type);
|
let meta_type = ctx.schema.make_type(&argument_meta.arg_type);
|
||||||
|
|
||||||
if !is_valid_literal_value(ctx.schema, &meta_type, &arg_value.item) {
|
if let Some(err) = validate_literal_value(ctx.schema, &meta_type, &arg_value.item) {
|
||||||
ctx.report_error(
|
ctx.report_error(&error_message(arg_name.item, err), &[arg_value.span.start]);
|
||||||
&error_message(arg_name.item, &argument_meta.arg_type),
|
|
||||||
&[arg_value.span.start],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String {
|
fn error_message(arg_name: impl fmt::Display, msg: impl fmt::Display) -> String {
|
||||||
format!("Invalid value for argument \"{arg_name}\", expected type \"{type_name}\"",)
|
format!("Invalid value for argument \"{arg_name}\", reason: {msg}")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -78,6 +75,7 @@ mod tests {
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
parser::SourcePosition,
|
parser::SourcePosition,
|
||||||
|
types::utilities::error,
|
||||||
validation::{expect_fails_rule, expect_passes_rule, RuleError},
|
validation::{expect_fails_rule, expect_passes_rule, RuleError},
|
||||||
value::DefaultScalarValue,
|
value::DefaultScalarValue,
|
||||||
};
|
};
|
||||||
|
@ -122,7 +120,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("nonNullIntArg", "Int!"),
|
&error_message("nonNullIntArg", error::non_null("Int!")),
|
||||||
&[SourcePosition::new(97, 3, 50)],
|
&[SourcePosition::new(97, 3, 50)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -140,7 +138,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("nonNullStringListArg", "[String!]!"),
|
&error_message("nonNullStringListArg", error::non_null("[String!]!")),
|
||||||
&[SourcePosition::new(111, 3, 64)],
|
&[SourcePosition::new(111, 3, 64)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -270,7 +268,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("stringArg", "String"),
|
&error_message("stringArg", error::type_value("1", "String")),
|
||||||
&[SourcePosition::new(89, 3, 42)],
|
&[SourcePosition::new(89, 3, 42)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -288,7 +286,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("stringArg", "String"),
|
&error_message("stringArg", error::type_value("1", "String")),
|
||||||
&[SourcePosition::new(89, 3, 42)],
|
&[SourcePosition::new(89, 3, 42)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -306,7 +304,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("stringArg", "String"),
|
&error_message("stringArg", error::type_value("true", "String")),
|
||||||
&[SourcePosition::new(89, 3, 42)],
|
&[SourcePosition::new(89, 3, 42)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -324,7 +322,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("stringArg", "String"),
|
&error_message("stringArg", error::type_value("BAR", "String")),
|
||||||
&[SourcePosition::new(89, 3, 42)],
|
&[SourcePosition::new(89, 3, 42)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -342,7 +340,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("intArg", "Int"),
|
&error_message("intArg", error::type_value("\"3\"", "Int")),
|
||||||
&[SourcePosition::new(83, 3, 36)],
|
&[SourcePosition::new(83, 3, 36)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -360,7 +358,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("intArg", "Int"),
|
&error_message("intArg", error::type_value("FOO", "Int")),
|
||||||
&[SourcePosition::new(83, 3, 36)],
|
&[SourcePosition::new(83, 3, 36)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -378,7 +376,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("intArg", "Int"),
|
&error_message("intArg", error::type_value("3", "Int")),
|
||||||
&[SourcePosition::new(83, 3, 36)],
|
&[SourcePosition::new(83, 3, 36)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -396,7 +394,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("intArg", "Int"),
|
&error_message("intArg", error::type_value("3.333", "Int")),
|
||||||
&[SourcePosition::new(83, 3, 36)],
|
&[SourcePosition::new(83, 3, 36)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -414,7 +412,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("floatArg", "Float"),
|
&error_message("floatArg", error::type_value("\"3.333\"", "Float")),
|
||||||
&[SourcePosition::new(87, 3, 40)],
|
&[SourcePosition::new(87, 3, 40)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -432,7 +430,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("floatArg", "Float"),
|
&error_message("floatArg", error::type_value("true", "Float")),
|
||||||
&[SourcePosition::new(87, 3, 40)],
|
&[SourcePosition::new(87, 3, 40)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -450,7 +448,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("floatArg", "Float"),
|
&error_message("floatArg", error::type_value("FOO", "Float")),
|
||||||
&[SourcePosition::new(87, 3, 40)],
|
&[SourcePosition::new(87, 3, 40)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -468,7 +466,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("booleanArg", "Boolean"),
|
&error_message("booleanArg", error::type_value("2", "Boolean")),
|
||||||
&[SourcePosition::new(91, 3, 44)],
|
&[SourcePosition::new(91, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -486,7 +484,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("booleanArg", "Boolean"),
|
&error_message("booleanArg", error::type_value("1", "Boolean")),
|
||||||
&[SourcePosition::new(91, 3, 44)],
|
&[SourcePosition::new(91, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -504,7 +502,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("booleanArg", "Boolean"),
|
&error_message("booleanArg", error::type_value("\"true\"", "Boolean")),
|
||||||
&[SourcePosition::new(91, 3, 44)],
|
&[SourcePosition::new(91, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -522,7 +520,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("booleanArg", "Boolean"),
|
&error_message("booleanArg", error::type_value("TRUE", "Boolean")),
|
||||||
&[SourcePosition::new(91, 3, 44)],
|
&[SourcePosition::new(91, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -540,7 +538,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("idArg", "ID"),
|
&error_message("idArg", error::type_value("1", "ID")),
|
||||||
&[SourcePosition::new(81, 3, 34)],
|
&[SourcePosition::new(81, 3, 34)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -558,7 +556,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("idArg", "ID"),
|
&error_message("idArg", error::type_value("true", "ID")),
|
||||||
&[SourcePosition::new(81, 3, 34)],
|
&[SourcePosition::new(81, 3, 34)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -576,7 +574,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("idArg", "ID"),
|
&error_message("idArg", error::type_value("SOMETHING", "ID")),
|
||||||
&[SourcePosition::new(81, 3, 34)],
|
&[SourcePosition::new(81, 3, 34)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -594,7 +592,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("dogCommand", "DogCommand"),
|
&error_message("dogCommand", error::enum_value("2", "DogCommand")),
|
||||||
&[SourcePosition::new(79, 3, 44)],
|
&[SourcePosition::new(79, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -612,7 +610,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("dogCommand", "DogCommand"),
|
&error_message("dogCommand", error::enum_value("1", "DogCommand")),
|
||||||
&[SourcePosition::new(79, 3, 44)],
|
&[SourcePosition::new(79, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -630,7 +628,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("dogCommand", "DogCommand"),
|
&error_message("dogCommand", error::enum_value("\"SIT\"", "DogCommand")),
|
||||||
&[SourcePosition::new(79, 3, 44)],
|
&[SourcePosition::new(79, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -648,7 +646,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("dogCommand", "DogCommand"),
|
&error_message("dogCommand", error::enum_value("true", "DogCommand")),
|
||||||
&[SourcePosition::new(79, 3, 44)],
|
&[SourcePosition::new(79, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -666,7 +664,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("dogCommand", "DogCommand"),
|
&error_message("dogCommand", error::type_value("JUGGLE", "DogCommand")),
|
||||||
&[SourcePosition::new(79, 3, 44)],
|
&[SourcePosition::new(79, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -684,7 +682,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("dogCommand", "DogCommand"),
|
&error_message("dogCommand", error::type_value("sit", "DogCommand")),
|
||||||
&[SourcePosition::new(79, 3, 44)],
|
&[SourcePosition::new(79, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -744,7 +742,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("stringListArg", "[String]"),
|
&error_message("stringListArg", error::type_value("2", "String")),
|
||||||
&[SourcePosition::new(97, 3, 50)],
|
&[SourcePosition::new(97, 3, 50)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -762,7 +760,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("stringListArg", "[String]"),
|
&error_message("stringListArg", error::type_value("1", "String")),
|
||||||
&[SourcePosition::new(97, 3, 50)],
|
&[SourcePosition::new(97, 3, 50)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -921,11 +919,11 @@ mod tests {
|
||||||
"#,
|
"#,
|
||||||
&[
|
&[
|
||||||
RuleError::new(
|
RuleError::new(
|
||||||
&error_message("req2", "Int!"),
|
&error_message("req2", error::type_value("\"two\"", "Int")),
|
||||||
&[SourcePosition::new(82, 3, 35)],
|
&[SourcePosition::new(82, 3, 35)],
|
||||||
),
|
),
|
||||||
RuleError::new(
|
RuleError::new(
|
||||||
&error_message("req1", "Int!"),
|
&error_message("req1", error::type_value("\"one\"", "Int")),
|
||||||
&[SourcePosition::new(95, 3, 48)],
|
&[SourcePosition::new(95, 3, 48)],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -944,7 +942,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("req1", "Int!"),
|
&error_message("req1", error::type_value("\"one\"", "Int")),
|
||||||
&[SourcePosition::new(82, 3, 35)],
|
&[SourcePosition::new(82, 3, 35)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -1058,7 +1056,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("complexArg", "ComplexInput"),
|
&error_message(
|
||||||
|
"complexArg",
|
||||||
|
error::missing_fields("ComplexInput", "\"requiredField\""),
|
||||||
|
),
|
||||||
&[SourcePosition::new(91, 3, 44)],
|
&[SourcePosition::new(91, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -1079,7 +1080,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("complexArg", "ComplexInput"),
|
&error_message(
|
||||||
|
"complexArg",
|
||||||
|
error::field(
|
||||||
|
"ComplexInput",
|
||||||
|
"stringListField",
|
||||||
|
error::type_value("2", "String"),
|
||||||
|
),
|
||||||
|
),
|
||||||
&[SourcePosition::new(91, 3, 44)],
|
&[SourcePosition::new(91, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -1100,7 +1108,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&error_message("complexArg", "ComplexInput"),
|
&error_message(
|
||||||
|
"complexArg",
|
||||||
|
error::unknown_field("ComplexInput", "unknownField"),
|
||||||
|
),
|
||||||
&[SourcePosition::new(91, 3, 44)],
|
&[SourcePosition::new(91, 3, 44)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
@ -1136,12 +1147,12 @@ mod tests {
|
||||||
"#,
|
"#,
|
||||||
&[
|
&[
|
||||||
RuleError::new(
|
RuleError::new(
|
||||||
&error_message("if", "Boolean!"),
|
&error_message("if", error::type_value("\"yes\"", "Boolean")),
|
||||||
&[SourcePosition::new(38, 2, 27)],
|
&[SourcePosition::new(46, 2, 31)],
|
||||||
),
|
),
|
||||||
RuleError::new(
|
RuleError::new(
|
||||||
&error_message("if", "Boolean!"),
|
&error_message("if", error::type_value("ENUM", "Boolean")),
|
||||||
&[SourcePosition::new(74, 3, 27)],
|
&[SourcePosition::new(86, 3, 31)],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::fmt;
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::VariableDefinition,
|
ast::VariableDefinition,
|
||||||
parser::Spanning,
|
parser::Spanning,
|
||||||
types::utilities::is_valid_literal_value,
|
types::utilities::validate_literal_value,
|
||||||
validation::{ValidatorContext, Visitor},
|
validation::{ValidatorContext, Visitor},
|
||||||
value::ScalarValue,
|
value::ScalarValue,
|
||||||
};
|
};
|
||||||
|
@ -36,9 +36,9 @@ where
|
||||||
} else {
|
} else {
|
||||||
let meta_type = ctx.schema.make_type(&var_def.var_type.item);
|
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(
|
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],
|
&[span.start],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,15 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String {
|
fn type_error_message(
|
||||||
format!("Invalid default value for argument \"{arg_name}\", expected type \"{type_name}\"")
|
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 {
|
fn non_null_error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String {
|
||||||
|
@ -64,6 +71,7 @@ mod tests {
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
parser::SourcePosition,
|
parser::SourcePosition,
|
||||||
|
types::utilities::error,
|
||||||
validation::{expect_fails_rule, expect_passes_rule, RuleError},
|
validation::{expect_fails_rule, expect_passes_rule, RuleError},
|
||||||
value::DefaultScalarValue,
|
value::DefaultScalarValue,
|
||||||
};
|
};
|
||||||
|
@ -120,11 +128,11 @@ mod tests {
|
||||||
&[
|
&[
|
||||||
RuleError::new(
|
RuleError::new(
|
||||||
&non_null_error_message("a", "Int!"),
|
&non_null_error_message("a", "Int!"),
|
||||||
&[SourcePosition::new(53, 1, 52)],
|
&[SourcePosition::new(55, 1, 54)],
|
||||||
),
|
),
|
||||||
RuleError::new(
|
RuleError::new(
|
||||||
&non_null_error_message("b", "String!"),
|
&non_null_error_message("b", "String!"),
|
||||||
&[SourcePosition::new(70, 1, 69)],
|
&[SourcePosition::new(72, 1, 71)],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -145,16 +153,20 @@ mod tests {
|
||||||
"#,
|
"#,
|
||||||
&[
|
&[
|
||||||
RuleError::new(
|
RuleError::new(
|
||||||
&type_error_message("a", "Int"),
|
&type_error_message("a", "Int", error::type_value("\"one\"", "Int")),
|
||||||
&[SourcePosition::new(61, 2, 22)],
|
&[SourcePosition::new(67, 2, 26)],
|
||||||
),
|
),
|
||||||
RuleError::new(
|
RuleError::new(
|
||||||
&type_error_message("b", "String"),
|
&type_error_message("b", "String", error::type_value("4", "String")),
|
||||||
&[SourcePosition::new(93, 3, 25)],
|
&[SourcePosition::new(103, 3, 29)],
|
||||||
),
|
),
|
||||||
RuleError::new(
|
RuleError::new(
|
||||||
&type_error_message("c", "ComplexInput"),
|
&type_error_message(
|
||||||
&[SourcePosition::new(127, 4, 31)],
|
"c",
|
||||||
|
"ComplexInput",
|
||||||
|
error::type_value("\"notverycomplex\"", "ComplexInput"),
|
||||||
|
),
|
||||||
|
&[SourcePosition::new(141, 4, 35)],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -170,8 +182,12 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&type_error_message("a", "ComplexInput"),
|
&type_error_message(
|
||||||
&[SourcePosition::new(57, 1, 56)],
|
"a",
|
||||||
|
"ComplexInput",
|
||||||
|
error::missing_fields("ComplexInput", "\"requiredField\""),
|
||||||
|
),
|
||||||
|
&[SourcePosition::new(59, 1, 58)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -186,8 +202,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
&[RuleError::new(
|
&[RuleError::new(
|
||||||
&type_error_message("a", "[String]"),
|
&type_error_message("a", "[String]", error::type_value("2", "String")),
|
||||||
&[SourcePosition::new(44, 1, 43)],
|
&[SourcePosition::new(46, 1, 45)],
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,11 +87,12 @@ mod as_input_field {
|
||||||
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
|
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
|
||||||
let res = juniper::execute(query, None, &schema, &graphql_vars! {}, &()).await;
|
let res = juniper::execute(query, None, &schema, &graphql_vars! {}, &()).await;
|
||||||
|
|
||||||
assert!(res.is_err());
|
assert!(res.is_err(), "result succeeded: {res:#?}");
|
||||||
assert!(res
|
assert_eq!(
|
||||||
.unwrap_err()
|
res.unwrap_err().to_string(),
|
||||||
.to_string()
|
"Invalid value for argument \"input\", reason: Error on \"Input\" field \"two\": \
|
||||||
.contains(r#"Invalid value for argument "input", expected type "Input!""#));
|
Expected list of length 2, but \"[true, true, false]\" has length 3. At 2:30\n",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -105,11 +106,12 @@ mod as_input_field {
|
||||||
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
|
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
|
||||||
let res = juniper::execute(query, None, &schema, &graphql_vars! {}, &()).await;
|
let res = juniper::execute(query, None, &schema, &graphql_vars! {}, &()).await;
|
||||||
|
|
||||||
assert!(res.is_err());
|
assert!(res.is_err(), "result succeeded: {res:#?}");
|
||||||
assert!(res
|
assert_eq!(
|
||||||
.unwrap_err()
|
res.unwrap_err().to_string(),
|
||||||
.to_string()
|
"Invalid value for argument \"input\", reason: Error on \"Input\" field \"two\": \
|
||||||
.contains(r#"Invalid value for argument "input", expected type "Input!""#));
|
Expected list of length 2, but \"true\" has length 1. At 2:30\n",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -178,11 +180,12 @@ mod as_input_argument {
|
||||||
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
|
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
|
||||||
let res = juniper::execute(query, None, &schema, &graphql_vars! {}, &()).await;
|
let res = juniper::execute(query, None, &schema, &graphql_vars! {}, &()).await;
|
||||||
|
|
||||||
assert!(res.is_err());
|
assert!(res.is_err(), "result succeeded: {res:#?}");
|
||||||
assert!(res
|
assert_eq!(
|
||||||
.unwrap_err()
|
res.unwrap_err().to_string(),
|
||||||
.to_string()
|
"Invalid value for argument \"input\", reason: Expected list of length 2, \
|
||||||
.contains(r#"Invalid value for argument "input", expected type "[Boolean!]!""#));
|
but \"[true, true, false]\" has length 3. At 2:30\n",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -196,11 +199,13 @@ mod as_input_argument {
|
||||||
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
|
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
|
||||||
let res = juniper::execute(query, None, &schema, &graphql_vars! {}, &()).await;
|
let res = juniper::execute(query, None, &schema, &graphql_vars! {}, &()).await;
|
||||||
|
|
||||||
assert!(res.is_err());
|
assert!(res.is_err(), "result succeeded: {res:#?}");
|
||||||
assert!(res
|
assert_eq!(
|
||||||
.unwrap_err()
|
res.unwrap_err().to_string(),
|
||||||
.to_string()
|
"Invalid value for argument \"input\", reason: Expected list of length 2, \
|
||||||
.contains(r#"Invalid value for argument "input", expected type "[Boolean!]!""#));
|
but \"true\" has length 1. At 2:30\n",
|
||||||
|
"invalid error returned",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
|
@ -189,7 +189,8 @@ mod default_value {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
execute(DOC, None, &schema, &graphql_vars! {}, &()).await,
|
||||||
Err(GraphQLError::ValidationError(vec![RuleError::new(
|
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)],
|
&[SourcePosition::new(11, 0, 11)],
|
||||||
)]))
|
)]))
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue