From e82534acedca596dd768a2747cdb0fcff1d3b06b Mon Sep 17 00:00:00 2001
From: Magnus Hallin <mhallin@fastmail.com>
Date: Wed, 14 Jun 2017 15:50:27 +0200
Subject: [PATCH] Initial work on Rocket integration

---
 Cargo.toml                          | 11 ++++-
 examples/rocket-server.rs           | 37 +++++++++++++++
 src/integrations/mod.rs             |  1 +
 src/integrations/rocket_handlers.rs | 74 +++++++++++++++++++++++++++++
 src/lib.rs                          |  8 +++-
 5 files changed, 129 insertions(+), 2 deletions(-)
 create mode 100644 examples/rocket-server.rs
 create mode 100644 src/integrations/rocket_handlers.rs

diff --git a/Cargo.toml b/Cargo.toml
index 35b47f18..c8bef915 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,19 +22,28 @@ path = "benches/bench.rs"
 name = "server"
 required-features = ["iron-handlers", "expose-test-schema"]
 
+[[example]]
+name = "rocket-server"
+required-features = ["rocket-handlers", "expose-test-schema"]
+
 [features]
 nightly = []
 iron-handlers = ["iron", "serde_json", "urlencoded"]
+rocket-handlers = ["rocket", "rocket_codegen", "serde_json"]
 expose-test-schema = []
 
 [dependencies]
 serde = { version = "^1.0.8" }
 serde_derive = {version="^1.0.8" }
 
-iron = { version = "^0.5.1", optional = true }
 serde_json = { version = "^1.0.2", optional = true }
+
+iron = { version = "^0.5.1", optional = true }
 urlencoded = { version = "^0.5.0", optional = true }
 
+rocket = { version = "^0.2.8", optional = true }
+rocket_codegen = { version = "^0.2.8", optional = true }
+
 [dev-dependencies]
 iron = "^0.5.1"
 router = "^0.5.0"
diff --git a/examples/rocket-server.rs b/examples/rocket-server.rs
new file mode 100644
index 00000000..c5b35d2d
--- /dev/null
+++ b/examples/rocket-server.rs
@@ -0,0 +1,37 @@
+#![feature(plugin)]
+#![plugin(rocket_codegen)]
+
+extern crate rocket;
+extern crate juniper;
+
+use rocket::response::content;
+use rocket::State;
+
+use juniper::tests::model::Database;
+use juniper::{EmptyMutation, RootNode};
+
+use juniper::rocket_handlers;
+
+type Schema = RootNode<'static, Database, EmptyMutation<Database>>;
+
+#[get("/")]
+fn graphiql() -> content::HTML<String> {
+    rocket_handlers::graphiql_source("/graphql")
+}
+
+#[post("/graphql", data="<request>")]
+fn post_graphql_handler(
+    context: State<Database>,
+    request: rocket_handlers::GraphQLRequest,
+    schema: State<Schema>
+) -> rocket_handlers::GraphQLResponse {
+    request.execute(&schema, &context)
+}
+
+fn main() {
+    rocket::ignite()
+        .manage(Database::new())
+        .manage(Schema::new(Database::new(), EmptyMutation::<Database>::new()))
+        .mount("/", routes![graphiql, post_graphql_handler])
+        .launch();
+}
\ No newline at end of file
diff --git a/src/integrations/mod.rs b/src/integrations/mod.rs
index 02768f18..42a4fb7b 100644
--- a/src/integrations/mod.rs
+++ b/src/integrations/mod.rs
@@ -1,2 +1,3 @@
 #[cfg(feature="iron-handlers")] pub mod iron_handlers;
+#[cfg(feature="rocket-handlers")] pub mod rocket_handlers;
 pub mod serde;
diff --git a/src/integrations/rocket_handlers.rs b/src/integrations/rocket_handlers.rs
new file mode 100644
index 00000000..c8573d03
--- /dev/null
+++ b/src/integrations/rocket_handlers.rs
@@ -0,0 +1,74 @@
+use std::io::{Cursor, Read};
+
+use serde_json;
+
+use rocket::Request;
+use rocket::data::{FromData, Outcome};
+use rocket::response::{Responder, Response, content};
+use rocket::http::{ContentType, Status};
+use rocket::Data;
+use rocket::Outcome::{Forward, Failure, Success};
+
+use ::http;
+
+use types::base::GraphQLType;
+use schema::model::RootNode;
+
+pub struct GraphQLResponse(Status, String);
+pub struct GraphQLRequest(http::GraphQLRequest);
+
+pub fn graphiql_source(graphql_endpoint_url: &str) -> content::HTML<String> {
+    content::HTML(::graphiql::graphiql_source(graphql_endpoint_url))
+}
+
+impl GraphQLRequest {
+    pub fn execute<CtxT, QueryT, MutationT>(
+        &self,
+        root_node: &RootNode<QueryT, MutationT>,
+        context: &CtxT,
+    )
+        -> GraphQLResponse
+        where QueryT: GraphQLType<Context=CtxT>,
+            MutationT: GraphQLType<Context=CtxT>,
+    {
+        let response = self.0.execute(root_node, context);
+        let status = if response.is_ok() { Status::Ok } else { Status::BadRequest };
+        let json = serde_json::to_string_pretty(&response).unwrap();
+
+        GraphQLResponse(status, json)
+    }
+}
+
+impl FromData for GraphQLRequest {
+    type Error = String;
+
+    fn from_data(request: &Request, data: Data) -> Outcome<Self, String> {
+        if !request.content_type().map_or(false, |ct| ct.is_json()) {
+            return Forward(data);
+        }
+
+        let mut body = String::new();
+        if let Err(e) = data.open().read_to_string(&mut body) {
+            return Failure((Status::InternalServerError, format!("{:?}", e)));
+        }
+
+        match serde_json::from_str(&body) {
+            Ok(value) => Success(GraphQLRequest(value)),
+            Err(failure) => return Failure(
+                (Status::BadRequest, format!("{}", failure)),
+            ),
+        }
+    }
+}
+
+impl<'r> Responder<'r> for GraphQLResponse {
+    fn respond(self) -> Result<Response<'r>, Status> {
+        let GraphQLResponse(status, body) = self;
+
+        Ok(Response::build()
+            .header(ContentType::new("application", "json"))
+            .status(status)
+            .sized_body(Cursor::new(body))
+            .finalize())
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index c32a7bcb..156fea00 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -185,6 +185,11 @@ built-in [GraphiQL][6] handler included.
 
 #![cfg_attr(feature="nightly", feature(test))]
 #![warn(missing_docs)]
+
+#![cfg_attr(feature="rocket-handlers", feature(plugin))]
+#![cfg_attr(feature="rocket-handlers", plugin(rocket_codegen))]
+#[cfg(feature="rocket-handlers")] extern crate rocket;
+
 #[cfg(feature="nightly")] extern crate test;
 #[cfg(feature="iron-handlers")] #[macro_use(itry)] extern crate iron;
 #[cfg(feature="iron-handlers")] extern crate urlencoded;
@@ -192,7 +197,7 @@ built-in [GraphiQL][6] handler included.
 extern crate serde;
 #[macro_use] extern crate serde_derive;
 
-#[cfg(feature="iron-handlers")] extern crate serde_json;
+#[cfg(any(feature="iron-handlers", feature="rocket-handlers"))] extern crate serde_json;
 
 #[macro_use] mod macros;
 mod ast;
@@ -232,6 +237,7 @@ pub use result_ext::ResultExt;
 pub use schema::meta;
 
 #[cfg(feature="iron-handlers")] pub use integrations::iron_handlers;
+#[cfg(feature="rocket-handlers")] pub use integrations::rocket_handlers;
 
 /// An error that prevented query execution
 #[derive(Debug, PartialEq)]