Add executor tests, fix a bunch of bugs
This commit is contained in:
parent
251f957a7f
commit
c555241978
6 changed files with 414 additions and 9 deletions
|
@ -1,6 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use ::GraphQLError;
|
||||
use ast::{InputValue, ToInputValue, Document, Selection, Fragment, Definition, Type, FromInputValue, OperationType};
|
||||
use value::Value;
|
||||
use parser::SourcePosition;
|
||||
|
@ -198,6 +199,15 @@ impl<'a> FieldPath<'a> {
|
|||
}
|
||||
|
||||
impl ExecutionError {
|
||||
#[doc(hidden)]
|
||||
pub fn new(location: SourcePosition, path: &[&str], message: &str) -> ExecutionError {
|
||||
ExecutionError {
|
||||
location: location,
|
||||
path: path.iter().map(|s| (*s).to_owned()).collect(),
|
||||
message: message.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The error message
|
||||
pub fn message(&self) -> &str {
|
||||
&self.message
|
||||
|
@ -214,16 +224,16 @@ impl ExecutionError {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn execute_validated_query<QueryT, MutationT, CtxT>(
|
||||
pub fn execute_validated_query<'a, QueryT, MutationT, CtxT>(
|
||||
document: Document,
|
||||
operation_name: Option<&str>,
|
||||
root_node: &RootNode<CtxT, QueryT, MutationT>,
|
||||
variables: &HashMap<String, InputValue>,
|
||||
context: &CtxT
|
||||
)
|
||||
-> (Value, Vec<ExecutionError>)
|
||||
-> Result<(Value, Vec<ExecutionError>), GraphQLError<'a>>
|
||||
where QueryT: GraphQLType<CtxT>,
|
||||
MutationT: GraphQLType<CtxT>,
|
||||
MutationT: GraphQLType<CtxT>
|
||||
{
|
||||
let mut fragments = vec![];
|
||||
let mut operation = None;
|
||||
|
@ -232,7 +242,7 @@ pub fn execute_validated_query<QueryT, MutationT, CtxT>(
|
|||
match def {
|
||||
Definition::Operation(op) => {
|
||||
if operation_name.is_none() && operation.is_some() {
|
||||
panic!("Must provide operation name if query contains multiple operations");
|
||||
return Err(GraphQLError::MultipleOperationsProvided);
|
||||
}
|
||||
|
||||
let move_op = operation_name.is_none()
|
||||
|
@ -246,7 +256,11 @@ pub fn execute_validated_query<QueryT, MutationT, CtxT>(
|
|||
};
|
||||
}
|
||||
|
||||
let op = operation.expect("Could not find operation to execute");
|
||||
let op = match operation {
|
||||
Some(op) => op,
|
||||
None => return Err(GraphQLError::UnknownOperationName),
|
||||
};
|
||||
|
||||
let mut errors = Vec::new();
|
||||
let value;
|
||||
|
||||
|
@ -269,7 +283,7 @@ pub fn execute_validated_query<QueryT, MutationT, CtxT>(
|
|||
|
||||
errors.sort();
|
||||
|
||||
(value, errors)
|
||||
Ok((value, errors))
|
||||
}
|
||||
|
||||
impl<CtxT> Registry<CtxT> {
|
||||
|
|
332
src/executor_tests/executor.rs
Normal file
332
src/executor_tests/executor.rs
Normal file
|
@ -0,0 +1,332 @@
|
|||
mod field_execution {
|
||||
use value::Value;
|
||||
use ast::InputValue;
|
||||
use schema::model::RootNode;
|
||||
|
||||
struct DataType;
|
||||
struct DeepDataType;
|
||||
|
||||
graphql_object!(DataType: () |&self| {
|
||||
field a() -> &str { "Apple" }
|
||||
field b() -> &str { "Banana" }
|
||||
field c() -> &str { "Cookie" }
|
||||
field d() -> &str { "Donut" }
|
||||
field e() -> &str { "Egg" }
|
||||
field f() -> &str { "Fish" }
|
||||
|
||||
field pic(size: Option<i64>) -> String {
|
||||
format!("Pic of size: {}", size.unwrap_or(50))
|
||||
}
|
||||
|
||||
field deep() -> DeepDataType {
|
||||
DeepDataType
|
||||
}
|
||||
});
|
||||
|
||||
graphql_object!(DeepDataType: () |&self| {
|
||||
field a() -> &str { "Already Been Done" }
|
||||
field b() -> &str { "Boring" }
|
||||
field c() -> &[Option<&str>] { &[Some("Contrived"), None, Some("Confusing")] }
|
||||
|
||||
field deeper() -> &[Option<DataType>] { &[Some(DataType), None, Some(DataType) ] }
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let schema = RootNode::new(DataType, ());
|
||||
let doc = r"
|
||||
query Example($size: Int) {
|
||||
a,
|
||||
b,
|
||||
x: c
|
||||
...c
|
||||
f
|
||||
...on DataType {
|
||||
pic(size: $size)
|
||||
}
|
||||
deep {
|
||||
a
|
||||
b
|
||||
c
|
||||
deeper {
|
||||
a
|
||||
b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment c on DataType {
|
||||
d
|
||||
e
|
||||
}";
|
||||
|
||||
let vars = vec![
|
||||
("size".to_owned(), InputValue::int(100))
|
||||
].into_iter().collect();
|
||||
|
||||
let (result, errs) = ::execute(doc, None, &schema, &vars, &())
|
||||
.expect("Execution failed");
|
||||
|
||||
assert_eq!(errs, []);
|
||||
|
||||
println!("Result: {:?}", result);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Value::object(vec![
|
||||
("a", Value::string("Apple")),
|
||||
("b", Value::string("Banana")),
|
||||
("x", Value::string("Cookie")),
|
||||
("d", Value::string("Donut")),
|
||||
("e", Value::string("Egg")),
|
||||
("f", Value::string("Fish")),
|
||||
("pic", Value::string("Pic of size: 100")),
|
||||
("deep", Value::object(vec![
|
||||
("a", Value::string("Already Been Done")),
|
||||
("b", Value::string("Boring")),
|
||||
("c", Value::list(vec![
|
||||
Value::string("Contrived"),
|
||||
Value::null(),
|
||||
Value::string("Confusing"),
|
||||
])),
|
||||
("deeper", Value::list(vec![
|
||||
Value::object(vec![
|
||||
("a", Value::string("Apple")),
|
||||
("b", Value::string("Banana")),
|
||||
].into_iter().collect()),
|
||||
Value::null(),
|
||||
Value::object(vec![
|
||||
("a", Value::string("Apple")),
|
||||
("b", Value::string("Banana")),
|
||||
].into_iter().collect()),
|
||||
])),
|
||||
].into_iter().collect())),
|
||||
].into_iter().collect()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mod merge_parallel_fragments {
|
||||
use value::Value;
|
||||
use schema::model::RootNode;
|
||||
|
||||
struct Type;
|
||||
|
||||
graphql_object!(Type: () |&self| {
|
||||
field a() -> &str { "Apple" }
|
||||
field b() -> &str { "Banana" }
|
||||
field c() -> &str { "Cherry" }
|
||||
field deep() -> Type { Type }
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let schema = RootNode::new(Type, ());
|
||||
let doc = r"
|
||||
{ a, ...FragOne, ...FragTwo }
|
||||
fragment FragOne on Type {
|
||||
b
|
||||
deep { b, deeper: deep { b } }
|
||||
}
|
||||
fragment FragTwo on Type {
|
||||
c
|
||||
deep { c, deeper: deep { c } }
|
||||
}";
|
||||
|
||||
let vars = vec![].into_iter().collect();
|
||||
|
||||
let (result, errs) = ::execute(doc, None, &schema, &vars, &())
|
||||
.expect("Execution failed");
|
||||
|
||||
assert_eq!(errs, []);
|
||||
|
||||
println!("Result: {:?}", result);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Value::object(vec![
|
||||
("a", Value::string("Apple")),
|
||||
("b", Value::string("Banana")),
|
||||
("c", Value::string("Cherry")),
|
||||
("deep", Value::object(vec![
|
||||
("b", Value::string("Banana")),
|
||||
("c", Value::string("Cherry")),
|
||||
("deeper", Value::object(vec![
|
||||
("b", Value::string("Banana")),
|
||||
("c", Value::string("Cherry")),
|
||||
].into_iter().collect())),
|
||||
].into_iter().collect())),
|
||||
].into_iter().collect()));
|
||||
}
|
||||
}
|
||||
|
||||
mod threads_context_correctly {
|
||||
use value::Value;
|
||||
use schema::model::RootNode;
|
||||
|
||||
struct Schema;
|
||||
|
||||
graphql_object!(Schema: String |&self| {
|
||||
field a(&mut executor) -> String { executor.context().clone() }
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let schema = RootNode::new(Schema, ());
|
||||
let doc = r"{ a }";
|
||||
|
||||
let vars = vec![].into_iter().collect();
|
||||
|
||||
let (result, errs) = ::execute(doc, None, &schema, &vars, &"Context value".to_owned())
|
||||
.expect("Execution failed");
|
||||
|
||||
assert_eq!(errs, []);
|
||||
|
||||
println!("Result: {:?}", result);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Value::object(vec![
|
||||
("a", Value::string("Context value")),
|
||||
].into_iter().collect()));
|
||||
}
|
||||
}
|
||||
|
||||
mod nulls_out_errors {
|
||||
use value::Value;
|
||||
use schema::model::RootNode;
|
||||
use executor::{ExecutionError, FieldResult};
|
||||
use parser::SourcePosition;
|
||||
|
||||
struct Schema;
|
||||
|
||||
graphql_object!(Schema: () |&self| {
|
||||
field sync() -> FieldResult<&str> { Ok("sync") }
|
||||
field sync_error() -> FieldResult<&str> { Err("Error for syncError".to_owned()) }
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let schema = RootNode::new(Schema, ());
|
||||
let doc = r"{ sync, syncError }";
|
||||
|
||||
let vars = vec![].into_iter().collect();
|
||||
|
||||
let (result, errs) = ::execute(doc, None, &schema, &vars, &())
|
||||
.expect("Execution failed");
|
||||
|
||||
println!("Result: {:?}", result);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Value::object(vec![
|
||||
("sync", Value::string("sync")),
|
||||
("syncError", Value::null()),
|
||||
].into_iter().collect()));
|
||||
|
||||
assert_eq!(
|
||||
errs,
|
||||
vec![
|
||||
ExecutionError::new(
|
||||
SourcePosition::new(8, 0, 8),
|
||||
&["syncError"],
|
||||
"Error for syncError",
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
mod named_operations {
|
||||
use value::Value;
|
||||
use schema::model::RootNode;
|
||||
use ::GraphQLError;
|
||||
|
||||
struct Schema;
|
||||
|
||||
graphql_object!(Schema: () |&self| {
|
||||
field a() -> &str { "b" }
|
||||
});
|
||||
|
||||
#[test]
|
||||
fn uses_inline_operation_if_no_name_provided() {
|
||||
let schema = RootNode::new(Schema, ());
|
||||
let doc = r"{ a }";
|
||||
|
||||
let vars = vec![].into_iter().collect();
|
||||
|
||||
let (result, errs) = ::execute(doc, None, &schema, &vars, &())
|
||||
.expect("Execution failed");
|
||||
|
||||
assert_eq!(errs, []);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Value::object(vec![
|
||||
("a", Value::string("b")),
|
||||
].into_iter().collect()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_only_named_operation() {
|
||||
let schema = RootNode::new(Schema, ());
|
||||
let doc = r"query Example { a }";
|
||||
|
||||
let vars = vec![].into_iter().collect();
|
||||
|
||||
let (result, errs) = ::execute(doc, None, &schema, &vars, &())
|
||||
.expect("Execution failed");
|
||||
|
||||
assert_eq!(errs, []);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Value::object(vec![
|
||||
("a", Value::string("b")),
|
||||
].into_iter().collect()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uses_named_operation_if_name_provided() {
|
||||
let schema = RootNode::new(Schema, ());
|
||||
let doc = r"query Example { first: a } query OtherExample { second: a }";
|
||||
|
||||
let vars = vec![].into_iter().collect();
|
||||
|
||||
let (result, errs) = ::execute(doc, Some("OtherExample"), &schema, &vars, &())
|
||||
.expect("Execution failed");
|
||||
|
||||
assert_eq!(errs, []);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Value::object(vec![
|
||||
("second", Value::string("b")),
|
||||
].into_iter().collect()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_if_multiple_operations_provided_but_no_name() {
|
||||
let schema = RootNode::new(Schema, ());
|
||||
let doc = r"query Example { first: a } query OtherExample { second: a }";
|
||||
|
||||
let vars = vec![].into_iter().collect();
|
||||
|
||||
let err = ::execute(doc, None, &schema, &vars, &())
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(err, GraphQLError::MultipleOperationsProvided);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_if_unknown_operation_name_provided() {
|
||||
let schema = RootNode::new(Schema, ());
|
||||
let doc = r"query Example { first: a } query OtherExample { second: a }";
|
||||
|
||||
let vars = vec![].into_iter().collect();
|
||||
|
||||
let err = ::execute(doc, Some("UnknownExample"), &schema, &vars, &())
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(err, GraphQLError::UnknownOperationName);
|
||||
}
|
||||
}
|
|
@ -2,3 +2,4 @@ mod introspection;
|
|||
mod variables;
|
||||
mod enums;
|
||||
mod directives;
|
||||
mod executor;
|
||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -226,6 +226,9 @@ pub use schema::meta;
|
|||
pub enum GraphQLError<'a> {
|
||||
ParseError(Spanning<ParseError<'a>>),
|
||||
ValidationError(Vec<RuleError>),
|
||||
NoOperationProvided,
|
||||
MultipleOperationsProvided,
|
||||
UnknownOperationName,
|
||||
}
|
||||
|
||||
/// Execute a query in a provided schema
|
||||
|
@ -260,7 +263,7 @@ pub fn execute<'a, CtxT, QueryT, MutationT>(
|
|||
}
|
||||
}
|
||||
|
||||
Ok(execute_validated_query(document, operation_name, root_node, variables, context))
|
||||
execute_validated_query(document, operation_name, root_node, variables, context)
|
||||
}
|
||||
|
||||
impl<'a> From<Spanning<ParseError<'a>>> for GraphQLError<'a> {
|
||||
|
@ -274,6 +277,12 @@ impl<'a> ToJson for GraphQLError<'a> {
|
|||
let errs = match *self {
|
||||
GraphQLError::ParseError(ref err) => parse_error_to_json(err),
|
||||
GraphQLError::ValidationError(ref errs) => errs.to_json(),
|
||||
GraphQLError::MultipleOperationsProvided => Json::String(
|
||||
"Must provide operation name if query contains multiple operations.".to_owned()),
|
||||
GraphQLError::NoOperationProvided => Json::String(
|
||||
"Must provide an operation".to_owned()),
|
||||
GraphQLError::UnknownOperationName => Json::String(
|
||||
"Unknown operation".to_owned()),
|
||||
};
|
||||
|
||||
Json::Object(vec![
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use ast::{InputValue, Selection, Directive, FromInputValue};
|
||||
use value::Value;
|
||||
|
@ -324,8 +325,11 @@ fn resolve_selection_set_into<T, CtxT>(
|
|||
&mut sub_exec);
|
||||
|
||||
match field_result {
|
||||
Ok(v) => { result.insert(response_name.clone(), v); }
|
||||
Err(e) => { sub_exec.push_error(e, start_pos); }
|
||||
Ok(v) => merge_key_into(result, response_name.clone(), v),
|
||||
Err(e) => {
|
||||
sub_exec.push_error(e, start_pos);
|
||||
result.insert(response_name.clone(), Value::null());
|
||||
}
|
||||
}
|
||||
},
|
||||
Selection::FragmentSpread(Spanning { item: spread, .. }) => {
|
||||
|
@ -408,3 +412,40 @@ fn is_excluded(directives: &Option<Vec<Directive>>, vars: &HashMap<String, Input
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_key_into(
|
||||
result: &mut HashMap<String, Value>,
|
||||
response_name: String,
|
||||
value: Value,
|
||||
) {
|
||||
match result.entry(response_name) {
|
||||
Entry::Occupied(mut e) => {
|
||||
println!("Merging object at '{}'", e.key());
|
||||
match (e.get_mut().as_mut_object_value(), value) {
|
||||
(Some(dest_obj), Value::Object(src_obj)) => {
|
||||
merge_maps(dest_obj, src_obj);
|
||||
},
|
||||
_ => {
|
||||
println!("Not merging object/object - this is bad :(");
|
||||
}
|
||||
}
|
||||
},
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(value);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_maps(
|
||||
dest: &mut HashMap<String, Value>,
|
||||
src: HashMap<String, Value>,
|
||||
) {
|
||||
for (key, value) in src {
|
||||
if dest.contains_key(&key) {
|
||||
merge_key_into(dest, key, value);
|
||||
}
|
||||
else {
|
||||
dest.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,14 @@ impl Value {
|
|||
}
|
||||
}
|
||||
|
||||
/// Mutable view into the underlying object value, if present.
|
||||
pub fn as_mut_object_value(&mut self) -> Option<&mut HashMap<String, Value>> {
|
||||
match *self {
|
||||
Value::Object(ref mut o) => Some(o),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// View the underlying list value, if present.
|
||||
pub fn as_list_value(&self) -> Option<&Vec<Value>> {
|
||||
match *self {
|
||||
|
|
Loading…
Reference in a new issue