Skip to content

Handle an array of GraphQL queries #171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion juniper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ juniper_codegen = { version = "0.9.2", path = "../juniper_codegen" }
fnv = "1.0.3"
indexmap = { version = "1.0.0", features = ["serde-1"] }
serde = { version = "1.0.8" }
serde_derive = {version="1.0.2" }
serde_derive = { version = "1.0.2" }

chrono = { version = "0.4.0", optional = true }
serde_json = { version="1.0.2", optional = true }
Expand Down
16 changes: 16 additions & 0 deletions juniper/src/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ pub mod tests {

println!(" - test_simple_post");
test_simple_post(integration);

println!(" - test_batched_post");
test_batched_post(integration);
}

fn unwrap_json_response(response: &TestResponse) -> Json {
Expand Down Expand Up @@ -252,4 +255,17 @@ pub mod tests {
.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")
);
}
}
1 change: 1 addition & 0 deletions juniper_iron/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ repository = "https://github.com/graphql-rust/juniper"
[dependencies]
serde = { version = "1.0.2" }
serde_json = { version = "1.0.2" }
serde_derive = { version = "1.0.2" }
juniper = { version = "0.9.2", path = "../juniper" }

urlencoded = { version = ">= 0.5, < 0.7" }
Expand Down
56 changes: 50 additions & 6 deletions juniper_iron/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ extern crate iron;
extern crate iron_test;
extern crate juniper;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
extern crate urlencoded;

use iron::prelude::*;
Expand All @@ -125,6 +127,48 @@ use serde_json::error::Error as SerdeError;
use juniper::{GraphQLType, InputValue, RootNode};
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
///
/// The handler responds to GET requests and POST requests only. In GET
Expand Down Expand Up @@ -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>()
.map_err(GraphQLIronError::Url)?;

Expand All @@ -208,24 +252,24 @@ where
let operation_name = parse_url_param(url_query_string.remove("operationName"))?;
let variables = parse_variable_param(url_query_string.remove("variables"))?;

Ok(http::GraphQLRequest::new(
Ok(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
input_query,
operation_name,
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();
itry!(req.body.read_to_string(&mut request_payload));

Ok(
serde_json::from_str::<http::GraphQLRequest>(request_payload.as_str())
serde_json::from_str::<GraphQLBatchRequest>(request_payload.as_str())
.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 content_type = "application/json".parse::<Mime>().unwrap();
let json = serde_json::to_string_pretty(&response).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion juniper_rocket/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ repository = "https://github.com/graphql-rust/juniper"

[dependencies]
serde = { version = "1.0.2" }
serde_derive = {version="1.0.2" }
serde_json = { version = "1.0.2" }
serde_derive = { version = "1.0.2" }
juniper = { version = "0.9.2" , path = "../juniper"}

rocket = { version = "0.3.9" }
Expand Down
62 changes: 53 additions & 9 deletions juniper_rocket/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Check the LICENSE file for details.
extern crate juniper;
extern crate rocket;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;

use std::io::{Cursor, Read};
use std::error::Error;
Expand All @@ -60,13 +62,55 @@ use juniper::http;
use juniper::GraphQLType;
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
///
/// See the `http` module for more information. This type can be constructed
/// automatically from both GET and POST routes by implementing the `FromForm`
/// and `FromData` traits.
#[derive(Debug, PartialEq)]
pub struct GraphQLRequest(http::GraphQLRequest);
pub struct GraphQLRequest(GraphQLBatchRequest);

/// Simple wrapper around the result of executing a GraphQL query
pub struct GraphQLResponse(Status, String);
Expand Down Expand Up @@ -154,11 +198,11 @@ impl<'f> FromForm<'f> for GraphQLRequest {
}

if let Some(query) = query {
Ok(GraphQLRequest(http::GraphQLRequest::new(
Ok(GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
query,
operation_name,
variables,
)))
))))
} else {
Err("Query parameter missing".to_owned())
}
Expand Down Expand Up @@ -269,11 +313,11 @@ mod fromform_tests {
let result = GraphQLRequest::from_form(&mut items, false);
assert!(result.is_ok());
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(),
None,
Some(variables),
));
)));
assert_eq!(result.unwrap(), expected);
}

Expand All @@ -284,11 +328,11 @@ mod fromform_tests {
let result = GraphQLRequest::from_form(&mut items, false);
assert!(result.is_ok());
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(),
None,
Some(variables),
));
)));
assert_eq!(result.unwrap(), expected);
}

Expand All @@ -298,11 +342,11 @@ mod fromform_tests {
let mut items = FormItems::from(form_string);
let result = GraphQLRequest::from_form(&mut items, false);
assert!(result.is_ok());
let expected = GraphQLRequest(http::GraphQLRequest::new(
let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
"%foo bar baz&?".to_string(),
Some("test".to_string()),
None,
));
)));
assert_eq!(result.unwrap(), expected);
}
}
Expand Down