From 93deb2862a5c19616ca556db0cfe961af0093977 Mon Sep 17 00:00:00 2001
From: Christian Legnitto <LegNeato@users.noreply.github.com>
Date: Wed, 7 Jul 2021 06:26:22 -1000
Subject: [PATCH] Make juniper_rocket_async replace juniper_rocket (#955)

* Make juniper_rocket_async replace juniper_rocket

Now that rocket v0.5 is async and in rc, merge the two juniper projects.

Fixes https://github.com/graphql-rust/juniper/issues/947.

* Remove println
---
 Cargo.toml                                    |   1 -
 Makefile.toml                                 |   8 +-
 juniper/release.toml                          |   3 -
 juniper_rocket/CHANGELOG.md                   |   1 +
 juniper_rocket/Cargo.toml                     |   5 +-
 juniper_rocket/Makefile.toml                  |  17 -
 juniper_rocket/examples/rocket_server.rs      |  26 +-
 juniper_rocket/src/lib.rs                     | 461 ++++++++-----
 juniper_rocket/tests/custom_response_tests.rs |   3 +-
 juniper_rocket_async/.gitignore               |   2 -
 juniper_rocket_async/CHANGELOG.md             |  62 --
 juniper_rocket_async/Cargo.toml               |  21 -
 juniper_rocket_async/LICENSE                  |  25 -
 juniper_rocket_async/README.md                |  35 -
 .../examples/rocket_server.rs                 |  48 --
 juniper_rocket_async/src/lib.rs               | 643 ------------------
 .../tests/custom_response_tests.rs            |   7 -
 17 files changed, 303 insertions(+), 1065 deletions(-)
 delete mode 100644 juniper_rocket/Makefile.toml
 delete mode 100644 juniper_rocket_async/.gitignore
 delete mode 100644 juniper_rocket_async/CHANGELOG.md
 delete mode 100644 juniper_rocket_async/Cargo.toml
 delete mode 100644 juniper_rocket_async/LICENSE
 delete mode 100644 juniper_rocket_async/README.md
 delete mode 100644 juniper_rocket_async/examples/rocket_server.rs
 delete mode 100644 juniper_rocket_async/src/lib.rs
 delete mode 100644 juniper_rocket_async/tests/custom_response_tests.rs

diff --git a/Cargo.toml b/Cargo.toml
index ddb54b5b..a64f814f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,6 @@ members = [
   "juniper_hyper",
   "juniper_iron",
   "juniper_rocket",
-  "juniper_rocket_async",
   "juniper_subscriptions",
   "juniper_graphql_ws",
   "juniper_warp",
diff --git a/Makefile.toml b/Makefile.toml
index 36d6e955..f6c96e19 100644
--- a/Makefile.toml
+++ b/Makefile.toml
@@ -12,7 +12,7 @@ CARGO_MAKE_CARGO_ALL_FEATURES = ""
 [tasks.release]
 condition = { env_set = [ "RELEASE_LEVEL" ] }
 workspace = false
-env = { "CARGO_MAKE_WORKSPACE_SKIP_MEMBERS" = "integration_tests/*;examples/*;juniper_benchmarks;juniper_rocket_async" }
+env = { "CARGO_MAKE_WORKSPACE_SKIP_MEMBERS" = "integration_tests/*;examples/*;juniper_benchmarks;" }
 run_task = { name = "release-INTERNAL", fork = true }
 
 
@@ -39,7 +39,7 @@ args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/../_build/releas
 [tasks.release-dry-run]
 condition = { env_set = [ "RELEASE_LEVEL" ] }
 workspace = false
-env = { "CARGO_MAKE_WORKSPACE_SKIP_MEMBERS" = "integration_tests/*;examples/*;juniper_benchmarks;juniper_rocket_async" }
+env = { "CARGO_MAKE_WORKSPACE_SKIP_MEMBERS" = "integration_tests/*;examples/*;juniper_benchmarks;" }
 run_task = { name = "release-dry-run-INTERNAL", fork = true }
 
 [tasks.release-some-dry-run]
@@ -53,7 +53,7 @@ run_task = { name = "release-some-dry-run-INTERNAL", fork = true }
 [tasks.release-dry-run-INTERNAL]
 private = true
 condition = { env_set = [ "RELEASE_LEVEL" ] }
-env = { "CARGO_MAKE_WORKSPACE_SKIP_MEMBERS" = "integration_tests/*;examples/*;juniper_benchmarks;juniper_rocket_async" }
+env = { "CARGO_MAKE_WORKSPACE_SKIP_MEMBERS" = "integration_tests/*;examples/*;juniper_benchmarks;" }
 description = "Run `cargo-release --dry-run` for every crate"
 command = "cargo-release"
 args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/../_build/release.toml", "--dry-run", "${RELEASE_LEVEL}"]
@@ -68,7 +68,7 @@ args = ["release", "--config", "${CARGO_MAKE_WORKING_DIRECTORY}/../_build/releas
 [tasks.release-local-test]
 condition = { env_set = [ "RELEASE_LEVEL" ] }
 workspace = false
-env = { "CARGO_MAKE_WORKSPACE_SKIP_MEMBERS" = "integration_tests/*;examples/*;juniper_benchmarks;juniper_rocket_async" }
+env = { "CARGO_MAKE_WORKSPACE_SKIP_MEMBERS" = "integration_tests/*;examples/*;juniper_benchmarks;" }
 run_task = { name = "release-local-test-INTERNAL", fork = true }
 
 
diff --git a/juniper/release.toml b/juniper/release.toml
index 8779c9b4..7831d4e2 100644
--- a/juniper/release.toml
+++ b/juniper/release.toml
@@ -21,9 +21,6 @@ pre-release-replacements = [
   # Rocket
   {file="../juniper_rocket/Cargo.toml", min=0, search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""},
   {file="../juniper_rocket/Cargo.toml", min=0, search="\\[dev-dependencies\\.juniper\\]\nversion = \"[^\"]+\"", replace="[dev-dependencies.juniper]\nversion = \"{{version}}\""},
-  # Rocket Async
-  {file="../juniper_rocket_async/Cargo.toml", min=0, search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""},
-  {file="../juniper_rocket_async/Cargo.toml", min=0, search="\\[dev-dependencies\\.juniper\\]\nversion = \"[^\"]+\"", replace="[dev-dependencies.juniper]\nversion = \"{{version}}\""},
   # Warp
   {file="../juniper_warp/Cargo.toml", min=0, search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""},
   {file="../juniper_warp/Cargo.toml", min=0, search="\\[dev-dependencies\\.juniper\\]\nversion = \"[^\"]+\"", replace="[dev-dependencies.juniper]\nversion = \"{{version}}\""},
diff --git a/juniper_rocket/CHANGELOG.md b/juniper_rocket/CHANGELOG.md
index fdc8e016..e9005978 100644
--- a/juniper_rocket/CHANGELOG.md
+++ b/juniper_rocket/CHANGELOG.md
@@ -1,5 +1,6 @@
 # master
 
+- Require async rocket support (`rocket` >= 0.5-rc1).
 - Compatibility with the latest `juniper`.
 
 # [[0.7.1] 2021-06-07](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.7.1)
diff --git a/juniper_rocket/Cargo.toml b/juniper_rocket/Cargo.toml
index a8da2c45..41af3490 100644
--- a/juniper_rocket/Cargo.toml
+++ b/juniper_rocket/Cargo.toml
@@ -12,8 +12,9 @@ documentation = "https://docs.rs/juniper_rocket"
 repository = "https://github.com/graphql-rust/juniper"
 
 [dependencies]
-juniper = { version = "0.15.6", path = "../juniper", default-features = false}
-rocket = { version = "0.4.10", default-features = false }
+futures = "0.3.1"
+juniper = { version = "0.15.6", path = "../juniper", default-features = false }
+rocket = { version = "0.5.0-rc.1", default-features = false }
 serde_json = "1.0.2"
 
 [dev-dependencies]
diff --git a/juniper_rocket/Makefile.toml b/juniper_rocket/Makefile.toml
deleted file mode 100644
index 8695d6a6..00000000
--- a/juniper_rocket/Makefile.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[tasks.build-verbose]
-condition = { channels = ["nightly"] }
-
-[tasks.build-verbose.windows]
-condition = { channels = ["nightly"], env = { "TARGET" = "x86_64-pc-windows-msvc" } }
-
-[tasks.test-verbose]
-condition = { channels = ["nightly"] }
-
-[tasks.test-verbose.windows]
-condition = { channels = ["nightly"], env = { "TARGET" = "x86_64-pc-windows-msvc" } }
-
-[tasks.ci-coverage-flow]
-condition = { channels = ["nightly"] }
-
-[tasks.ci-coverage-flow.windows]
-disabled = true
\ No newline at end of file
diff --git a/juniper_rocket/examples/rocket_server.rs b/juniper_rocket/examples/rocket_server.rs
index 0a55adad..76a6b296 100644
--- a/juniper_rocket/examples/rocket_server.rs
+++ b/juniper_rocket/examples/rocket_server.rs
@@ -1,11 +1,8 @@
-#![feature(decl_macro, proc_macro_hygiene)]
-
-use rocket::{response::content, State};
-
 use juniper::{
     tests::fixtures::starwars::schema::{Database, Query},
     EmptyMutation, EmptySubscription, RootNode,
 };
+use rocket::{response::content, Rocket, State};
 
 type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
 
@@ -16,24 +13,25 @@ fn graphiql() -> content::Html<String> {
 
 #[rocket::get("/graphql?<request>")]
 fn get_graphql_handler(
-    context: State<Database>,
+    context: &State<Database>,
     request: juniper_rocket::GraphQLRequest,
-    schema: State<Schema>,
+    schema: &State<Schema>,
 ) -> juniper_rocket::GraphQLResponse {
-    request.execute_sync(&schema, &context)
+    request.execute_sync(&*schema, &*context)
 }
 
 #[rocket::post("/graphql", data = "<request>")]
 fn post_graphql_handler(
-    context: State<Database>,
+    context: &State<Database>,
     request: juniper_rocket::GraphQLRequest,
-    schema: State<Schema>,
+    schema: &State<Schema>,
 ) -> juniper_rocket::GraphQLResponse {
-    request.execute_sync(&schema, &context)
+    request.execute_sync(&*schema, &*context)
 }
 
-fn main() {
-    rocket::ignite()
+#[rocket::main]
+async fn main() {
+    Rocket::build()
         .manage(Database::new())
         .manage(Schema::new(
             Query,
@@ -44,5 +42,7 @@ fn main() {
             "/",
             rocket::routes![graphiql, get_graphql_handler, post_graphql_handler],
         )
-        .launch();
+        .launch()
+        .await
+        .expect("server to launch");
 }
diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs
index e623480c..2ab7898d 100644
--- a/juniper_rocket/src/lib.rs
+++ b/juniper_rocket/src/lib.rs
@@ -36,23 +36,23 @@ Check the LICENSE file for details.
 
 */
 
-#![doc(html_root_url = "https://docs.rs/juniper_rocket/0.2.0")]
-#![feature(decl_macro, proc_macro_hygiene)]
+#![doc(html_root_url = "https://docs.rs/juniper_rocket/0.7.1")]
 
-use std::io::{Cursor, Read};
+use std::{borrow::Cow, io::Cursor};
+
+use rocket::{
+    data::{self, FromData, ToByteUnit},
+    form::{error::ErrorKind, DataField, Error, Errors, FromForm, Options, ValueField},
+    http::{ContentType, Status},
+    outcome::Outcome::{Failure, Forward, Success},
+    response::{self, content, Responder, Response},
+    Data, Request,
+};
 
 use juniper::{
     http::{self, GraphQLBatchRequest},
-    DefaultScalarValue, FieldError, GraphQLType, InputValue, RootNode, ScalarValue,
-};
-use rocket::{
-    data::{FromDataSimple, Outcome as FromDataOutcome},
-    http::{ContentType, RawStr, Status},
-    request::{FormItems, FromForm, FromFormValue},
-    response::{content, Responder, Response},
-    Data,
-    Outcome::{Forward, Success},
-    Request,
+    DefaultScalarValue, FieldError, GraphQLSubscriptionType, GraphQLType, GraphQLTypeAsync,
+    InputValue, RootNode, ScalarValue,
 };
 
 /// Simple wrapper around an incoming GraphQL request
@@ -71,22 +71,22 @@ pub struct GraphQLResponse(pub Status, pub String);
 /// Generate an HTML page containing GraphiQL
 pub fn graphiql_source(
     graphql_endpoint_url: &str,
-    subscriptions_endpoint: Option<&str>,
+    subscriptions_endpoint_url: Option<&str>,
 ) -> content::Html<String> {
     content::Html(juniper::http::graphiql::graphiql_source(
         graphql_endpoint_url,
-        subscriptions_endpoint,
+        subscriptions_endpoint_url,
     ))
 }
 
 /// Generate an HTML page containing GraphQL Playground
 pub fn playground_source(
     graphql_endpoint_url: &str,
-    subscriptions_endpoint: Option<&str>,
+    subscriptions_endpoint_url: Option<&str>,
 ) -> content::Html<String> {
     content::Html(juniper::http::playground::playground_source(
         graphql_endpoint_url,
-        subscriptions_endpoint,
+        subscriptions_endpoint_url,
     ))
 }
 
@@ -94,7 +94,7 @@ impl<S> GraphQLRequest<S>
 where
     S: ScalarValue,
 {
-    /// Execute an incoming GraphQL query
+    /// Synchronously execute an incoming GraphQL query.
     pub fn execute_sync<CtxT, QueryT, MutationT, SubscriptionT>(
         &self,
         root_node: &RootNode<QueryT, MutationT, SubscriptionT, S>,
@@ -116,6 +116,33 @@ where
         GraphQLResponse(status, json)
     }
 
+    /// Asynchronously execute an incoming GraphQL query.
+    pub async fn execute<CtxT, QueryT, MutationT, SubscriptionT>(
+        &self,
+        root_node: &RootNode<'_, QueryT, MutationT, SubscriptionT, S>,
+        context: &CtxT,
+    ) -> GraphQLResponse
+    where
+        QueryT: GraphQLTypeAsync<S, Context = CtxT>,
+        QueryT::TypeInfo: Sync,
+        MutationT: GraphQLTypeAsync<S, Context = CtxT>,
+        MutationT::TypeInfo: Sync,
+        SubscriptionT: GraphQLSubscriptionType<S, Context = CtxT>,
+        SubscriptionT::TypeInfo: Sync,
+        CtxT: Sync,
+        S: Send + Sync,
+    {
+        let response = self.0.execute(root_node, context).await;
+        let status = if response.is_ok() {
+            Status::Ok
+        } else {
+            Status::BadRequest
+        };
+        let json = serde_json::to_string(&response).unwrap();
+
+        GraphQLResponse(status, json)
+    }
+
     /// Returns the operation names associated with this request.
     ///
     /// For batch requests there will be multiple names.
@@ -130,10 +157,8 @@ impl GraphQLResponse {
     /// # Examples
     ///
     /// ```
-    /// # #![feature(decl_macro, proc_macro_hygiene)]
-    /// #
-    /// # use rocket::http::Cookies;
-    /// # use rocket::request::Form;
+    /// # use rocket::http::CookieJar;
+    /// # use rocket::form::Form;
     /// # use rocket::response::content;
     /// # use rocket::State;
     /// #
@@ -144,17 +169,17 @@ impl GraphQLResponse {
     /// #
     /// #[rocket::get("/graphql?<request..>")]
     /// fn get_graphql_handler(
-    ///     mut cookies: Cookies,
-    ///     context: State<Database>,
-    ///     request: Form<juniper_rocket::GraphQLRequest>,
-    ///     schema: State<Schema>,
+    ///     cookies: &CookieJar,
+    ///     context: &State<Database>,
+    ///     request: juniper_rocket::GraphQLRequest,
+    ///     schema: &State<Schema>,
     /// ) -> juniper_rocket::GraphQLResponse {
     ///     if cookies.get("user_id").is_none() {
     ///         let err = FieldError::new("User is not logged in", Value::null());
     ///         return juniper_rocket::GraphQLResponse::error(err);
     ///     }
     ///
-    ///     request.execute_sync(&schema, &context)
+    ///     request.execute_sync(&*schema, &*context)
     /// }
     /// ```
     pub fn error(error: FieldError) -> Self {
@@ -174,128 +199,172 @@ impl GraphQLResponse {
     }
 }
 
-impl<'f, S> FromForm<'f> for GraphQLRequest<S>
-where
-    S: ScalarValue,
-{
-    type Error = String;
+pub struct GraphQLContext<'f, S: ScalarValue> {
+    opts: Options,
+    query: Option<String>,
+    operation_name: Option<String>,
+    variables: Option<InputValue<S>>,
+    errors: Errors<'f>,
+}
 
-    fn from_form(form_items: &mut FormItems<'f>, strict: bool) -> Result<Self, String> {
-        let mut query = None;
-        let mut operation_name = None;
-        let mut variables = None;
+impl<'f, S: ScalarValue> GraphQLContext<'f, S> {
+    fn query(&mut self, value: String) {
+        if self.query.is_some() {
+            let error = Error::from(ErrorKind::Duplicate).with_name("query");
 
-        for form_item in form_items {
-            let (key, value) = form_item.key_value();
-            // Note: we explicitly decode in the match arms to save work rather
-            // than decoding every form item blindly.
-            match key.as_str() {
-                "query" => {
-                    if query.is_some() {
-                        return Err("Query parameter must not occur more than once".to_owned());
-                    } else {
-                        match value.url_decode() {
-                            Ok(v) => query = Some(v),
-                            Err(e) => return Err(e.to_string()),
-                        }
-                    }
-                }
-                "operation_name" => {
-                    if operation_name.is_some() {
-                        return Err(
-                            "Operation name parameter must not occur more than once".to_owned()
-                        );
-                    } else {
-                        match value.url_decode() {
-                            Ok(v) => operation_name = Some(v),
-                            Err(e) => return Err(e.to_string()),
-                        }
-                    }
-                }
-                "variables" => {
-                    if variables.is_some() {
-                        return Err("Variables parameter must not occur more than once".to_owned());
-                    } else {
-                        let decoded;
-                        match value.url_decode() {
-                            Ok(v) => decoded = v,
-                            Err(e) => return Err(e.to_string()),
-                        }
-                        variables = Some(
-                            serde_json::from_str::<InputValue<_>>(&decoded)
-                                .map_err(|err| err.to_string())?,
-                        );
-                    }
-                }
-                _ => {
-                    if strict {
-                        return Err(format!("Prohibited extra field '{}'", key));
-                    }
+            self.errors.push(error)
+        } else {
+            self.query = Some(value);
+        }
+    }
+
+    fn operation_name(&mut self, value: String) {
+        if self.operation_name.is_some() {
+            let error = Error::from(ErrorKind::Duplicate).with_name("operation_name");
+
+            self.errors.push(error)
+        } else {
+            self.operation_name = Some(value);
+        }
+    }
+
+    fn variables(&mut self, value: String) {
+        if self.variables.is_some() {
+            let error = Error::from(ErrorKind::Duplicate).with_name("variables");
+
+            self.errors.push(error)
+        } else {
+            let parse_result = serde_json::from_str::<InputValue<S>>(&value);
+
+            match parse_result {
+                Ok(variables) => self.variables = Some(variables),
+                Err(e) => {
+                    let error = Error::from(ErrorKind::Validation(Cow::Owned(e.to_string())))
+                        .with_name("variables");
+
+                    self.errors.push(error);
                 }
             }
         }
+    }
+}
 
-        if let Some(query) = query {
-            Ok(GraphQLRequest(GraphQLBatchRequest::Single(
-                http::GraphQLRequest::new(query, operation_name, variables),
-            )))
-        } else {
-            Err("Query parameter missing".to_owned())
+#[rocket::async_trait]
+impl<'f, S> FromForm<'f> for GraphQLRequest<S>
+where
+    S: ScalarValue + Send,
+{
+    type Context = GraphQLContext<'f, S>;
+
+    fn init(opts: Options) -> Self::Context {
+        GraphQLContext {
+            opts,
+            query: None,
+            operation_name: None,
+            variables: None,
+            errors: Errors::new(),
+        }
+    }
+
+    fn push_value(ctx: &mut Self::Context, field: ValueField<'f>) {
+        match field.name.key().map(|key| key.as_str()) {
+            Some("query") => ctx.query(field.value.to_owned()),
+            Some("operation_name") => ctx.operation_name(field.value.to_owned()),
+            Some("variables") => ctx.variables(field.value.to_owned()),
+            Some(key) => {
+                if ctx.opts.strict {
+                    let error = Error::from(ErrorKind::Unknown).with_name(key);
+
+                    ctx.errors.push(error)
+                }
+            }
+            None => {
+                if ctx.opts.strict {
+                    let error = Error::from(ErrorKind::Unexpected);
+
+                    ctx.errors.push(error)
+                }
+            }
+        }
+    }
+
+    async fn push_data(ctx: &mut Self::Context, field: DataField<'f, '_>) {
+        if ctx.opts.strict {
+            let error = Error::from(ErrorKind::Unexpected).with_name(field.name);
+
+            ctx.errors.push(error)
+        }
+    }
+
+    fn finalize(mut ctx: Self::Context) -> rocket::form::Result<'f, Self> {
+        if ctx.query.is_none() {
+            let error = Error::from(ErrorKind::Missing).with_name("query");
+
+            ctx.errors.push(error)
+        }
+
+        match ctx.errors.is_empty() {
+            true => Ok(GraphQLRequest(GraphQLBatchRequest::Single(
+                http::GraphQLRequest::new(ctx.query.unwrap(), ctx.operation_name, ctx.variables),
+            ))),
+            false => Err(ctx.errors),
         }
     }
 }
 
-impl<'v, S> FromFormValue<'v> for GraphQLRequest<S>
+const BODY_LIMIT: u64 = 1024 * 100;
+
+#[rocket::async_trait]
+impl<'r, S> FromData<'r> for GraphQLRequest<S>
 where
     S: ScalarValue,
 {
     type Error = String;
 
-    fn from_form_value(form_value: &'v RawStr) -> Result<Self, Self::Error> {
-        let mut form_items = FormItems::from(form_value);
+    async fn from_data(
+        req: &'r Request<'_>,
+        data: Data<'r>,
+    ) -> data::Outcome<'r, Self, Self::Error> {
+        use rocket::tokio::io::AsyncReadExt as _;
 
-        Self::from_form(&mut form_items, true)
-    }
-}
-
-impl<S> FromDataSimple for GraphQLRequest<S>
-where
-    S: ScalarValue,
-{
-    type Error = String;
-
-    fn from_data(req: &Request, data: Data) -> FromDataOutcome<Self, Self::Error> {
         let content_type = req
             .content_type()
             .map(|ct| (ct.top().as_str(), ct.sub().as_str()));
         let is_json = match content_type {
             Some(("application", "json")) => true,
             Some(("application", "graphql")) => false,
-            _ => return Forward(data),
+            _ => return Box::pin(async move { Forward(data) }).await,
         };
 
-        let mut body = String::new();
-        data.open()
-            .read_to_string(&mut body)
-            .map_err(|e| (Status::InternalServerError, format!("{:?}", e)))?;
+        Box::pin(async move {
+            let mut body = String::new();
+            let mut reader = data.open(BODY_LIMIT.bytes());
+            if let Err(e) = reader.read_to_string(&mut body).await {
+                return Failure((Status::InternalServerError, format!("{:?}", e)));
+            }
 
-        Success(GraphQLRequest(if is_json {
-            serde_json::from_str(&body).map_err(|e| (Status::BadRequest, format!("{}", e)))?
-        } else {
-            GraphQLBatchRequest::Single(http::GraphQLRequest::new(body, None, None))
-        }))
+            Success(GraphQLRequest(if is_json {
+                match serde_json::from_str(&body) {
+                    Ok(req) => req,
+                    Err(e) => return Failure((Status::BadRequest, format!("{}", e))),
+                }
+            } else {
+                GraphQLBatchRequest::Single(http::GraphQLRequest::new(body, None, None))
+            }))
+        })
+        .await
     }
 }
 
-impl<'r> Responder<'r> for GraphQLResponse {
-    fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
+impl<'r, 'o: 'r> Responder<'r, 'o> for GraphQLResponse {
+    fn respond_to(self, _req: &'r Request<'_>) -> response::Result<'o> {
         let GraphQLResponse(status, body) = self;
 
-        Ok(Response::build()
+        Response::build()
             .header(ContentType::new("application", "json"))
             .status(status)
-            .sized_body(Cursor::new(body))
-            .finalize())
+            .sized_body(body.len(), Cursor::new(body))
+            .ok()
     }
 }
 
@@ -303,40 +372,66 @@ impl<'r> Responder<'r> for GraphQLResponse {
 mod fromform_tests {
     use super::*;
     use juniper::InputValue;
-    use rocket::request::{FormItems, FromForm};
-    use std::str;
+    use rocket::{
+        form::{error::ErrorKind, Error, Form, Strict},
+        http::RawStr,
+    };
+    use std::borrow::Cow;
 
-    fn check_error(input: &str, error: &str, strict: bool) {
-        let mut items = FormItems::from(input);
-        let result: Result<GraphQLRequest, _> = GraphQLRequest::from_form(&mut items, strict);
-        assert!(result.is_err());
-        assert_eq!(result.unwrap_err(), error);
+    fn check_error(input: &str, expected_errors: Vec<Error>, strict: bool) {
+        let errors = if strict {
+            let result = Form::<Strict<GraphQLRequest>>::parse_encoded(RawStr::new(input));
+            assert!(result.is_err());
+            result.unwrap_err()
+        } else {
+            let result = Form::<GraphQLRequest>::parse_encoded(RawStr::new(input));
+            assert!(result.is_err());
+            result.unwrap_err()
+        };
+        assert_eq!(errors.len(), expected_errors.len());
+        for (error, expected) in errors.iter().zip(&expected_errors) {
+            match (&error.kind, &expected.kind) {
+                (ErrorKind::Unknown, ErrorKind::Unknown) => (),
+                (kind_a, kind_b) => assert_eq!(kind_a, kind_b),
+            };
+            assert_eq!(error.name, expected.name);
+            assert_eq!(error.value, expected.value);
+            assert_eq!(error.entity, expected.entity);
+        }
     }
 
     #[test]
     fn test_empty_form() {
-        check_error("", "Query parameter missing", false);
+        check_error(
+            "",
+            vec![Error::from(ErrorKind::Missing).with_name("query")],
+            false,
+        );
     }
 
     #[test]
     fn test_no_query() {
         check_error(
             "operation_name=foo&variables={}",
-            "Query parameter missing",
+            vec![Error::from(ErrorKind::Missing).with_name("query")],
             false,
         );
     }
 
     #[test]
     fn test_strict() {
-        check_error("query=test&foo=bar", "Prohibited extra field \'foo\'", true);
+        check_error(
+            "query=test&foo=bar",
+            vec![Error::from(ErrorKind::Unknown).with_name("foo")],
+            true,
+        );
     }
 
     #[test]
     fn test_duplicate_query() {
         check_error(
             "query=foo&query=bar",
-            "Query parameter must not occur more than once",
+            vec![Error::from(ErrorKind::Duplicate).with_name("query")],
             false,
         );
     }
@@ -345,7 +440,7 @@ mod fromform_tests {
     fn test_duplicate_operation_name() {
         check_error(
             "query=test&operation_name=op1&operation_name=op2",
-            "Operation name parameter must not occur more than once",
+            vec![Error::from(ErrorKind::Duplicate).with_name("operation_name")],
             false,
         );
     }
@@ -354,7 +449,7 @@ mod fromform_tests {
     fn test_duplicate_variables() {
         check_error(
             "query=test&variables={}&variables={}",
-            "Variables parameter must not occur more than once",
+            vec![Error::from(ErrorKind::Duplicate).with_name("variables")],
             false,
         );
     }
@@ -363,16 +458,18 @@ mod fromform_tests {
     fn test_variables_invalid_json() {
         check_error(
             "query=test&variables=NOT_JSON",
-            "expected value at line 1 column 1",
+            vec![Error::from(ErrorKind::Validation(Cow::Owned(
+                "expected value at line 1 column 1".to_owned(),
+            )))
+            .with_name("variables")],
             false,
         );
     }
 
     #[test]
     fn test_variables_valid_json() {
-        let form_string = r#"query=test&variables={"foo":"bar"}"#;
-        let mut items = FormItems::from(form_string);
-        let result = GraphQLRequest::from_form(&mut items, false);
+        let result: Result<GraphQLRequest, Errors> =
+            Form::parse_encoded(RawStr::new(r#"query=test&variables={"foo":"bar"}"#));
         assert!(result.is_ok());
         let variables = ::serde_json::from_str::<InputValue>(r#"{"foo":"bar"}"#).unwrap();
         let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
@@ -385,10 +482,9 @@ mod fromform_tests {
 
     #[test]
     fn test_variables_encoded_json() {
-        let form_string = r#"query=test&variables={"foo": "x%20y%26%3F+z"}"#;
-        let mut items = FormItems::from(form_string);
-        let result = GraphQLRequest::from_form(&mut items, false);
-        assert!(result.is_ok());
+        let result: Result<GraphQLRequest, Errors> = Form::parse_encoded(RawStr::new(
+            r#"query=test&variables={"foo":"x%20y%26%3F+z"}"#,
+        ));
         let variables = ::serde_json::from_str::<InputValue>(r#"{"foo":"x y&? z"}"#).unwrap();
         let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
             "test".to_string(),
@@ -400,9 +496,9 @@ mod fromform_tests {
 
     #[test]
     fn test_url_decode() {
-        let form_string = "query=%25foo%20bar+baz%26%3F&operation_name=test";
-        let mut items = FormItems::from(form_string);
-        let result: Result<GraphQLRequest, _> = GraphQLRequest::from_form(&mut items, false);
+        let result: Result<GraphQLRequest, Errors> = Form::parse_encoded(RawStr::new(
+            "query=%25foo%20bar+baz%26%3F&operation_name=test",
+        ));
         assert!(result.is_ok());
         let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
             "%foo bar baz&?".to_string(),
@@ -415,6 +511,9 @@ mod fromform_tests {
 
 #[cfg(test)]
 mod tests {
+
+    use futures;
+
     use juniper::{
         http::tests as http_tests,
         tests::fixtures::starwars::schema::{Database, Query},
@@ -423,30 +522,28 @@ mod tests {
     use rocket::{
         self, get,
         http::ContentType,
-        local::{Client, LocalRequest},
-        post,
-        request::Form,
-        routes, Rocket, State,
+        local::asynchronous::{Client, LocalResponse},
+        post, routes, Build, Rocket, State,
     };
 
     type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
 
     #[get("/?<request..>")]
     fn get_graphql_handler(
-        context: State<Database>,
-        request: Form<super::GraphQLRequest>,
-        schema: State<Schema>,
+        context: &State<Database>,
+        request: super::GraphQLRequest,
+        schema: &State<Schema>,
     ) -> super::GraphQLResponse {
-        request.execute_sync(&schema, &context)
+        request.execute_sync(&*schema, &*context)
     }
 
     #[post("/", data = "<request>")]
     fn post_graphql_handler(
-        context: State<Database>,
+        context: &State<Database>,
         request: super::GraphQLRequest,
-        schema: State<Schema>,
+        schema: &State<Schema>,
     ) -> super::GraphQLResponse {
-        request.execute_sync(&schema, &context)
+        request.execute_sync(&*schema, &*context)
     }
 
     struct TestRocketIntegration {
@@ -455,86 +552,90 @@ mod tests {
 
     impl http_tests::HttpIntegration for TestRocketIntegration {
         fn get(&self, url: &str) -> http_tests::TestResponse {
-            let req = &self.client.get(url);
-            make_test_response(req)
+            let req = self.client.get(url);
+            let req = futures::executor::block_on(req.dispatch());
+            futures::executor::block_on(make_test_response(req))
         }
 
         fn post_json(&self, url: &str, body: &str) -> http_tests::TestResponse {
-            let req = &self.client.post(url).header(ContentType::JSON).body(body);
-            make_test_response(req)
+            let req = self.client.post(url).header(ContentType::JSON).body(body);
+            let req = futures::executor::block_on(req.dispatch());
+            futures::executor::block_on(make_test_response(req))
         }
 
         fn post_graphql(&self, url: &str, body: &str) -> http_tests::TestResponse {
-            let req = &self
+            let req = self
                 .client
                 .post(url)
                 .header(ContentType::new("application", "graphql"))
                 .body(body);
-            make_test_response(req)
+            let req = futures::executor::block_on(req.dispatch());
+            futures::executor::block_on(make_test_response(req))
         }
     }
 
-    #[test]
-    fn test_rocket_integration() {
+    #[rocket::async_test]
+    async fn test_rocket_integration() {
         let rocket = make_rocket();
-        let client = Client::new(rocket).expect("valid rocket");
+        let client = Client::untracked(rocket).await.expect("valid rocket");
         let integration = TestRocketIntegration { client };
 
         http_tests::run_http_test_suite(&integration);
     }
 
-    #[test]
-    fn test_operation_names() {
+    #[rocket::async_test]
+    async fn test_operation_names() {
         #[post("/", data = "<request>")]
         fn post_graphql_assert_operation_name_handler(
-            context: State<Database>,
+            context: &State<Database>,
             request: super::GraphQLRequest,
-            schema: State<Schema>,
+            schema: &State<Schema>,
         ) -> super::GraphQLResponse {
             assert_eq!(request.operation_names(), vec![Some("TestQuery")]);
-            request.execute_sync(&schema, &context)
+            request.execute_sync(&*schema, &*context)
         }
 
         let rocket = make_rocket_without_routes()
             .mount("/", routes![post_graphql_assert_operation_name_handler]);
-        let client = Client::new(rocket).expect("valid rocket");
+        let client = Client::untracked(rocket).await.expect("valid rocket");
 
-        let req = client
+        let resp = client
             .post("/")
             .header(ContentType::JSON)
-            .body(r#"{"query": "query TestQuery {hero{name}}", "operationName": "TestQuery"}"#);
-        let resp = make_test_response(&req);
+            .body(r#"{"query": "query TestQuery {hero{name}}", "operationName": "TestQuery"}"#)
+            .dispatch()
+            .await;
+        let resp = make_test_response(resp);
 
-        assert_eq!(resp.status_code, 200);
+        assert_eq!(resp.await.status_code, 200);
     }
 
-    fn make_rocket() -> Rocket {
+    fn make_rocket() -> Rocket<Build> {
         make_rocket_without_routes().mount("/", routes![post_graphql_handler, get_graphql_handler])
     }
 
-    fn make_rocket_without_routes() -> Rocket {
-        rocket::ignite().manage(Database::new()).manage(Schema::new(
+    fn make_rocket_without_routes() -> Rocket<Build> {
+        Rocket::build().manage(Database::new()).manage(Schema::new(
             Query,
             EmptyMutation::<Database>::new(),
             EmptySubscription::<Database>::new(),
         ))
     }
 
-    fn make_test_response(request: &LocalRequest) -> http_tests::TestResponse {
-        let mut response = request.clone().dispatch();
+    async fn make_test_response(response: LocalResponse<'_>) -> http_tests::TestResponse {
         let status_code = response.status().code as i32;
         let content_type = response
             .content_type()
             .expect("No content type header from handler")
             .to_string();
         let body = response
-            .body()
-            .expect("No body returned from GraphQL handler")
-            .into_string();
+            .into_string()
+            .await
+            .expect("No body returned from GraphQL handler");
 
         http_tests::TestResponse {
             status_code,
-            body,
+            body: Some(body),
             content_type,
         }
     }
diff --git a/juniper_rocket/tests/custom_response_tests.rs b/juniper_rocket/tests/custom_response_tests.rs
index c4de3e26..050be566 100644
--- a/juniper_rocket/tests/custom_response_tests.rs
+++ b/juniper_rocket/tests/custom_response_tests.rs
@@ -1,6 +1,5 @@
-use rocket::http::Status;
-
 use juniper_rocket::GraphQLResponse;
+use rocket::http::Status;
 
 #[test]
 fn test_graphql_response_is_public() {
diff --git a/juniper_rocket_async/.gitignore b/juniper_rocket_async/.gitignore
deleted file mode 100644
index a9d37c56..00000000
--- a/juniper_rocket_async/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-target
-Cargo.lock
diff --git a/juniper_rocket_async/CHANGELOG.md b/juniper_rocket_async/CHANGELOG.md
deleted file mode 100644
index d2889166..00000000
--- a/juniper_rocket_async/CHANGELOG.md
+++ /dev/null
@@ -1,62 +0,0 @@
-# master
-
-- Compatibility with the latest `juniper`.
-- Rocket integration does not require default features.
-- Support `application/graphql` POST requests.
-
-# [[0.5.1] 2019-10-24](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.5.1)
-
-- Compatibility with the latest `juniper`.
-
-# [[0.5.0] 2019-09-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.5.0)
-
-- Compatibility with the latest `juniper`.
-
-# [[0.4.1] 2019-07-29](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.4.1)
-
-- Compatibility with the latest `juniper`.
-
-# [[0.4.0] 2019-07-19](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.4.0)
-
-- Compatibility with the latest `juniper`.
-
-# [[0.3.0] 2019-05-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.3.0)
-
-- Expose the operation names from `GraphQLRequest`.
-- Compatibility with the latest `juniper`.
-
-# [0.2.0] 2018-12-17
-
-### Rocket updated to v0.4
-
-[Rocket](https://rocket.rs) integration now requires Rocket `0.4.0`. This is due
-to changes with the way Rocket handles form parsing. Before this update, it was
-impossible to leverage Rocket integration with Rocket beyond 0.3.x.
-
-Check out [Rocket's Changelog](https://github.com/SergioBenitez/Rocket/blob/v0.4/CHANGELOG.md)
-for more details on the 0.4 release.
-
-# juniper_rocket [0.1.3] 2018-09-13
-
-- Add `juniper-0.10.0` compatibility.
-
-# juniper_rocket [0.1.2] 2018-01-13
-
-## Changes
-
-### Rocket updated to `0.3.6`
-
-[Rocket](https://rocket.rs) integration now requires Rocket `0.3.6` to
-support building with recent Rust nightlies.
-
-Additional information and supported nightly versions can be found in [Rocket's changelog](https://github.com/SergioBenitez/Rocket/blob/master/CHANGELOG.md#version-036-jan-12-2018).
-
-[#125](https://github.com/graphql-rust/juniper/issues/125)
-
-### Decoding of query params
-
-When processing GET requests, query parameters were not properly url_decoded,
-
-This was fixed by [PR #122](https://github.com/graphql-rust/juniper/pull/128) by @LegNeato.
-
-This fixed the [issue #116](https://github.com/graphql-rust/juniper/issues/116).
diff --git a/juniper_rocket_async/Cargo.toml b/juniper_rocket_async/Cargo.toml
deleted file mode 100644
index 68a5fa3c..00000000
--- a/juniper_rocket_async/Cargo.toml
+++ /dev/null
@@ -1,21 +0,0 @@
-[package]
-name = "juniper_rocket_async"
-version = "0.5.1"
-edition = "2018"
-authors = [
-    "Magnus Hallin <mhallin@fastmail.com>",
-    "Christoph Herzog <chris@theduke.at>",
-]
-description = "Juniper GraphQL integration with Rocket"
-license = "BSD-2-Clause"
-documentation = "https://docs.rs/juniper_rocket"
-repository = "https://github.com/graphql-rust/juniper"
-
-[dependencies]
-futures = "0.3.1"
-juniper = { version = "0.15.6", path = "../juniper", default-features = false }
-rocket = { version = "0.5.0-rc.1", default-features = false }
-serde_json = "1.0.2"
-
-[dev-dependencies]
-juniper = { version = "0.15.6", path = "../juniper", features = ["expose-test-schema"] }
diff --git a/juniper_rocket_async/LICENSE b/juniper_rocket_async/LICENSE
deleted file mode 100644
index 0ccd1e17..00000000
--- a/juniper_rocket_async/LICENSE
+++ /dev/null
@@ -1,25 +0,0 @@
-BSD 2-Clause License
-
-Copyright (c) 2016, Magnus Hallin
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
-  list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
-  this list of conditions and the following disclaimer in the documentation
-  and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/juniper_rocket_async/README.md b/juniper_rocket_async/README.md
deleted file mode 100644
index 9ff6f6e2..00000000
--- a/juniper_rocket_async/README.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# juniper_rocket
-
-This repository contains the [Rocket][Rocket] web server integration for 
-[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust.
-
-## Documentation
-
-For documentation, including guides and examples, check out [Juniper][Juniper].
-
-A basic usage example can also be found in the [Api documentation][documentation].
-
-## Examples
-
-Check [examples/rocket_server.rs][example] for example code of a working Rocket 
-server with GraphQL handlers.
-
-## Links
-
-* [Juniper][Juniper]
-* [Api Reference][documetation]
-* [Rocket][Iron]
-
-## License
-
-This project is under the BSD-2 license.
-
-Check the LICENSE file for details.
-
-[Rocket]: https://rocket.rs
-[Juniper]: https://github.com/graphql-rust/juniper
-[GraphQL]: http://graphql.org
-[documentation]: https://docs.rs/juniper_rocket
-[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/rocket_server.rs
-
-
diff --git a/juniper_rocket_async/examples/rocket_server.rs b/juniper_rocket_async/examples/rocket_server.rs
deleted file mode 100644
index fa86fddf..00000000
--- a/juniper_rocket_async/examples/rocket_server.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-use juniper::{
-    tests::fixtures::starwars::schema::{Database, Query},
-    EmptyMutation, EmptySubscription, RootNode,
-};
-use rocket::{response::content, Rocket, State};
-
-type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
-
-#[rocket::get("/")]
-fn graphiql() -> content::Html<String> {
-    juniper_rocket_async::graphiql_source("/graphql", None)
-}
-
-#[rocket::get("/graphql?<request>")]
-fn get_graphql_handler(
-    context: &State<Database>,
-    request: juniper_rocket_async::GraphQLRequest,
-    schema: &State<Schema>,
-) -> juniper_rocket_async::GraphQLResponse {
-    request.execute_sync(&*schema, &*context)
-}
-
-#[rocket::post("/graphql", data = "<request>")]
-fn post_graphql_handler(
-    context: &State<Database>,
-    request: juniper_rocket_async::GraphQLRequest,
-    schema: &State<Schema>,
-) -> juniper_rocket_async::GraphQLResponse {
-    request.execute_sync(&*schema, &*context)
-}
-
-#[rocket::main]
-async fn main() {
-    Rocket::build()
-        .manage(Database::new())
-        .manage(Schema::new(
-            Query,
-            EmptyMutation::<Database>::new(),
-            EmptySubscription::<Database>::new(),
-        ))
-        .mount(
-            "/",
-            rocket::routes![graphiql, get_graphql_handler, post_graphql_handler],
-        )
-        .launch()
-        .await
-        .expect("server to launch");
-}
diff --git a/juniper_rocket_async/src/lib.rs b/juniper_rocket_async/src/lib.rs
deleted file mode 100644
index 4d2ab9b1..00000000
--- a/juniper_rocket_async/src/lib.rs
+++ /dev/null
@@ -1,643 +0,0 @@
-/*!
-
-# juniper_rocket_async
-
-This repository contains the [Rocket][Rocket] web server integration for
-[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust.
-
-## Documentation
-
-For documentation, including guides and examples, check out [Juniper][Juniper].
-
-A basic usage example can also be found in the [Api documentation][documentation].
-
-## Examples
-
-Check [examples/rocket_server.rs][example] for example code of a working Rocket
-server with GraphQL handlers.
-
-## Links
-
-* [Juniper][Juniper]
-* [Api Reference][documentation]
-* [Rocket][Rocket]
-
-## License
-
-This project is under the BSD-2 license.
-
-Check the LICENSE file for details.
-
-[Rocket]: https://rocket.rs
-[Juniper]: https://github.com/graphql-rust/juniper
-[GraphQL]: http://graphql.org
-[documentation]: https://docs.rs/juniper_rocket_async
-[example]: https://github.com/graphql-rust/juniper_rocket_async/blob/master/examples/rocket_server.rs
-
-*/
-
-#![doc(html_root_url = "https://docs.rs/juniper_rocket_async/0.2.0")]
-
-use std::{borrow::Cow, io::Cursor};
-
-use rocket::{
-    data::{self, FromData, ToByteUnit},
-    form::{error::ErrorKind, DataField, Error, Errors, FromForm, Options, ValueField},
-    http::{ContentType, Status},
-    outcome::Outcome::{Failure, Forward, Success},
-    response::{self, content, Responder, Response},
-    Data, Request,
-};
-
-use juniper::{
-    http::{self, GraphQLBatchRequest},
-    DefaultScalarValue, FieldError, GraphQLSubscriptionType, GraphQLType, GraphQLTypeAsync,
-    InputValue, RootNode, ScalarValue,
-};
-
-/// 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<S = DefaultScalarValue>(GraphQLBatchRequest<S>)
-where
-    S: ScalarValue;
-
-/// Simple wrapper around the result of executing a GraphQL query
-pub struct GraphQLResponse(pub Status, pub String);
-
-/// Generate an HTML page containing GraphiQL
-pub fn graphiql_source(
-    graphql_endpoint_url: &str,
-    subscriptions_endpoint_url: Option<&str>,
-) -> content::Html<String> {
-    content::Html(juniper::http::graphiql::graphiql_source(
-        graphql_endpoint_url,
-        subscriptions_endpoint_url,
-    ))
-}
-
-/// Generate an HTML page containing GraphQL Playground
-pub fn playground_source(
-    graphql_endpoint_url: &str,
-    subscriptions_endpoint_url: Option<&str>,
-) -> content::Html<String> {
-    content::Html(juniper::http::playground::playground_source(
-        graphql_endpoint_url,
-        subscriptions_endpoint_url,
-    ))
-}
-
-impl<S> GraphQLRequest<S>
-where
-    S: ScalarValue,
-{
-    /// Synchronously execute an incoming GraphQL query.
-    pub fn execute_sync<CtxT, QueryT, MutationT, SubscriptionT>(
-        &self,
-        root_node: &RootNode<QueryT, MutationT, SubscriptionT, S>,
-        context: &CtxT,
-    ) -> GraphQLResponse
-    where
-        QueryT: GraphQLType<S, Context = CtxT>,
-        MutationT: GraphQLType<S, Context = CtxT>,
-        SubscriptionT: GraphQLType<S, Context = CtxT>,
-    {
-        let response = self.0.execute_sync(root_node, context);
-        let status = if response.is_ok() {
-            Status::Ok
-        } else {
-            Status::BadRequest
-        };
-        let json = serde_json::to_string(&response).unwrap();
-
-        GraphQLResponse(status, json)
-    }
-
-    /// Asynchronously execute an incoming GraphQL query.
-    pub async fn execute<CtxT, QueryT, MutationT, SubscriptionT>(
-        &self,
-        root_node: &RootNode<'_, QueryT, MutationT, SubscriptionT, S>,
-        context: &CtxT,
-    ) -> GraphQLResponse
-    where
-        QueryT: GraphQLTypeAsync<S, Context = CtxT>,
-        QueryT::TypeInfo: Sync,
-        MutationT: GraphQLTypeAsync<S, Context = CtxT>,
-        MutationT::TypeInfo: Sync,
-        SubscriptionT: GraphQLSubscriptionType<S, Context = CtxT>,
-        SubscriptionT::TypeInfo: Sync,
-        CtxT: Sync,
-        S: Send + Sync,
-    {
-        let response = self.0.execute(root_node, context).await;
-        let status = if response.is_ok() {
-            Status::Ok
-        } else {
-            Status::BadRequest
-        };
-        let json = serde_json::to_string(&response).unwrap();
-
-        GraphQLResponse(status, json)
-    }
-
-    /// Returns the operation names associated with this request.
-    ///
-    /// For batch requests there will be multiple names.
-    pub fn operation_names(&self) -> Vec<Option<&str>> {
-        self.0.operation_names()
-    }
-}
-
-impl GraphQLResponse {
-    /// Constructs an error response outside of the normal execution flow
-    ///
-    /// # Examples
-    ///
-    /// ```
-    /// # use rocket::http::CookieJar;
-    /// # use rocket::form::Form;
-    /// # use rocket::response::content;
-    /// # use rocket::State;
-    /// #
-    /// # use juniper::tests::fixtures::starwars::schema::{Database, Query};
-    /// # use juniper::{EmptyMutation, EmptySubscription, FieldError, RootNode, Value};
-    /// #
-    /// # type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
-    /// #
-    /// #[rocket::get("/graphql?<request..>")]
-    /// fn get_graphql_handler(
-    ///     cookies: &CookieJar,
-    ///     context: &State<Database>,
-    ///     request: juniper_rocket_async::GraphQLRequest,
-    ///     schema: &State<Schema>,
-    /// ) -> juniper_rocket_async::GraphQLResponse {
-    ///     if cookies.get("user_id").is_none() {
-    ///         let err = FieldError::new("User is not logged in", Value::null());
-    ///         return juniper_rocket_async::GraphQLResponse::error(err);
-    ///     }
-    ///
-    ///     request.execute_sync(&*schema, &*context)
-    /// }
-    /// ```
-    pub fn error(error: FieldError) -> Self {
-        let response = http::GraphQLResponse::error(error);
-        let json = serde_json::to_string(&response).unwrap();
-        GraphQLResponse(Status::BadRequest, json)
-    }
-
-    /// Constructs a custom response outside of the normal execution flow
-    ///
-    /// This is intended for highly customized integrations and should only
-    /// be used as a last resort. For normal juniper use, use the response
-    /// from GraphQLRequest::execute_sync(..).
-    pub fn custom(status: Status, response: serde_json::Value) -> Self {
-        let json = serde_json::to_string(&response).unwrap();
-        GraphQLResponse(status, json)
-    }
-}
-
-pub struct GraphQLContext<'f, S: ScalarValue> {
-    opts: Options,
-    query: Option<String>,
-    operation_name: Option<String>,
-    variables: Option<InputValue<S>>,
-    errors: Errors<'f>,
-}
-
-impl<'f, S: ScalarValue> GraphQLContext<'f, S> {
-    fn query(&mut self, value: String) {
-        if self.query.is_some() {
-            let error = Error::from(ErrorKind::Duplicate).with_name("query");
-
-            self.errors.push(error)
-        } else {
-            println!("{}", value);
-            self.query = Some(value);
-        }
-    }
-
-    fn operation_name(&mut self, value: String) {
-        if self.operation_name.is_some() {
-            let error = Error::from(ErrorKind::Duplicate).with_name("operation_name");
-
-            self.errors.push(error)
-        } else {
-            self.operation_name = Some(value);
-        }
-    }
-
-    fn variables(&mut self, value: String) {
-        if self.variables.is_some() {
-            let error = Error::from(ErrorKind::Duplicate).with_name("variables");
-
-            self.errors.push(error)
-        } else {
-            let parse_result = serde_json::from_str::<InputValue<S>>(&value);
-
-            match parse_result {
-                Ok(variables) => self.variables = Some(variables),
-                Err(e) => {
-                    let error = Error::from(ErrorKind::Validation(Cow::Owned(e.to_string())))
-                        .with_name("variables");
-
-                    self.errors.push(error);
-                }
-            }
-        }
-    }
-}
-
-#[rocket::async_trait]
-impl<'f, S> FromForm<'f> for GraphQLRequest<S>
-where
-    S: ScalarValue + Send,
-{
-    type Context = GraphQLContext<'f, S>;
-
-    fn init(opts: Options) -> Self::Context {
-        GraphQLContext {
-            opts,
-            query: None,
-            operation_name: None,
-            variables: None,
-            errors: Errors::new(),
-        }
-    }
-
-    fn push_value(ctx: &mut Self::Context, field: ValueField<'f>) {
-        match field.name.key().map(|key| key.as_str()) {
-            Some("query") => ctx.query(field.value.to_owned()),
-            Some("operation_name") => ctx.operation_name(field.value.to_owned()),
-            Some("variables") => ctx.variables(field.value.to_owned()),
-            Some(key) => {
-                if ctx.opts.strict {
-                    let error = Error::from(ErrorKind::Unknown).with_name(key);
-
-                    ctx.errors.push(error)
-                }
-            }
-            None => {
-                if ctx.opts.strict {
-                    let error = Error::from(ErrorKind::Unexpected);
-
-                    ctx.errors.push(error)
-                }
-            }
-        }
-    }
-
-    async fn push_data(ctx: &mut Self::Context, field: DataField<'f, '_>) {
-        if ctx.opts.strict {
-            let error = Error::from(ErrorKind::Unexpected).with_name(field.name);
-
-            ctx.errors.push(error)
-        }
-    }
-
-    fn finalize(mut ctx: Self::Context) -> rocket::form::Result<'f, Self> {
-        if ctx.query.is_none() {
-            let error = Error::from(ErrorKind::Missing).with_name("query");
-
-            ctx.errors.push(error)
-        }
-
-        match ctx.errors.is_empty() {
-            true => Ok(GraphQLRequest(GraphQLBatchRequest::Single(
-                http::GraphQLRequest::new(ctx.query.unwrap(), ctx.operation_name, ctx.variables),
-            ))),
-            false => Err(ctx.errors),
-        }
-    }
-}
-
-const BODY_LIMIT: u64 = 1024 * 100;
-
-#[rocket::async_trait]
-impl<'r, S> FromData<'r> for GraphQLRequest<S>
-where
-    S: ScalarValue,
-{
-    type Error = String;
-
-    async fn from_data(
-        req: &'r Request<'_>,
-        data: Data<'r>,
-    ) -> data::Outcome<'r, Self, Self::Error> {
-        use rocket::tokio::io::AsyncReadExt as _;
-
-        let content_type = req
-            .content_type()
-            .map(|ct| (ct.top().as_str(), ct.sub().as_str()));
-        let is_json = match content_type {
-            Some(("application", "json")) => true,
-            Some(("application", "graphql")) => false,
-            _ => return Box::pin(async move { Forward(data) }).await,
-        };
-
-        Box::pin(async move {
-            let mut body = String::new();
-            let mut reader = data.open(BODY_LIMIT.bytes());
-            if let Err(e) = reader.read_to_string(&mut body).await {
-                return Failure((Status::InternalServerError, format!("{:?}", e)));
-            }
-
-            Success(GraphQLRequest(if is_json {
-                match serde_json::from_str(&body) {
-                    Ok(req) => req,
-                    Err(e) => return Failure((Status::BadRequest, format!("{}", e))),
-                }
-            } else {
-                GraphQLBatchRequest::Single(http::GraphQLRequest::new(body, None, None))
-            }))
-        })
-        .await
-    }
-}
-
-impl<'r, 'o: 'r> Responder<'r, 'o> for GraphQLResponse {
-    fn respond_to(self, _req: &'r Request<'_>) -> response::Result<'o> {
-        let GraphQLResponse(status, body) = self;
-
-        Response::build()
-            .header(ContentType::new("application", "json"))
-            .status(status)
-            .sized_body(body.len(), Cursor::new(body))
-            .ok()
-    }
-}
-
-#[cfg(test)]
-mod fromform_tests {
-    use super::*;
-    use juniper::InputValue;
-    use rocket::{
-        form::{error::ErrorKind, Error, Form, Strict},
-        http::RawStr,
-    };
-    use std::borrow::Cow;
-
-    fn check_error(input: &str, expected_errors: Vec<Error>, strict: bool) {
-        let errors = if strict {
-            let result = Form::<Strict<GraphQLRequest>>::parse_encoded(RawStr::new(input));
-            assert!(result.is_err());
-            result.unwrap_err()
-        } else {
-            let result = Form::<GraphQLRequest>::parse_encoded(RawStr::new(input));
-            assert!(result.is_err());
-            result.unwrap_err()
-        };
-        assert_eq!(errors.len(), expected_errors.len());
-        for (error, expected) in errors.iter().zip(&expected_errors) {
-            match (&error.kind, &expected.kind) {
-                (ErrorKind::Unknown, ErrorKind::Unknown) => (),
-                (kind_a, kind_b) => assert_eq!(kind_a, kind_b),
-            };
-            assert_eq!(error.name, expected.name);
-            assert_eq!(error.value, expected.value);
-            assert_eq!(error.entity, expected.entity);
-        }
-    }
-
-    #[test]
-    fn test_empty_form() {
-        check_error(
-            "",
-            vec![Error::from(ErrorKind::Missing).with_name("query")],
-            false,
-        );
-    }
-
-    #[test]
-    fn test_no_query() {
-        check_error(
-            "operation_name=foo&variables={}",
-            vec![Error::from(ErrorKind::Missing).with_name("query")],
-            false,
-        );
-    }
-
-    #[test]
-    fn test_strict() {
-        check_error(
-            "query=test&foo=bar",
-            vec![Error::from(ErrorKind::Unknown).with_name("foo")],
-            true,
-        );
-    }
-
-    #[test]
-    fn test_duplicate_query() {
-        check_error(
-            "query=foo&query=bar",
-            vec![Error::from(ErrorKind::Duplicate).with_name("query")],
-            false,
-        );
-    }
-
-    #[test]
-    fn test_duplicate_operation_name() {
-        check_error(
-            "query=test&operation_name=op1&operation_name=op2",
-            vec![Error::from(ErrorKind::Duplicate).with_name("operation_name")],
-            false,
-        );
-    }
-
-    #[test]
-    fn test_duplicate_variables() {
-        check_error(
-            "query=test&variables={}&variables={}",
-            vec![Error::from(ErrorKind::Duplicate).with_name("variables")],
-            false,
-        );
-    }
-
-    #[test]
-    fn test_variables_invalid_json() {
-        check_error(
-            "query=test&variables=NOT_JSON",
-            vec![Error::from(ErrorKind::Validation(Cow::Owned(
-                "expected value at line 1 column 1".to_owned(),
-            )))
-            .with_name("variables")],
-            false,
-        );
-    }
-
-    #[test]
-    fn test_variables_valid_json() {
-        let result: Result<GraphQLRequest, Errors> =
-            Form::parse_encoded(RawStr::new(r#"query=test&variables={"foo":"bar"}"#));
-        assert!(result.is_ok());
-        let variables = ::serde_json::from_str::<InputValue>(r#"{"foo":"bar"}"#).unwrap();
-        let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
-            "test".to_string(),
-            None,
-            Some(variables),
-        )));
-        assert_eq!(result.unwrap(), expected);
-    }
-
-    #[test]
-    fn test_variables_encoded_json() {
-        let result: Result<GraphQLRequest, Errors> = Form::parse_encoded(RawStr::new(
-            r#"query=test&variables={"foo":"x%20y%26%3F+z"}"#,
-        ));
-        let variables = ::serde_json::from_str::<InputValue>(r#"{"foo":"x y&? z"}"#).unwrap();
-        let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
-            "test".to_string(),
-            None,
-            Some(variables),
-        )));
-        assert_eq!(result.unwrap(), expected);
-    }
-
-    #[test]
-    fn test_url_decode() {
-        let result: Result<GraphQLRequest, Errors> = Form::parse_encoded(RawStr::new(
-            "query=%25foo%20bar+baz%26%3F&operation_name=test",
-        ));
-        assert!(result.is_ok());
-        let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
-            "%foo bar baz&?".to_string(),
-            Some("test".to_string()),
-            None,
-        )));
-        assert_eq!(result.unwrap(), expected);
-    }
-}
-
-#[cfg(test)]
-mod tests {
-
-    use futures;
-
-    use juniper::{
-        http::tests as http_tests,
-        tests::fixtures::starwars::schema::{Database, Query},
-        EmptyMutation, EmptySubscription, RootNode,
-    };
-    use rocket::{
-        self, get,
-        http::ContentType,
-        local::asynchronous::{Client, LocalResponse},
-        post, routes, Build, Rocket, State,
-    };
-
-    type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
-
-    #[get("/?<request..>")]
-    fn get_graphql_handler(
-        context: &State<Database>,
-        request: super::GraphQLRequest,
-        schema: &State<Schema>,
-    ) -> super::GraphQLResponse {
-        request.execute_sync(&*schema, &*context)
-    }
-
-    #[post("/", data = "<request>")]
-    fn post_graphql_handler(
-        context: &State<Database>,
-        request: super::GraphQLRequest,
-        schema: &State<Schema>,
-    ) -> super::GraphQLResponse {
-        request.execute_sync(&*schema, &*context)
-    }
-
-    struct TestRocketIntegration {
-        client: Client,
-    }
-
-    impl http_tests::HttpIntegration for TestRocketIntegration {
-        fn get(&self, url: &str) -> http_tests::TestResponse {
-            let req = self.client.get(url);
-            let req = futures::executor::block_on(req.dispatch());
-            futures::executor::block_on(make_test_response(req))
-        }
-
-        fn post_json(&self, url: &str, body: &str) -> http_tests::TestResponse {
-            let req = self.client.post(url).header(ContentType::JSON).body(body);
-            let req = futures::executor::block_on(req.dispatch());
-            futures::executor::block_on(make_test_response(req))
-        }
-
-        fn post_graphql(&self, url: &str, body: &str) -> http_tests::TestResponse {
-            let req = self
-                .client
-                .post(url)
-                .header(ContentType::new("application", "graphql"))
-                .body(body);
-            let req = futures::executor::block_on(req.dispatch());
-            futures::executor::block_on(make_test_response(req))
-        }
-    }
-
-    #[rocket::async_test]
-    async fn test_rocket_integration() {
-        let rocket = make_rocket();
-        let client = Client::untracked(rocket).await.expect("valid rocket");
-        let integration = TestRocketIntegration { client };
-
-        http_tests::run_http_test_suite(&integration);
-    }
-
-    #[rocket::async_test]
-    async fn test_operation_names() {
-        #[post("/", data = "<request>")]
-        fn post_graphql_assert_operation_name_handler(
-            context: &State<Database>,
-            request: super::GraphQLRequest,
-            schema: &State<Schema>,
-        ) -> super::GraphQLResponse {
-            assert_eq!(request.operation_names(), vec![Some("TestQuery")]);
-            request.execute_sync(&*schema, &*context)
-        }
-
-        let rocket = make_rocket_without_routes()
-            .mount("/", routes![post_graphql_assert_operation_name_handler]);
-        let client = Client::untracked(rocket).await.expect("valid rocket");
-
-        let resp = client
-            .post("/")
-            .header(ContentType::JSON)
-            .body(r#"{"query": "query TestQuery {hero{name}}", "operationName": "TestQuery"}"#)
-            .dispatch()
-            .await;
-        let resp = make_test_response(resp);
-
-        assert_eq!(resp.await.status_code, 200);
-    }
-
-    fn make_rocket() -> Rocket<Build> {
-        make_rocket_without_routes().mount("/", routes![post_graphql_handler, get_graphql_handler])
-    }
-
-    fn make_rocket_without_routes() -> Rocket<Build> {
-        Rocket::build().manage(Database::new()).manage(Schema::new(
-            Query,
-            EmptyMutation::<Database>::new(),
-            EmptySubscription::<Database>::new(),
-        ))
-    }
-
-    async fn make_test_response(response: LocalResponse<'_>) -> http_tests::TestResponse {
-        let status_code = response.status().code as i32;
-        let content_type = response
-            .content_type()
-            .expect("No content type header from handler")
-            .to_string();
-        let body = response
-            .into_string()
-            .await
-            .expect("No body returned from GraphQL handler");
-
-        http_tests::TestResponse {
-            status_code,
-            body: Some(body),
-            content_type,
-        }
-    }
-}
diff --git a/juniper_rocket_async/tests/custom_response_tests.rs b/juniper_rocket_async/tests/custom_response_tests.rs
deleted file mode 100644
index 3cee40c9..00000000
--- a/juniper_rocket_async/tests/custom_response_tests.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use juniper_rocket_async::GraphQLResponse;
-use rocket::http::Status;
-
-#[test]
-fn test_graphql_response_is_public() {
-    let _ = GraphQLResponse(Status::Unauthorized, "Unauthorized".to_string());
-}