Handle an array of GraphQL queries (#171)
This commit is contained in:
parent
f0cbc97dc7
commit
e84167286b
6 changed files with 122 additions and 17 deletions
|
@ -37,7 +37,7 @@ juniper_codegen = { version = "0.9.2", path = "../juniper_codegen" }
|
||||||
fnv = "1.0.3"
|
fnv = "1.0.3"
|
||||||
indexmap = { version = "1.0.0", features = ["serde-1"] }
|
indexmap = { version = "1.0.0", features = ["serde-1"] }
|
||||||
serde = { version = "1.0.8" }
|
serde = { version = "1.0.8" }
|
||||||
serde_derive = {version="1.0.2" }
|
serde_derive = { version = "1.0.2" }
|
||||||
|
|
||||||
chrono = { version = "0.4.0", optional = true }
|
chrono = { version = "0.4.0", optional = true }
|
||||||
serde_json = { version="1.0.2", optional = true }
|
serde_json = { version="1.0.2", optional = true }
|
||||||
|
|
|
@ -165,6 +165,9 @@ pub mod tests {
|
||||||
|
|
||||||
println!(" - test_simple_post");
|
println!(" - test_simple_post");
|
||||||
test_simple_post(integration);
|
test_simple_post(integration);
|
||||||
|
|
||||||
|
println!(" - test_batched_post");
|
||||||
|
test_batched_post(integration);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unwrap_json_response(response: &TestResponse) -> Json {
|
fn unwrap_json_response(response: &TestResponse) -> Json {
|
||||||
|
@ -257,4 +260,17 @@ pub mod tests {
|
||||||
.expect("Invalid JSON constant in test")
|
.expect("Invalid JSON constant in test")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_batched_post<T: HTTPIntegration>(integration: &T) {
|
||||||
|
let response = integration.post("/", r#"[{"query": "{hero{name}}"}, {"query": "{hero{name}}"}]"#);
|
||||||
|
|
||||||
|
assert_eq!(response.status_code, 200);
|
||||||
|
assert_eq!(response.content_type, "application/json");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
unwrap_json_response(&response),
|
||||||
|
serde_json::from_str::<Json>(r#"[{"data": {"hero": {"name": "R2-D2"}}}, {"data": {"hero": {"name": "R2-D2"}}}]"#)
|
||||||
|
.expect("Invalid JSON constant in test")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ repository = "https://github.com/graphql-rust/juniper"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0.2" }
|
serde = { version = "1.0.2" }
|
||||||
serde_json = { version = "1.0.2" }
|
serde_json = { version = "1.0.2" }
|
||||||
|
serde_derive = { version = "1.0.2" }
|
||||||
juniper = { version = "0.9.2", path = "../juniper" }
|
juniper = { version = "0.9.2", path = "../juniper" }
|
||||||
|
|
||||||
urlencoded = { version = ">= 0.5, < 0.7" }
|
urlencoded = { version = ">= 0.5, < 0.7" }
|
||||||
|
|
|
@ -107,6 +107,8 @@ extern crate iron;
|
||||||
extern crate iron_test;
|
extern crate iron_test;
|
||||||
extern crate juniper;
|
extern crate juniper;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
extern crate urlencoded;
|
extern crate urlencoded;
|
||||||
|
|
||||||
use iron::prelude::*;
|
use iron::prelude::*;
|
||||||
|
@ -125,6 +127,48 @@ use serde_json::error::Error as SerdeError;
|
||||||
use juniper::{GraphQLType, InputValue, RootNode};
|
use juniper::{GraphQLType, InputValue, RootNode};
|
||||||
use juniper::http;
|
use juniper::http;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum GraphQLBatchRequest {
|
||||||
|
Single(http::GraphQLRequest),
|
||||||
|
Batch(Vec<http::GraphQLRequest>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum GraphQLBatchResponse<'a> {
|
||||||
|
Single(http::GraphQLResponse<'a>),
|
||||||
|
Batch(Vec<http::GraphQLResponse<'a>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphQLBatchRequest {
|
||||||
|
pub fn execute<'a, CtxT, QueryT, MutationT>(
|
||||||
|
&'a self,
|
||||||
|
root_node: &RootNode<QueryT, MutationT>,
|
||||||
|
context: &CtxT,
|
||||||
|
) -> GraphQLBatchResponse<'a>
|
||||||
|
where
|
||||||
|
QueryT: GraphQLType<Context = CtxT>,
|
||||||
|
MutationT: GraphQLType<Context = CtxT>,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
&GraphQLBatchRequest::Single(ref request) =>
|
||||||
|
GraphQLBatchResponse::Single(request.execute(root_node, context)),
|
||||||
|
&GraphQLBatchRequest::Batch(ref requests) =>
|
||||||
|
GraphQLBatchResponse::Batch(requests.iter().map(|request| request.execute(root_node, context)).collect()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GraphQLBatchResponse<'a> {
|
||||||
|
fn is_ok(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&GraphQLBatchResponse::Single(ref response) => response.is_ok(),
|
||||||
|
&GraphQLBatchResponse::Batch(ref responses) => responses.iter().fold(true, |ok, response| ok && response.is_ok()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Handler that executes `GraphQL` queries in the given schema
|
/// Handler that executes `GraphQL` queries in the given schema
|
||||||
///
|
///
|
||||||
/// The handler responds to GET requests and POST requests only. In GET
|
/// The handler responds to GET requests and POST requests only. In GET
|
||||||
|
@ -199,7 +243,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_get(&self, req: &mut Request) -> IronResult<http::GraphQLRequest> {
|
fn handle_get(&self, req: &mut Request) -> IronResult<GraphQLBatchRequest> {
|
||||||
let url_query_string = req.get_mut::<UrlEncodedQuery>()
|
let url_query_string = req.get_mut::<UrlEncodedQuery>()
|
||||||
.map_err(GraphQLIronError::Url)?;
|
.map_err(GraphQLIronError::Url)?;
|
||||||
|
|
||||||
|
@ -208,24 +252,24 @@ where
|
||||||
let operation_name = parse_url_param(url_query_string.remove("operationName"))?;
|
let operation_name = parse_url_param(url_query_string.remove("operationName"))?;
|
||||||
let variables = parse_variable_param(url_query_string.remove("variables"))?;
|
let variables = parse_variable_param(url_query_string.remove("variables"))?;
|
||||||
|
|
||||||
Ok(http::GraphQLRequest::new(
|
Ok(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
|
||||||
input_query,
|
input_query,
|
||||||
operation_name,
|
operation_name,
|
||||||
variables,
|
variables,
|
||||||
))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_post(&self, req: &mut Request) -> IronResult<http::GraphQLRequest> {
|
fn handle_post(&self, req: &mut Request) -> IronResult<GraphQLBatchRequest> {
|
||||||
let mut request_payload = String::new();
|
let mut request_payload = String::new();
|
||||||
itry!(req.body.read_to_string(&mut request_payload));
|
itry!(req.body.read_to_string(&mut request_payload));
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
serde_json::from_str::<http::GraphQLRequest>(request_payload.as_str())
|
serde_json::from_str::<GraphQLBatchRequest>(request_payload.as_str())
|
||||||
.map_err(GraphQLIronError::Serde)?,
|
.map_err(GraphQLIronError::Serde)?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute(&self, context: &CtxT, request: http::GraphQLRequest) -> IronResult<Response> {
|
fn execute(&self, context: &CtxT, request: GraphQLBatchRequest) -> IronResult<Response> {
|
||||||
let response = request.execute(&self.root_node, context);
|
let response = request.execute(&self.root_node, context);
|
||||||
let content_type = "application/json".parse::<Mime>().unwrap();
|
let content_type = "application/json".parse::<Mime>().unwrap();
|
||||||
let json = serde_json::to_string_pretty(&response).unwrap();
|
let json = serde_json::to_string_pretty(&response).unwrap();
|
||||||
|
|
|
@ -12,8 +12,8 @@ repository = "https://github.com/graphql-rust/juniper"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0.2" }
|
serde = { version = "1.0.2" }
|
||||||
serde_derive = {version="1.0.2" }
|
|
||||||
serde_json = { version = "1.0.2" }
|
serde_json = { version = "1.0.2" }
|
||||||
|
serde_derive = { version = "1.0.2" }
|
||||||
juniper = { version = "0.9.2" , path = "../juniper"}
|
juniper = { version = "0.9.2" , path = "../juniper"}
|
||||||
|
|
||||||
rocket = { version = "0.3.9" }
|
rocket = { version = "0.3.9" }
|
||||||
|
|
|
@ -42,6 +42,8 @@ Check the LICENSE file for details.
|
||||||
extern crate juniper;
|
extern crate juniper;
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
use std::io::{Cursor, Read};
|
use std::io::{Cursor, Read};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
@ -61,13 +63,55 @@ use juniper::GraphQLType;
|
||||||
use juniper::FieldError;
|
use juniper::FieldError;
|
||||||
use juniper::RootNode;
|
use juniper::RootNode;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum GraphQLBatchRequest {
|
||||||
|
Single(http::GraphQLRequest),
|
||||||
|
Batch(Vec<http::GraphQLRequest>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum GraphQLBatchResponse<'a> {
|
||||||
|
Single(http::GraphQLResponse<'a>),
|
||||||
|
Batch(Vec<http::GraphQLResponse<'a>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphQLBatchRequest {
|
||||||
|
pub fn execute<'a, CtxT, QueryT, MutationT>(
|
||||||
|
&'a self,
|
||||||
|
root_node: &RootNode<QueryT, MutationT>,
|
||||||
|
context: &CtxT,
|
||||||
|
) -> GraphQLBatchResponse<'a>
|
||||||
|
where
|
||||||
|
QueryT: GraphQLType<Context = CtxT>,
|
||||||
|
MutationT: GraphQLType<Context = CtxT>,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
&GraphQLBatchRequest::Single(ref request) =>
|
||||||
|
GraphQLBatchResponse::Single(request.execute(root_node, context)),
|
||||||
|
&GraphQLBatchRequest::Batch(ref requests) =>
|
||||||
|
GraphQLBatchResponse::Batch(requests.iter().map(|request| request.execute(root_node, context)).collect()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GraphQLBatchResponse<'a> {
|
||||||
|
fn is_ok(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&GraphQLBatchResponse::Single(ref response) => response.is_ok(),
|
||||||
|
&GraphQLBatchResponse::Batch(ref responses) => responses.iter().fold(true, |ok, response| ok && response.is_ok()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Simple wrapper around an incoming GraphQL request
|
/// Simple wrapper around an incoming GraphQL request
|
||||||
///
|
///
|
||||||
/// See the `http` module for more information. This type can be constructed
|
/// See the `http` module for more information. This type can be constructed
|
||||||
/// automatically from both GET and POST routes by implementing the `FromForm`
|
/// automatically from both GET and POST routes by implementing the `FromForm`
|
||||||
/// and `FromData` traits.
|
/// and `FromData` traits.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct GraphQLRequest(http::GraphQLRequest);
|
pub struct GraphQLRequest(GraphQLBatchRequest);
|
||||||
|
|
||||||
/// Simple wrapper around the result of executing a GraphQL query
|
/// Simple wrapper around the result of executing a GraphQL query
|
||||||
pub struct GraphQLResponse(Status, String);
|
pub struct GraphQLResponse(Status, String);
|
||||||
|
@ -209,11 +253,11 @@ impl<'f> FromForm<'f> for GraphQLRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(query) = query {
|
if let Some(query) = query {
|
||||||
Ok(GraphQLRequest(http::GraphQLRequest::new(
|
Ok(GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
|
||||||
query,
|
query,
|
||||||
operation_name,
|
operation_name,
|
||||||
variables,
|
variables,
|
||||||
)))
|
))))
|
||||||
} else {
|
} else {
|
||||||
Err("Query parameter missing".to_owned())
|
Err("Query parameter missing".to_owned())
|
||||||
}
|
}
|
||||||
|
@ -324,11 +368,11 @@ mod fromform_tests {
|
||||||
let result = GraphQLRequest::from_form(&mut items, false);
|
let result = GraphQLRequest::from_form(&mut items, false);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
let variables = ::serde_json::from_str::<InputValue>(r#"{"foo":"bar"}"#).unwrap();
|
let variables = ::serde_json::from_str::<InputValue>(r#"{"foo":"bar"}"#).unwrap();
|
||||||
let expected = GraphQLRequest(http::GraphQLRequest::new(
|
let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
|
||||||
"test".to_string(),
|
"test".to_string(),
|
||||||
None,
|
None,
|
||||||
Some(variables),
|
Some(variables),
|
||||||
));
|
)));
|
||||||
assert_eq!(result.unwrap(), expected);
|
assert_eq!(result.unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,11 +383,11 @@ mod fromform_tests {
|
||||||
let result = GraphQLRequest::from_form(&mut items, false);
|
let result = GraphQLRequest::from_form(&mut items, false);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
let variables = ::serde_json::from_str::<InputValue>(r#"{"foo":"x y&? z"}"#).unwrap();
|
let variables = ::serde_json::from_str::<InputValue>(r#"{"foo":"x y&? z"}"#).unwrap();
|
||||||
let expected = GraphQLRequest(http::GraphQLRequest::new(
|
let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
|
||||||
"test".to_string(),
|
"test".to_string(),
|
||||||
None,
|
None,
|
||||||
Some(variables),
|
Some(variables),
|
||||||
));
|
)));
|
||||||
assert_eq!(result.unwrap(), expected);
|
assert_eq!(result.unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,11 +397,11 @@ mod fromform_tests {
|
||||||
let mut items = FormItems::from(form_string);
|
let mut items = FormItems::from(form_string);
|
||||||
let result = GraphQLRequest::from_form(&mut items, false);
|
let result = GraphQLRequest::from_form(&mut items, false);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
let expected = GraphQLRequest(http::GraphQLRequest::new(
|
let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
|
||||||
"%foo bar baz&?".to_string(),
|
"%foo bar baz&?".to_string(),
|
||||||
Some("test".to_string()),
|
Some("test".to_string()),
|
||||||
None,
|
None,
|
||||||
));
|
)));
|
||||||
assert_eq!(result.unwrap(), expected);
|
assert_eq!(result.unwrap(), expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue