Add input variable tests, fix a bunch of related bugs

This commit is contained in:
Magnus Hallin 2016-11-09 21:48:21 +01:00
parent ba7839f321
commit b3a750ffa5
12 changed files with 1046 additions and 20 deletions

View file

@ -289,7 +289,8 @@ impl InputValue {
/// Resolve all variables to their values.
pub fn into_const(self, vars: &HashMap<String, InputValue>) -> InputValue {
match self {
InputValue::Variable(v) => vars[&v].clone(),
InputValue::Variable(v) => vars.get(&v)
.map_or_else(InputValue::null, Clone::clone),
InputValue::List(l) => InputValue::List(
l.into_iter().map(|s| s.map(|v| v.into_const(vars))).collect()
),

View file

@ -1 +1,2 @@
mod introspection;
mod variables;

View file

@ -0,0 +1,737 @@
use std::collections::HashMap;
use value::Value;
use ast::InputValue;
use schema::model::RootNode;
use ::GraphQLError::ValidationError;
use validation::RuleError;
use parser::SourcePosition;
#[derive(Debug)]
struct TestComplexScalar;
struct TestType;
graphql_scalar!(TestComplexScalar {
resolve(&self) -> Value {
Value::string("SerializedValue")
}
from_input_value(v: &InputValue) -> Option<TestComplexScalar> {
if let Some(s) = v.as_string_value() {
if s == "SerializedValue" {
return Some(TestComplexScalar);
}
}
None
}
});
graphql_input_object!(
#[derive(Debug)]
struct TestInputObject {
a: Option<String>,
b: Option<Vec<Option<String>>>,
c: String,
d: Option<TestComplexScalar>,
}
);
graphql_input_object!(
#[derive(Debug)]
struct TestNestedInputObject {
na: TestInputObject,
nb: String,
}
);
graphql_object!(TestType: () |&self| {
field field_with_object_input(input: Option<TestInputObject>) -> String {
format!("{:?}", input)
}
field field_with_nullable_string_input(input: Option<String>) -> String {
format!("{:?}", input)
}
field field_with_non_nullable_string_input(input: String) -> String {
format!("{:?}", input)
}
field field_with_default_argument_value(input = ("Hello World".to_owned()): String) -> String {
format!("{:?}", input)
}
field field_with_nested_object_input(input: Option<TestNestedInputObject>) -> String {
format!("{:?}", input)
}
field list(input: Option<Vec<Option<String>>>) -> String {
format!("{:?}", input)
}
field nn_list(input: Vec<Option<String>>) -> String {
format!("{:?}", input)
}
field list_nn(input: Option<Vec<String>>) -> String {
format!("{:?}", input)
}
field nn_list_nn(input: Vec<String>) -> String {
format!("{:?}", input)
}
});
fn run_variable_query<F>(query: &str, vars: HashMap<String, InputValue>, f: F)
where F: Fn(&HashMap<String, Value>) -> ()
{
let schema = RootNode::new(TestType, ());
let (result, errs) = ::execute(query, None, &schema, &vars, &())
.expect("Execution failed");
assert_eq!(errs, []);
println!("Result: {:?}", result);
let obj = result.as_object_value().expect("Result is not an object");
f(obj);
}
fn run_query<F>(query: &str, f: F)
where F: Fn(&HashMap<String, Value>) -> ()
{
run_variable_query(query, HashMap::new(), f);
}
#[test]
fn inline_complex_input() {
run_query(
r#"{ fieldWithObjectInput(input: {a: "foo", b: ["bar"], c: "baz"}) }"#,
|result| {
assert_eq!(
result.get("fieldWithObjectInput"),
Some(&Value::string(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#)));
});
}
#[test]
fn inline_parse_single_value_to_list() {
run_query(
r#"{ fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"}) }"#,
|result| {
assert_eq!(
result.get("fieldWithObjectInput"),
Some(&Value::string(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#)));
});
}
#[test]
fn inline_runs_from_input_value_on_scalar() {
run_query(
r#"{ fieldWithObjectInput(input: {c: "baz", d: "SerializedValue"}) }"#,
|result| {
assert_eq!(
result.get("fieldWithObjectInput"),
Some(&Value::string(r#"Some(TestInputObject { a: None, b: None, c: "baz", d: Some(TestComplexScalar) })"#)));
});
}
#[test]
fn variable_complex_input() {
run_variable_query(
r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#,
vec![
("input".to_owned(), InputValue::object(vec![
("a", InputValue::string("foo")),
("b", InputValue::list(vec![InputValue::string("bar")])),
("c", InputValue::string("baz")),
].into_iter().collect())),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("fieldWithObjectInput"),
Some(&Value::string(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#)));
});
}
#[test]
fn variable_parse_single_value_to_list() {
run_variable_query(
r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#,
vec![
("input".to_owned(), InputValue::object(vec![
("a", InputValue::string("foo")),
("b", InputValue::string("bar")),
("c", InputValue::string("baz")),
].into_iter().collect())),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("fieldWithObjectInput"),
Some(&Value::string(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#)));
});
}
#[test]
fn variable_runs_from_input_value_on_scalar() {
run_variable_query(
r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#,
vec![
("input".to_owned(), InputValue::object(vec![
("c", InputValue::string("baz")),
("d", InputValue::string("SerializedValue")),
].into_iter().collect())),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("fieldWithObjectInput"),
Some(&Value::string(r#"Some(TestInputObject { a: None, b: None, c: "baz", d: Some(TestComplexScalar) })"#)));
});
}
#[test]
fn variable_error_on_nested_non_null() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#;
let vars = vec![
("input".to_owned(), InputValue::object(vec![
("a", InputValue::string("foo")),
("b", InputValue::string("bar")),
("c", InputValue::null()),
].into_iter().collect()))
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$input" got invalid value. In field "c": Expected "String!", found null."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn variable_error_on_incorrect_type() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#;
let vars = vec![
("input".to_owned(), InputValue::string("foo bar")),
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$input" got invalid value. Expected "TestInputObject", found not an object."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn variable_error_on_omit_non_null() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#;
let vars = vec![
("input".to_owned(), InputValue::object(vec![
("a", InputValue::string("foo")),
("b", InputValue::string("bar")),
].into_iter().collect()))
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$input" got invalid value. In field "c": Expected "String!", found null."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn variable_multiple_errors_with_nesting() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($input: TestNestedInputObject) { fieldWithNestedObjectInput(input: $input) }"#;
let vars = vec![
("input".to_owned(), InputValue::object(vec![
("na", InputValue::object(vec![
("a", InputValue::string("foo")),
].into_iter().collect())),
].into_iter().collect()))
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$input" got invalid value. In field "na": In field "c": Expected "String!", found null."#,
&[SourcePosition::new(8, 0, 8)],
),
RuleError::new(
r#"Variable "$input" got invalid value. In field "nb": Expected "String!", found null."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn variable_error_on_additional_field() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($input: TestInputObject) { fieldWithObjectInput(input: $input) }"#;
let vars = vec![
("input".to_owned(), InputValue::object(vec![
("a", InputValue::string("foo")),
("b", InputValue::string("bar")),
("c", InputValue::string("baz")),
("extra", InputValue::string("dog")),
].into_iter().collect()))
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$input" got invalid value. In field "extra": Unknown field."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn allow_nullable_inputs_to_be_omitted() {
run_query(
r#"{ fieldWithNullableStringInput }"#,
|result| {
assert_eq!(
result.get("fieldWithNullableStringInput"),
Some(&Value::string(r#"None"#)));
});
}
#[test]
fn allow_nullable_inputs_to_be_omitted_in_variable() {
run_query(
r#"query q($value: String) { fieldWithNullableStringInput(input: $value) }"#,
|result| {
assert_eq!(
result.get("fieldWithNullableStringInput"),
Some(&Value::string(r#"None"#)));
});
}
#[test]
fn allow_nullable_inputs_to_be_set_to_null_in_variable() {
run_variable_query(
r#"query q($value: String) { fieldWithNullableStringInput(input: $value) }"#,
vec![
("value".to_owned(), InputValue::null()),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("fieldWithNullableStringInput"),
Some(&Value::string(r#"None"#)));
});
}
#[test]
fn allow_nullable_inputs_to_be_set_to_value_in_variable() {
run_variable_query(
r#"query q($value: String) { fieldWithNullableStringInput(input: $value) }"#,
vec![
("value".to_owned(), InputValue::string("a")),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("fieldWithNullableStringInput"),
Some(&Value::string(r#"Some("a")"#)));
});
}
#[test]
fn allow_nullable_inputs_to_be_set_to_value_directly() {
run_query(
r#"{ fieldWithNullableStringInput(input: "a") }"#,
|result| {
assert_eq!(
result.get("fieldWithNullableStringInput"),
Some(&Value::string(r#"Some("a")"#)));
});
}
#[test]
fn does_not_allow_non_nullable_input_to_be_omitted_in_variable() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($value: String!) { fieldWithNonNullableStringInput(input: $value) }"#;
let vars = vec![
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$value" of required type "String!" was not provided."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn does_not_allow_non_nullable_input_to_be_set_to_null_in_variable() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($value: String!) { fieldWithNonNullableStringInput(input: $value) }"#;
let vars = vec![
("value".to_owned(), InputValue::null()),
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$value" of required type "String!" was not provided."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn allow_non_nullable_inputs_to_be_set_to_value_in_variable() {
run_variable_query(
r#"query q($value: String!) { fieldWithNonNullableStringInput(input: $value) }"#,
vec![
("value".to_owned(), InputValue::string("a")),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("fieldWithNonNullableStringInput"),
Some(&Value::string(r#""a""#)));
});
}
#[test]
fn allow_non_nullable_inputs_to_be_set_to_value_directly() {
run_query(
r#"{ fieldWithNonNullableStringInput(input: "a") }"#,
|result| {
assert_eq!(
result.get("fieldWithNonNullableStringInput"),
Some(&Value::string(r#""a""#)));
});
}
#[test]
fn allow_lists_to_be_null() {
run_variable_query(
r#"query q($input: [String]) { list(input: $input) }"#,
vec![
("input".to_owned(), InputValue::null()),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("list"),
Some(&Value::string(r#"None"#)));
});
}
#[test]
fn allow_lists_to_contain_values() {
run_variable_query(
r#"query q($input: [String]) { list(input: $input) }"#,
vec![
("input".to_owned(), InputValue::list(vec![
InputValue::string("A"),
])),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("list"),
Some(&Value::string(r#"Some([Some("A")])"#)));
});
}
#[test]
fn allow_lists_to_contain_null() {
run_variable_query(
r#"query q($input: [String]) { list(input: $input) }"#,
vec![
("input".to_owned(), InputValue::list(vec![
InputValue::string("A"),
InputValue::null(),
InputValue::string("B"),
])),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("list"),
Some(&Value::string(r#"Some([Some("A"), None, Some("B")])"#)));
});
}
#[test]
fn does_not_allow_non_null_lists_to_be_null() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($input: [String]!) { nnList(input: $input) }"#;
let vars = vec![
("input".to_owned(), InputValue::null()),
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$input" of required type "[String]!" was not provided."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn allow_non_null_lists_to_contain_values() {
run_variable_query(
r#"query q($input: [String]!) { nnList(input: $input) }"#,
vec![
("input".to_owned(), InputValue::list(vec![
InputValue::string("A"),
])),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("nnList"),
Some(&Value::string(r#"[Some("A")]"#)));
});
}
#[test]
fn allow_non_null_lists_to_contain_null() {
run_variable_query(
r#"query q($input: [String]!) { nnList(input: $input) }"#,
vec![
("input".to_owned(), InputValue::list(vec![
InputValue::string("A"),
InputValue::null(),
InputValue::string("B"),
])),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("nnList"),
Some(&Value::string(r#"[Some("A"), None, Some("B")]"#)));
});
}
#[test]
fn allow_lists_of_non_null_to_be_null() {
run_variable_query(
r#"query q($input: [String!]) { listNn(input: $input) }"#,
vec![
("input".to_owned(), InputValue::null()),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("listNn"),
Some(&Value::string(r#"None"#)));
});
}
#[test]
fn allow_lists_of_non_null_to_contain_values() {
run_variable_query(
r#"query q($input: [String!]) { listNn(input: $input) }"#,
vec![
("input".to_owned(), InputValue::list(vec![
InputValue::string("A"),
])),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("listNn"),
Some(&Value::string(r#"Some(["A"])"#)));
});
}
#[test]
fn does_not_allow_lists_of_non_null_to_contain_null() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($input: [String!]) { listNn(input: $input) }"#;
let vars = vec![
("input".to_owned(), InputValue::list(vec![
InputValue::string("A"),
InputValue::null(),
InputValue::string("B"),
])),
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$input" got invalid value. In element #1: Expected "String!", found null."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn does_not_allow_non_null_lists_of_non_null_to_contain_null() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($input: [String!]!) { nnListNn(input: $input) }"#;
let vars = vec![
("input".to_owned(), InputValue::list(vec![
InputValue::string("A"),
InputValue::null(),
InputValue::string("B"),
])),
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$input" got invalid value. In element #1: Expected "String!", found null."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn does_not_allow_non_null_lists_of_non_null_to_be_null() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($input: [String!]!) { nnListNn(input: $input) }"#;
let vars = vec![
("value".to_owned(), InputValue::null()),
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$input" of required type "[String!]!" was not provided."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn allow_non_null_lists_of_non_null_to_contain_values() {
run_variable_query(
r#"query q($input: [String!]!) { nnListNn(input: $input) }"#,
vec![
("input".to_owned(), InputValue::list(vec![
InputValue::string("A"),
])),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("nnListNn"),
Some(&Value::string(r#"["A"]"#)));
});
}
#[test]
fn does_not_allow_invalid_types_to_be_used_as_values() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($input: TestType!) { fieldWithObjectInput(input: $input) }"#;
let vars = vec![
("value".to_owned(), InputValue::list(vec![
InputValue::string("A"),
InputValue::string("B"),
])),
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$input" expected value of type "TestType!" which cannot be used as an input type."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn does_not_allow_unknown_types_to_be_used_as_values() {
let schema = RootNode::new(TestType, ());
let query = r#"query q($input: UnknownType!) { fieldWithObjectInput(input: $input) }"#;
let vars = vec![
("value".to_owned(), InputValue::list(vec![
InputValue::string("A"),
InputValue::string("B"),
])),
].into_iter().collect();
let error = ::execute(query, None, &schema, &vars, &())
.unwrap_err();
assert_eq!(error, ValidationError(vec![
RuleError::new(
r#"Variable "$input" expected value of type "UnknownType!" which cannot be used as an input type."#,
&[SourcePosition::new(8, 0, 8)],
),
]));
}
#[test]
fn default_argument_when_not_provided() {
run_query(
r#"{ fieldWithDefaultArgumentValue }"#,
|result| {
assert_eq!(
result.get("fieldWithDefaultArgumentValue"),
Some(&Value::string(r#""Hello World""#)));
});
}
#[test]
fn default_argument_when_nullable_variable_not_provided() {
run_query(
r#"query q($input: String) { fieldWithDefaultArgumentValue(input: $input) }"#,
|result| {
assert_eq!(
result.get("fieldWithDefaultArgumentValue"),
Some(&Value::string(r#""Hello World""#)));
});
}
#[test]
fn default_argument_when_nullable_variable_set_to_null() {
run_variable_query(
r#"query q($input: String) { fieldWithDefaultArgumentValue(input: $input) }"#,
vec![
("input".to_owned(), InputValue::null()),
].into_iter().collect(),
|result| {
assert_eq!(
result.get("fieldWithDefaultArgumentValue"),
Some(&Value::string(r#""Hello World""#)));
});
}

View file

@ -204,7 +204,7 @@ use std::collections::HashMap;
use rustc_serialize::json::{ToJson, Json};
use parser::{parse_document_source, ParseError, Spanning, SourcePosition};
use validation::{RuleError, ValidatorContext, visit_all_rules};
use validation::{RuleError, ValidatorContext, visit_all_rules, validate_input_values};
use executor::execute_validated_query;
pub use ast::{ToInputValue, FromInputValue, InputValue, Type, Selection};
@ -242,6 +242,14 @@ pub fn execute<'a, CtxT, QueryT, MutationT>(
{
let document = try!(parse_document_source(document_source));
{
let errors = validate_input_values(variables, &document, &root_node.schema);
if !errors.is_empty() {
return Err(GraphQLError::ValidationError(errors));
}
}
{
let mut ctx = ValidatorContext::new(&root_node.schema, &document);
visit_all_rules(&mut ctx, &document);

View file

@ -21,17 +21,6 @@ macro_rules! __graphql__args {
__graphql__args!(@assign_arg_vars, $args, $executorvar, $($rest)*);
};
(
@assign_arg_vars,
$args:ident, $executorvar:ident,
$name:ident : Option<$ty:ty> as $desc:tt $($rest:tt)*
) => {
let $name: Option<$ty> = $args
.get(&$crate::to_snake_case(stringify!($name)))
.unwrap_or(None);
__graphql__args!(@assign_arg_vars, $args, $executorvar, $($rest)*);
};
(
@assign_arg_vars,
$args:ident, $executorvar:ident,

View file

@ -50,8 +50,13 @@ macro_rules! graphql_input_object {
Some($name {
$( $field_name: {
let n: String = $crate::to_snake_case(stringify!($field_name));
let v: &$crate::InputValue = $var[&n[..]];
$crate::FromInputValue::from(v).unwrap()
let v: Option<&&$crate::InputValue> = $var.get(&n[..]);
if let Some(v) = v {
$crate::FromInputValue::from(v).unwrap()
} else {
$crate::FromInputValue::from(&$crate::InputValue::null()).unwrap()
}
} ),*
})
};

View file

@ -338,3 +338,13 @@ impl fmt::Display for DirectiveLocation {
})
}
}
impl<'a> fmt::Display for TypeType<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
TypeType::Concrete(ref t) => f.write_str(&t.name().unwrap()),
TypeType::List(ref i) => write!(f, "[{}]", i),
TypeType::NonNull(ref i) => write!(f, "{}!", i),
}
}
}

View file

@ -76,9 +76,11 @@ impl Arguments {
if let (&mut Some(ref mut args), &Some(ref meta_args)) = (&mut args, meta_args) {
for arg in meta_args {
if !args.contains_key(&arg.name) {
if !args.contains_key(&arg.name) || args[&arg.name].is_null() {
if let Some(ref default_value) = arg.default_value {
args.insert(arg.name.clone(), default_value.clone());
} else {
args.insert(arg.name.clone(), InputValue::null());
}
}
}

View file

@ -25,7 +25,7 @@ impl<T, CtxT> GraphQLType<CtxT> for Option<T> where T: GraphQLType<CtxT> {
impl<T> FromInputValue for Option<T> where T: FromInputValue {
fn from(v: &InputValue) -> Option<Option<T>> {
match v {
&InputValue::Null => None,
&InputValue::Null => Some(None),
v => match v.convert() {
Some(x) => Some(Some(x)),
None => None,
@ -79,7 +79,12 @@ impl<T> FromInputValue for Vec<T> where T: FromInputValue {
None
}
},
_ => None,
ref other =>
if let Some(e) = other.convert() {
Some(vec![ e ])
} else {
None
}
}
}
}

View file

@ -0,0 +1,268 @@
use std::collections::{HashMap, HashSet};
use std::fmt;
use parser::SourcePosition;
use ast::{InputValue, Document, Definition, VariableDefinitions};
use validation::RuleError;
use schema::model::{SchemaType, TypeType};
use schema::meta::{MetaType, ScalarMeta, InputObjectMeta, EnumMeta};
#[derive(Debug)]
enum Path<'a> {
Root,
ArrayElement(usize, &'a Path<'a>),
ObjectField(&'a str, &'a Path<'a>),
}
pub fn validate_input_values(
values: &HashMap<String, InputValue>,
document: &Document,
schema: &SchemaType,
)
-> Vec<RuleError>
{
let mut errs = vec![];
for def in document {
if let &Definition::Operation(ref op) = def {
if let Some(ref vars) = op.item.variable_definitions {
validate_var_defs(values, &vars.item, schema, &mut errs);
}
}
}
errs.sort();
errs
}
fn validate_var_defs(
values: &HashMap<String, InputValue>,
var_defs: &VariableDefinitions,
schema: &SchemaType,
errors: &mut Vec<RuleError>,
) {
for &(ref name, ref def) in var_defs.iter() {
let raw_type_name = def.var_type.item.innermost_name();
match schema.concrete_type_by_name(raw_type_name) {
Some(t) if t.is_input() => {
let ct = schema.make_type(&def.var_type.item);
if def.var_type.item.is_non_null() && is_absent_or_null(values.get(&name.item)) {
errors.push(RuleError::new(
&format!(
r#"Variable "${}" of required type "{}" was not provided."#,
name.item, def.var_type.item,
),
&[ name.start.clone() ],
));
} else if let Some(ref v) = values.get(&name.item) {
unify_value(&name.item, &name.start, v, &ct, schema, errors, Path::Root);
}
},
_ => errors.push(RuleError::new(
&format!(
r#"Variable "${}" expected value of type "{}" which cannot be used as an input type."#,
name.item, def.var_type.item,
),
&[ name.start.clone() ],
))
}
}
}
fn unify_value<'a>(
var_name: &str,
var_pos: &SourcePosition,
value: &InputValue,
meta_type: &TypeType<'a>,
schema: &SchemaType,
errors: &mut Vec<RuleError>,
path: Path<'a>,
) {
match *meta_type {
TypeType::NonNull(ref inner) => {
if value.is_null() {
push_unification_error(
errors, var_name, var_pos, &path,
&format!(r#"Expected "{}", found null"#, meta_type)
);
}
else {
unify_value(var_name, var_pos, value, &inner, schema, errors, path);
}
}
TypeType::List(ref inner) => {
if value.is_null() {
return;
}
match value.to_list_value() {
Some(l) =>
for (i, v) in l.iter().enumerate() {
unify_value(var_name, var_pos, v, &inner, schema, errors, Path::ArrayElement(i, &path));
},
_ => unify_value(var_name, var_pos, value, &inner, schema, errors, path)
}
}
TypeType::Concrete(mt) => {
if value.is_null() {
return;
}
match mt {
&MetaType::Scalar(ref sm) =>
unify_scalar(var_name, var_pos, value, sm, errors, &path),
&MetaType::Enum(ref em) =>
unify_enum(var_name, var_pos, value, em, errors, &path),
&MetaType::InputObject(ref iom) =>
unify_input_object(var_name, var_pos, value, iom, schema, errors, &path),
_ => panic!("Can't unify non-input concrete type"),
}
}
}
}
fn unify_scalar<'a>(
var_name: &str,
var_pos: &SourcePosition,
value: &InputValue,
meta: &ScalarMeta,
errors: &mut Vec<RuleError>,
path: &Path<'a>,
) {
match value {
&InputValue::List(_) =>
push_unification_error(
errors,
var_name,
var_pos,
path,
&format!(r#"Expected "{}", found list"#, meta.name),
),
&InputValue::Object(_) =>
push_unification_error(
errors,
var_name,
var_pos,
path,
&format!(r#"Expected "{}", found object"#, meta.name),
),
_ => (),
}
}
fn unify_enum<'a>(
var_name: &str,
var_pos: &SourcePosition,
value: &InputValue,
meta: &EnumMeta,
errors: &mut Vec<RuleError>,
path: &Path<'a>,
) {
match value {
&InputValue::String(_) | &InputValue::Enum(_) => (),
_ => push_unification_error(
errors,
var_name,
var_pos,
path,
&format!(r#"Expected "{}", found not a string or enum"#, meta.name),
)
}
}
fn unify_input_object<'a>(
var_name: &str,
var_pos: &SourcePosition,
value: &InputValue,
meta: &InputObjectMeta,
schema: &SchemaType,
errors: &mut Vec<RuleError>,
path: &Path<'a>,
) {
if let Some(ref obj) = value.to_object_value() {
let mut keys = obj.keys().collect::<HashSet<&&str>>();
for input_field in &meta.input_fields {
let mut has_value = false;
keys.remove(&input_field.name.as_str());
if let Some(ref value) = obj.get(input_field.name.as_str()) {
if !value.is_null() {
has_value = true;
unify_value(
var_name,
var_pos,
value,
&schema.make_type(&input_field.arg_type),
schema,
errors,
Path::ObjectField(&input_field.name, path),
);
}
}
if !has_value && input_field.arg_type.is_non_null() {
push_unification_error(
errors,
var_name,
var_pos,
&Path::ObjectField(&input_field.name, path),
&format!(r#"Expected "{}", found null"#, input_field.arg_type),
);
}
}
for key in keys {
push_unification_error(
errors,
var_name,
var_pos,
&Path::ObjectField(&key, path),
&format!("Unknown field"),
);
}
}
else {
push_unification_error(
errors,
var_name,
var_pos,
path,
&format!(r#"Expected "{}", found not an object"#, meta.name),
);
}
}
fn is_absent_or_null(v: Option<&InputValue>) -> bool {
v.map_or(true, InputValue::is_null)
}
fn push_unification_error<'a>(
errors: &mut Vec<RuleError>,
var_name: &str,
var_pos: &SourcePosition,
path: &Path<'a>,
message: &str,
) {
errors.push(RuleError::new(
&format!(
r#"Variable "${}" got invalid value. {}{}."#,
var_name, path, message,
),
&[ var_pos.clone() ],
));
}
impl<'a> fmt::Display for Path<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Path::Root => write!(f, ""),
Path::ArrayElement(idx, prev) => write!(f, "{}In element #{}: ", prev, idx),
Path::ObjectField(name, prev) => write!(f, r#"{}In field "{}": "#, prev, name),
}
}
}

View file

@ -5,6 +5,7 @@ mod traits;
mod context;
mod multi_visitor;
mod rules;
mod input_value;
#[cfg(test)]
mod test_harness;
@ -14,6 +15,7 @@ pub use self::visitor::visit;
pub use self::context::{RuleError, ValidatorContext};
pub use self::rules::visit_all_rules;
pub use self::multi_visitor::MultiVisitor;
pub use self::input_value::validate_input_values;
#[cfg(test)]
pub use self::test_harness::{

View file

@ -54,8 +54,6 @@ impl<'a> VariableInAllowedPosition<'a> {
(_, t) => t.clone(),
};
println!("Variable {} of type {} used in position expecting {}", var_name.item, expected_type, var_type);
if !ctx.schema.is_subtype(&expected_type, var_type) {
ctx.report_error(
&error_message(&var_name.item, &format!("{}", expected_type), &format!("{}", var_type)),