Handle an array of GraphQL queries
This commit is contained in:
parent
3b445f0577
commit
658cb29f60
3 changed files with 135 additions and 22 deletions
|
@ -9,6 +9,28 @@ use {GraphQLError, GraphQLType, RootNode, Value, Variables};
|
|||
use ast::InputValue;
|
||||
use executor::ExecutionError;
|
||||
|
||||
/// The result of executing a query
|
||||
pub trait ExecutionResponse: ser::Serialize {
|
||||
/// Was the request successful or not?
|
||||
fn is_ok(&self) -> bool;
|
||||
}
|
||||
|
||||
/// A request that can be executed
|
||||
pub trait Executable {
|
||||
/// The response that is produced by executing this request
|
||||
type Response: ExecutionResponse;
|
||||
|
||||
/// Executes this request against the provided schema
|
||||
fn execute<CtxT, QueryT, MutationT>(
|
||||
self,
|
||||
root_node: &RootNode<QueryT, MutationT>,
|
||||
context: &CtxT,
|
||||
) -> Self::Response
|
||||
where
|
||||
QueryT: GraphQLType<Context = CtxT>,
|
||||
MutationT: GraphQLType<Context = CtxT>;
|
||||
}
|
||||
|
||||
/// The expected structure of the decoded JSON document for either POST or GET requests.
|
||||
///
|
||||
/// For POST, you can use Serde to deserialize the incoming JSON data directly
|
||||
|
@ -53,16 +75,20 @@ impl GraphQLRequest {
|
|||
variables: variables,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Executable for &'a GraphQLRequest {
|
||||
type Response = GraphQLResponse<'a>;
|
||||
|
||||
/// Execute a GraphQL request using the specified schema and context
|
||||
///
|
||||
/// This is a simple wrapper around the `execute` function exposed at the
|
||||
/// top level of this crate.
|
||||
pub fn execute<'a, CtxT, QueryT, MutationT>(
|
||||
&'a self,
|
||||
fn execute<CtxT, QueryT, MutationT>(
|
||||
self,
|
||||
root_node: &RootNode<QueryT, MutationT>,
|
||||
context: &CtxT,
|
||||
) -> GraphQLResponse<'a>
|
||||
) -> Self::Response
|
||||
where
|
||||
QueryT: GraphQLType<Context = CtxT>,
|
||||
MutationT: GraphQLType<Context = CtxT>,
|
||||
|
@ -77,6 +103,45 @@ impl GraphQLRequest {
|
|||
}
|
||||
}
|
||||
|
||||
/// Wraps the GraphQLRequest allowing for an array of requests to be handled in one request.
|
||||
#[derive(Deserialize, Clone, Serialize, PartialEq, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum GraphQLBatchRequest {
|
||||
/// A single GraphQLRequest
|
||||
Single(GraphQLRequest),
|
||||
/// Multiple GraphQLRequests to be handled at once
|
||||
Batch(Vec<GraphQLRequest>),
|
||||
}
|
||||
|
||||
impl<'a> Executable for &'a GraphQLBatchRequest {
|
||||
type Response = GraphQLBatchResponse<'a>;
|
||||
|
||||
/// Execute all contained GraphQLRequests
|
||||
fn execute<CtxT, QueryT, MutationT>(
|
||||
self,
|
||||
root_node: &RootNode<QueryT, MutationT>,
|
||||
context: &CtxT,
|
||||
) -> Self::Response
|
||||
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
|
||||
.into_iter()
|
||||
.map(|req| req.execute(root_node, context))
|
||||
.collect()
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple wrapper around the result from executing a GraphQL query
|
||||
///
|
||||
/// This struct implements Serialize, so you can simply serialize this
|
||||
|
@ -84,12 +149,12 @@ impl GraphQLRequest {
|
|||
/// whether to send a 200 or 400 HTTP status code.
|
||||
pub struct GraphQLResponse<'a>(Result<(Value, Vec<ExecutionError>), GraphQLError<'a>>);
|
||||
|
||||
impl<'a> GraphQLResponse<'a> {
|
||||
impl<'a> ExecutionResponse for GraphQLResponse<'a> {
|
||||
/// Was the request successful or not?
|
||||
///
|
||||
/// Note that there still might be errors in the response even though it's
|
||||
/// considered OK. This is by design in GraphQL.
|
||||
pub fn is_ok(&self) -> bool {
|
||||
fn is_ok(&self) -> bool {
|
||||
self.0.is_ok()
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +188,38 @@ impl<'a> ser::Serialize for GraphQLResponse<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Wraps the GraphQLResponse so that multiple responses can be returned for a batched request.
|
||||
pub enum GraphQLBatchResponse<'a> {
|
||||
/// A single GraphQLResponse
|
||||
Single(GraphQLResponse<'a>),
|
||||
/// Multiple GraphQLResponses that were handled at once
|
||||
Batch(Vec<GraphQLResponse<'a>>),
|
||||
}
|
||||
|
||||
impl<'a> ExecutionResponse for GraphQLBatchResponse<'a> {
|
||||
/// Was the request successful or not?
|
||||
///
|
||||
/// Requires that all the batched responses are ok.
|
||||
fn is_ok(&self) -> bool {
|
||||
match self {
|
||||
&GraphQLBatchResponse::Single(ref response) => response.is_ok(),
|
||||
&GraphQLBatchResponse::Batch(ref batch) => batch.iter().fold(true, |ok, res| ok && res.is_ok()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ser::Serialize for GraphQLBatchResponse<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: ser::Serializer,
|
||||
{
|
||||
match self {
|
||||
&GraphQLBatchResponse::Single(ref response) => response.serialize(serializer),
|
||||
&GraphQLBatchResponse::Batch(ref batch) => batch.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "expose-test-schema"))]
|
||||
#[allow(missing_docs)]
|
||||
pub mod tests {
|
||||
|
@ -159,6 +256,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 {
|
||||
|
@ -251,4 +351,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")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ use std::fmt;
|
|||
use serde_json::error::Error as SerdeError;
|
||||
|
||||
use juniper::{GraphQLType, InputValue, RootNode};
|
||||
use juniper::http;
|
||||
use juniper::http::{self, ExecutionResponse, Executable};
|
||||
|
||||
/// Handler that executes GraphQL queries in the given schema
|
||||
///
|
||||
|
@ -199,7 +199,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_get(&self, req: &mut Request) -> IronResult<http::GraphQLRequest> {
|
||||
fn handle_get(&self, req: &mut Request) -> IronResult<http::GraphQLBatchRequest> {
|
||||
let url_query_string = req.get_mut::<UrlEncodedQuery>()
|
||||
.map_err(|e| GraphQLIronError::Url(e))?;
|
||||
|
||||
|
@ -208,24 +208,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(http::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<http::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::<http::GraphQLBatchRequest>(request_payload.as_str())
|
||||
.map_err(|err| GraphQLIronError::Serde(err))?,
|
||||
)
|
||||
}
|
||||
|
||||
fn execute(&self, context: &CtxT, request: http::GraphQLRequest) -> IronResult<Response> {
|
||||
fn execute(&self, context: &CtxT, request: http::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();
|
||||
|
|
|
@ -55,7 +55,7 @@ use rocket::Data;
|
|||
use rocket::Outcome::{Failure, Forward, Success};
|
||||
|
||||
use juniper::InputValue;
|
||||
use juniper::http;
|
||||
use juniper::http::{self, Executable, ExecutionResponse};
|
||||
|
||||
use juniper::GraphQLType;
|
||||
use juniper::RootNode;
|
||||
|
@ -66,7 +66,7 @@ use juniper::RootNode;
|
|||
/// 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(http::GraphQLBatchRequest);
|
||||
|
||||
/// Simple wrapper around the result of executing a GraphQL query
|
||||
pub struct GraphQLResponse(Status, String);
|
||||
|
@ -154,11 +154,11 @@ impl<'f> FromForm<'f> for GraphQLRequest {
|
|||
}
|
||||
|
||||
if let Some(query) = query {
|
||||
Ok(GraphQLRequest(http::GraphQLRequest::new(
|
||||
Ok(GraphQLRequest(http::GraphQLBatchRequest::Single(http::GraphQLRequest::new(
|
||||
query,
|
||||
operation_name,
|
||||
variables,
|
||||
)))
|
||||
))))
|
||||
} else {
|
||||
Err("Query parameter missing".to_owned())
|
||||
}
|
||||
|
@ -269,11 +269,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(http::GraphQLBatchRequest::Single(http::GraphQLRequest::new(
|
||||
"test".to_string(),
|
||||
None,
|
||||
Some(variables),
|
||||
));
|
||||
)));
|
||||
assert_eq!(result.unwrap(), expected);
|
||||
}
|
||||
|
||||
|
@ -284,11 +284,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(http::GraphQLBatchRequest::Single(http::GraphQLRequest::new(
|
||||
"test".to_string(),
|
||||
None,
|
||||
Some(variables),
|
||||
));
|
||||
)));
|
||||
assert_eq!(result.unwrap(), expected);
|
||||
}
|
||||
|
||||
|
@ -298,11 +298,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(http::GraphQLBatchRequest::Single(http::GraphQLRequest::new(
|
||||
"%foo bar baz&?".to_string(),
|
||||
Some("test".to_string()),
|
||||
None,
|
||||
));
|
||||
)));
|
||||
assert_eq!(result.unwrap(), expected);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue