Repository: graphql-rust/juniper (language: rust)

Issue:
[juniper_rocket] Document GET request syntax
I am using the Juniper Rocket example. The POST endpoint and GraphiQL interface work for me, but I can't figure out how to send a GET request. (I'm also new to using Rocket.) I have tried

```
/graphql?{}
/graphql?request={}
/graphql?query={}
```
where {} is a URL-encoded version of
```
{
  __schema {
    types {
      name
    }
  }
}
```
and none of these work for me.

An explicit example of a GET request that works would be helpful.


Discussion / hints:
@ysulyma `/graphql?query` [should work](https://graphql.org/learn/serving-over-http/#get-request), but [`rocket` example](https://github.com/graphql-rust/juniper/blob/0df1e448e87d6b5f65d16710ca4b05d873459964/juniper_rocket/examples/rocket_server.rs#L14-L21) is incorrect, as it uses `Deserialize` impl of [`GraphQLRequest`](https://docs.rs/juniper/latest/juniper/http/struct.GraphQLRequest.html). We'll address this in `0.16` release. 

Until then you have 2 options:
1. Use `POST` for both mutations and queries. This should be [ok](https://graphql.org/learn/serving-over-http/#post-request). 
2. Implement custom structure with right `Deserialize` impl and convert this struct into a [`GraphQLRequest`](https://docs.rs/juniper/latest/juniper/http/struct.GraphQLRequest.html). 
> @ysulyma `/graphql?query` [should work](https://graphql.org/learn/serving-over-http/#get-request), but [`rocket` example](https://github.com/graphql-rust/juniper/blob/0df1e448e87d6b5f65d16710ca4b05d873459964/juniper_rocket/examples/rocket_server.rs#L14-L21) is incorrect, as it uses `Deserialize` impl of [`GraphQLRequest`](https://docs.rs/juniper/latest/juniper/http/struct.GraphQLRequest.html).

This is not true. In [the specified example](https://github.com/graphql-rust/juniper/blob/0df1e448e87d6b5f65d16710ca4b05d873459964/juniper_rocket/examples/rocket_server.rs#L14-L21) is used [`juniper_rocket::GraphQLRequest`](https://github.com/graphql-rust/juniper/blob/0df1e448e87d6b5f65d16710ca4b05d873459964/juniper_rocket/src/lib.rs#L25-L28), rather than [`juniper::http::GraphQLRequest`](https://github.com/graphql-rust/juniper/blob/0df1e448e87d6b5f65d16710ca4b05d873459964/juniper/src/http/mod.rs#L27-L45), and it doesn't implement `Deserialize` at all. In fact, it [implements `FromForm`](https://github.com/graphql-rust/juniper/blob/0df1e448e87d6b5f65d16710ca4b05d873459964/juniper_rocket/src/lib.rs#L226-L287), which gives it [ability to being parsed from URL query strings](https://rocket.rs/v0.5/guide/requests/#query-strings). So all this works okay. Passing [`juniper::http::tests`](https://github.com/graphql-rust/juniper/blob/0df1e448e87d6b5f65d16710ca4b05d873459964/juniper/src/http/mod.rs#L394-L398) confirms it.

The example, however, is really broken. Rather than having
```rust
#[rocket::get("/graphql?<request>")]
```
attribute, it should have
```rust
#[rocket::get("/graphql?<request..>")]
```



Fix patch:
diff --git a/book/src/servers/rocket.md b/book/src/servers/rocket.md
index bf9768120..fe2f6cfbb 100644
--- a/book/src/servers/rocket.md
+++ b/book/src/servers/rocket.md
@@ -19,4 +19,4 @@ Included in the source is a [small example][example] which sets up a basic Graph
 [graphiql]: https://github.com/graphql/graphiql
 [rocket]: https://rocket.rs/
 [juniper_rocket]: https://github.com/graphql-rust/juniper/tree/master/juniper_rocket
-[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/rocket_server.rs
+[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/simple.rs
diff --git a/juniper_rocket/CHANGELOG.md b/juniper_rocket/CHANGELOG.md
index 3f273a456..ba1a3db97 100644
--- a/juniper_rocket/CHANGELOG.md
+++ b/juniper_rocket/CHANGELOG.md
@@ -17,10 +17,15 @@ All user visible changes to `juniper_rocket` crate will be documented in this fi
 
 - `AsRef` and `AsMut` implementation for `GraphQLRequest` to its inner type. ([#968], [#930])
 
+### Changed
+
+- Made `subscriptions_endpoint_url` argument polymorphic in `graphiql_source()` and `playground_source()`. ([#1223])
+
 [#930]: /../../issues/930
 [#968]: /../../pull/968
 [#1205]: /../../pull/1205
 [#1220]: /../../pull/1220
+[#1223]: /../../pull/1223
 
 
 
diff --git a/juniper_rocket/README.md b/juniper_rocket/README.md
index 806baf244..0ea4276e9 100644
--- a/juniper_rocket/README.md
+++ b/juniper_rocket/README.md
@@ -24,7 +24,7 @@ A basic usage example can also be found in the [API docs][`juniper_rocket`].
 
 ## Examples
 
-Check [`examples/rocket_server.rs`][1] for example code of a working [`rocket`] server with [GraphQL] handlers.
+Check [`examples/simple.rs`][1] for example code of a working [`rocket`] server with [GraphQL] handlers.
 
 
 
@@ -43,4 +43,4 @@ This project is licensed under [BSD 2-Clause License](https://github.com/graphql
 [Juniper Book]: https://graphql-rust.github.io
 [Rust]: https://www.rust-lang.org
 
-[1]: https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/rocket_server.rs
+[1]: https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/simple.rs
diff --git a/juniper_rocket/examples/rocket_server.rs b/juniper_rocket/examples/rocket_server.rs
deleted file mode 100644
index a37464f65..000000000
--- a/juniper_rocket/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, State};
-
-type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
-
-#[rocket::get("/")]
-fn graphiql() -> content::RawHtml<String> {
-    juniper_rocket::graphiql_source("/graphql", None)
-}
-
-#[rocket::get("/graphql?<request>")]
-fn get_graphql_handler(
-    context: &State<Database>,
-    request: juniper_rocket::GraphQLRequest,
-    schema: &State<Schema>,
-) -> juniper_rocket::GraphQLResponse {
-    request.execute_sync(schema, context)
-}
-
-#[rocket::post("/graphql", data = "<request>")]
-fn post_graphql_handler(
-    context: &State<Database>,
-    request: juniper_rocket::GraphQLRequest,
-    schema: &State<Schema>,
-) -> juniper_rocket::GraphQLResponse {
-    request.execute_sync(schema, context)
-}
-
-#[rocket::main]
-async fn main() {
-    let _ = 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/examples/simple.rs b/juniper_rocket/examples/simple.rs
new file mode 100644
index 000000000..f3a6d905a
--- /dev/null
+++ b/juniper_rocket/examples/simple.rs
@@ -0,0 +1,68 @@
+use juniper::{
+    tests::fixtures::starwars::schema::{Database, Query},
+    EmptyMutation, EmptySubscription, RootNode,
+};
+use rocket::{response::content::RawHtml, routes, State};
+
+type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
+
+#[rocket::get("/")]
+async fn homepage() -> RawHtml<&'static str> {
+    RawHtml(
+        "<html><h1>juniper_rocket/simple example</h1>\
+               <div>visit <a href=\"/graphiql\">GraphiQL</a></div>\
+               <div>visit <a href=\"/playground\">GraphQL Playground</a></div>\
+         </html>",
+    )
+}
+
+#[rocket::get("/graphiql")]
+fn graphiql() -> RawHtml<String> {
+    juniper_rocket::graphiql_source("/graphql", None)
+}
+
+#[rocket::get("/playground")]
+fn playground() -> RawHtml<String> {
+    juniper_rocket::playground_source("/graphql", None)
+}
+
+// GET request accepts query parameters like these:
+// ?query=<urlencoded-graphql-query-string>
+// &operationName=<optional-name>
+// &variables=<optional-json-encoded-variables>
+// See details here: https://graphql.org/learn/serving-over-http#get-request
+#[rocket::get("/graphql?<request..>")]
+async fn get_graphql(
+    db: &State<Database>,
+    request: juniper_rocket::GraphQLRequest,
+    schema: &State<Schema>,
+) -> juniper_rocket::GraphQLResponse {
+    request.execute(schema, db).await
+}
+
+#[rocket::post("/graphql", data = "<request>")]
+async fn post_graphql(
+    db: &State<Database>,
+    request: juniper_rocket::GraphQLRequest,
+    schema: &State<Schema>,
+) -> juniper_rocket::GraphQLResponse {
+    request.execute(schema, db).await
+}
+
+#[rocket::main]
+async fn main() {
+    _ = rocket::build()
+        .manage(Database::new())
+        .manage(Schema::new(
+            Query,
+            EmptyMutation::new(),
+            EmptySubscription::new(),
+        ))
+        .mount(
+            "/",
+            routes![homepage, graphiql, playground, get_graphql, post_graphql],
+        )
+        .launch()
+        .await
+        .expect("server to launch");
+}
diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs
index 436b9a9f3..934047d17 100644
--- a/juniper_rocket/src/lib.rs
+++ b/juniper_rocket/src/lib.rs
@@ -7,7 +7,7 @@ use rocket::{
     form::{error::ErrorKind, DataField, Error, Errors, FromForm, Options, ValueField},
     http::{ContentType, Status},
     outcome::Outcome,
-    response::{self, content, Responder, Response},
+    response::{self, content::RawHtml, Responder, Response},
     Data, Request,
 };
 
@@ -17,11 +17,50 @@ use juniper::{
     InputValue, RootNode, ScalarValue,
 };
 
-/// Simple wrapper around an incoming GraphQL request
+/// Simple wrapper around an incoming GraphQL request.
 ///
-/// See the `http` module for more information. This type can be constructed
-/// automatically from both GET and POST routes by implementing the `FromForm`
-/// and `FromData` traits.
+/// See the [`http`] module for more information. This type can be constructed automatically from
+/// both GET and POST routes, as implements [`FromForm`] and [`FromData`] traits.
+///
+/// # Example
+///
+/// ```rust
+/// use juniper::{
+///     tests::fixtures::starwars::schema::{Database, Query},
+///     EmptyMutation, EmptySubscription, RootNode,
+/// };
+/// use rocket::{routes, State};
+///
+/// type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
+///
+/// // GET request accepts query parameters like these:
+/// // ?query=<urlencoded-graphql-query-string>
+/// // &operationName=<optional-name>
+/// // &variables=<optional-json-encoded-variables>
+/// // See details here: https://graphql.org/learn/serving-over-http#get-request
+/// #[rocket::get("/graphql?<request..>")]
+/// async fn get_graphql_handler(
+///     db: &State<Database>,
+///     request: juniper_rocket::GraphQLRequest,
+///     schema: &State<Schema>,
+/// ) -> juniper_rocket::GraphQLResponse {
+///     request.execute(schema, db).await
+/// }
+///
+/// #[rocket::post("/graphql", data = "<request>")]
+/// async fn post_graphql_handler(
+///     db: &State<Database>,
+///     request: juniper_rocket::GraphQLRequest,
+///     schema: &State<Schema>,
+/// ) -> juniper_rocket::GraphQLResponse {
+///     request.execute(schema, db).await
+/// }
+///
+/// let rocket = rocket::build()
+///     .manage(Database::new())
+///     .manage(Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()))
+///     .mount("/", routes![get_graphql_handler, post_graphql_handler]);
+/// ```
 #[derive(Debug, PartialEq)]
 pub struct GraphQLRequest<S = DefaultScalarValue>(GraphQLBatchRequest<S>)
 where
@@ -42,25 +81,59 @@ impl<S: ScalarValue> AsMut<GraphQLBatchRequest<S>> for GraphQLRequest<S> {
 /// 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(
+/// Generates a [`RawHtml`] page containing [GraphiQL].
+///
+/// This does not handle routing, so you can mount it on any endpoint.
+///
+/// # Example
+///
+/// ```rust
+/// use rocket::{response::content::RawHtml, routes};
+///
+/// #[rocket::get("/graphiql")]
+/// fn graphiql() -> RawHtml<String> {
+///     juniper_rocket::graphiql_source("/graphql", "/subscriptions")
+/// }
+///
+/// let rocket = rocket::build().mount("/", routes![graphiql]);
+/// ```
+///
+/// [GraphiQL]: https://github.com/graphql/graphiql
+pub fn graphiql_source<'a>(
     graphql_endpoint_url: &str,
-    subscriptions_endpoint_url: Option<&str>,
-) -> content::RawHtml<String> {
-    content::RawHtml(juniper::http::graphiql::graphiql_source(
+    subscriptions_endpoint_url: impl Into<Option<&'a str>>,
+) -> RawHtml<String> {
+    RawHtml(http::graphiql::graphiql_source(
         graphql_endpoint_url,
-        subscriptions_endpoint_url,
+        subscriptions_endpoint_url.into(),
     ))
 }
 
-/// Generate an HTML page containing GraphQL Playground
-pub fn playground_source(
+/// Generates a [`RawHtml`] page containing [GraphQL Playground].
+///
+/// This does not handle routing, so you can mount it on any endpoint.
+///
+/// # Example
+///
+/// ```rust
+/// use rocket::{response::content::RawHtml, routes};
+///
+/// #[rocket::get("/playground")]
+/// fn playground() -> RawHtml<String> {
+///     juniper_rocket::playground_source("/graphql", "/subscriptions")
+/// }
+///
+/// let rocket = rocket::build().mount("/", routes![playground]);
+/// ```
+///
+/// [GraphQL Playground]: https://github.com/prisma/graphql-playground
+pub fn playground_source<'a>(
     graphql_endpoint_url: &str,
-    subscriptions_endpoint_url: Option<&str>,
-) -> content::RawHtml<String> {
-    content::RawHtml(juniper::http::playground::playground_source(
+    subscriptions_endpoint_url: impl Into<Option<&'a str>>,
+) -> RawHtml<String> {
+    RawHtml(http::playground::playground_source(
         graphql_endpoint_url,
-        subscriptions_endpoint_url,
+        subscriptions_endpoint_url.into(),
     ))
 }
 
@@ -126,7 +199,7 @@ where
 }
 
 impl GraphQLResponse {
-    /// Constructs an error response outside of the normal execution flow
+    /// Constructs an error response outside of the normal execution flow.
     ///
     /// # Examples
     ///
@@ -194,7 +267,7 @@ impl<'f, S: ScalarValue> GraphQLContext<'f, S> {
 
     fn operation_name(&mut self, value: String) {
         if self.operation_name.is_some() {
-            let error = Error::from(ErrorKind::Duplicate).with_name("operation_name");
+            let error = Error::from(ErrorKind::Duplicate).with_name("operationName");
 
             self.errors.push(error)
         } else {
@@ -243,7 +316,7 @@ where
     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.into()),
-            Some("operation_name") => ctx.operation_name(field.value.into()),
+            Some("operation_name" | "operationName") => ctx.operation_name(field.value.into()),
             Some("variables") => ctx.variables(field.value.into()),
             Some(key) => {
                 if ctx.opts.strict {
@@ -348,30 +421,39 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for GraphQLResponse {
 
 #[cfg(test)]
 mod fromform_tests {
-    use super::*;
-    use juniper::InputValue;
+    use std::borrow::Cow;
+
+    use juniper::{http, InputValue};
     use rocket::{
-        form::{error::ErrorKind, Error, Form, Strict},
+        form::{error::ErrorKind, Error, Errors, Form, Strict},
         http::RawStr,
     };
-    use std::borrow::Cow;
+
+    use super::GraphQLRequest;
 
     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()
+            let res = Form::<Strict<GraphQLRequest>>::parse_encoded(RawStr::new(input));
+
+            assert!(res.is_err(), "result: {:#?}", res.unwrap());
+
+            res.unwrap_err()
         } else {
-            let result = Form::<GraphQLRequest>::parse_encoded(RawStr::new(input));
-            assert!(result.is_err());
-            result.unwrap_err()
+            let res = Form::<GraphQLRequest>::parse_encoded(RawStr::new(input));
+
+            assert!(res.is_err(), "result: {:#?}", res.unwrap());
+
+            res.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);
@@ -379,7 +461,7 @@ mod fromform_tests {
     }
 
     #[test]
-    fn test_empty_form() {
+    fn empty_form() {
         check_error(
             "",
             vec![Error::from(ErrorKind::Missing).with_name("query")],
@@ -388,7 +470,7 @@ mod fromform_tests {
     }
 
     #[test]
-    fn test_no_query() {
+    fn no_query() {
         check_error(
             "operation_name=foo&variables={}",
             vec![Error::from(ErrorKind::Missing).with_name("query")],
@@ -397,7 +479,7 @@ mod fromform_tests {
     }
 
     #[test]
-    fn test_strict() {
+    fn strict() {
         check_error(
             "query=test&foo=bar",
             vec![Error::from(ErrorKind::Unknown).with_name("foo")],
@@ -406,7 +488,7 @@ mod fromform_tests {
     }
 
     #[test]
-    fn test_duplicate_query() {
+    fn duplicate_query() {
         check_error(
             "query=foo&query=bar",
             vec![Error::from(ErrorKind::Duplicate).with_name("query")],
@@ -415,16 +497,16 @@ mod fromform_tests {
     }
 
     #[test]
-    fn test_duplicate_operation_name() {
+    fn duplicate_operation_name() {
         check_error(
-            "query=test&operation_name=op1&operation_name=op2",
-            vec![Error::from(ErrorKind::Duplicate).with_name("operation_name")],
+            "query=test&operationName=op1&operationName=op2",
+            vec![Error::from(ErrorKind::Duplicate).with_name("operationName")],
             false,
         );
     }
 
     #[test]
-    fn test_duplicate_variables() {
+    fn duplicate_variables() {
         check_error(
             "query=test&variables={}&variables={}",
             vec![Error::from(ErrorKind::Duplicate).with_name("variables")],
@@ -433,7 +515,7 @@ mod fromform_tests {
     }
 
     #[test]
-    fn test_variables_invalid_json() {
+    fn variables_invalid_json() {
         check_error(
             "query=test&variables=NOT_JSON",
             vec![Error::from(ErrorKind::Validation(Cow::Owned(
@@ -445,174 +527,45 @@ mod fromform_tests {
     }
 
     #[test]
-    fn test_variables_valid_json() {
+    fn 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".into(),
-            None,
-            Some(variables),
-        )));
+        let expected = GraphQLRequest(http::GraphQLBatchRequest::Single(
+            http::GraphQLRequest::new("test".into(), None, Some(variables)),
+        ));
+
         assert_eq!(result.unwrap(), expected);
     }
 
     #[test]
-    fn test_variables_encoded_json() {
+    fn 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".into(),
-            None,
-            Some(variables),
-        )));
+        let expected = GraphQLRequest(http::GraphQLBatchRequest::Single(
+            http::GraphQLRequest::new("test".into(), None, Some(variables)),
+        ));
+
         assert_eq!(result.unwrap(), expected);
     }
 
     #[test]
-    fn test_url_decode() {
+    fn url_decode() {
         let result: Result<GraphQLRequest, Errors> = Form::parse_encoded(RawStr::new(
-            "query=%25foo%20bar+baz%26%3F&operation_name=test",
+            "query=%25foo%20bar+baz%26%3F&operationName=test",
         ));
-        assert!(result.is_ok());
-        let expected = GraphQLRequest(GraphQLBatchRequest::Single(http::GraphQLRequest::new(
-            "%foo bar baz&?".into(),
-            Some("test".into()),
-            None,
-        )));
-        assert_eq!(result.unwrap(), expected);
-    }
-}
-
-#[cfg(test)]
-mod tests {
-
-    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])
-    }
+        assert!(result.is_ok());
 
-    fn make_rocket_without_routes() -> Rocket<Build> {
-        Rocket::build().manage(Database::new()).manage(Schema::new(
-            Query,
-            EmptyMutation::<Database>::new(),
-            EmptySubscription::<Database>::new(),
-        ))
-    }
+        let expected = GraphQLRequest(http::GraphQLBatchRequest::Single(
+            http::GraphQLRequest::new("%foo bar baz&?".into(), Some("test".into()), None),
+        ));
 
-    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,
-        }
+        assert_eq!(result.unwrap(), expected);
     }
 }


Test patch:
diff --git a/juniper_rocket/tests/http_test_suite.rs b/juniper_rocket/tests/http_test_suite.rs
new file mode 100644
index 000000000..3e7ca823d
--- /dev/null
+++ b/juniper_rocket/tests/http_test_suite.rs
@@ -0,0 +1,155 @@
+use futures::executor;
+use juniper::{
+    http::tests::{run_http_test_suite, HttpIntegration, TestResponse},
+    tests::fixtures::starwars::schema::{Database, Query},
+    EmptyMutation, EmptySubscription, RootNode,
+};
+use juniper_rocket::{GraphQLRequest, GraphQLResponse};
+use rocket::{
+    get,
+    http::ContentType,
+    local::asynchronous::{Client, LocalResponse},
+    post, routes, Build, Rocket, State,
+};
+
+type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscription<Database>>;
+
+fn bootstrap_rocket() -> Rocket<Build> {
+    Rocket::build().manage(Database::new()).manage(Schema::new(
+        Query,
+        EmptyMutation::<Database>::new(),
+        EmptySubscription::<Database>::new(),
+    ))
+}
+
+fn make_rocket() -> Rocket<Build> {
+    #[get("/?<request..>")]
+    async fn get_handler(
+        context: &State<Database>,
+        request: GraphQLRequest,
+        schema: &State<Schema>,
+    ) -> GraphQLResponse {
+        request.execute(schema, context).await
+    }
+
+    #[post("/", data = "<request>")]
+    async fn post_handler(
+        context: &State<Database>,
+        request: GraphQLRequest,
+        schema: &State<Schema>,
+    ) -> GraphQLResponse {
+        request.execute(schema, context).await
+    }
+
+    bootstrap_rocket().mount("/", routes![post_handler, get_handler])
+}
+
+fn make_sync_rocket() -> Rocket<Build> {
+    #[get("/?<request..>")]
+    fn get_handler_sync(
+        context: &State<Database>,
+        request: GraphQLRequest,
+        schema: &State<Schema>,
+    ) -> GraphQLResponse {
+        request.execute_sync(schema, context)
+    }
+
+    #[post("/", data = "<request>")]
+    fn post_handler_sync(
+        context: &State<Database>,
+        request: GraphQLRequest,
+        schema: &State<Schema>,
+    ) -> GraphQLResponse {
+        request.execute_sync(schema, context)
+    }
+
+    bootstrap_rocket().mount("/", routes![post_handler_sync, get_handler_sync])
+}
+
+struct TestRocketIntegration {
+    client: Client,
+}
+
+async fn into_test_response(response: LocalResponse<'_>) -> 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");
+
+    TestResponse {
+        status_code,
+        content_type,
+        body: Some(body),
+    }
+}
+
+impl HttpIntegration for TestRocketIntegration {
+    fn get(&self, url: &str) -> TestResponse {
+        let req = self.client.get(url);
+        let resp = executor::block_on(req.dispatch());
+        executor::block_on(into_test_response(resp))
+    }
+
+    fn post_json(&self, url: &str, body: &str) -> TestResponse {
+        let req = self.client.post(url).header(ContentType::JSON).body(body);
+        let resp = executor::block_on(req.dispatch());
+        executor::block_on(into_test_response(resp))
+    }
+
+    fn post_graphql(&self, url: &str, body: &str) -> TestResponse {
+        let req = self
+            .client
+            .post(url)
+            .header(ContentType::new("application", "graphql"))
+            .body(body);
+        let resp = executor::block_on(req.dispatch());
+        executor::block_on(into_test_response(resp))
+    }
+}
+
+#[rocket::async_test]
+async fn test_rocket_integration() {
+    let rocket = make_rocket();
+    let client = Client::untracked(rocket).await.expect("valid rocket");
+
+    run_http_test_suite(&TestRocketIntegration { client });
+}
+
+#[rocket::async_test]
+async fn test_sync_rocket_integration() {
+    let rocket = make_sync_rocket();
+    let client = Client::untracked(rocket).await.expect("valid rocket");
+
+    run_http_test_suite(&TestRocketIntegration { client });
+}
+
+#[rocket::async_test]
+async fn test_operation_names() {
+    #[post("/", data = "<request>")]
+    async fn post_graphql_assert_operation_name_handler(
+        context: &State<Database>,
+        request: GraphQLRequest,
+        schema: &State<Schema>,
+    ) -> GraphQLResponse {
+        assert_eq!(request.operation_names(), vec![Some("TestQuery")]);
+        request.execute(schema, context).await
+    }
+
+    let rocket = bootstrap_rocket().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 = into_test_response(resp).await;
+
+    assert_eq!(resp.status_code, 200, "response: {resp:#?}");
+}