diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 6f5afa1f..80a88f3f 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -18,11 +18,14 @@ See [#419](https://github.com/graphql-rust/juniper/pull/419). - `SchemaType` is now public - This is helpful when using `context.getSchema()` inside of your field resolvers +- Support subscriptions in GraphiQL + See [#569](https://github.com/graphql-rust/juniper/pull/569). ## Breaking Changes - `juniper::graphiql` has moved to `juniper::http::graphiql` + - `juniper::http::graphiql::graphiql_source` now requies a second parameter for subscriptions - remove old `graphql_object!` macro, rename `object` proc macro to `graphql_object` diff --git a/juniper/src/http/graphiql.rs b/juniper/src/http/graphiql.rs index f79d091e..d1cd12ce 100644 --- a/juniper/src/http/graphiql.rs +++ b/juniper/src/http/graphiql.rs @@ -1,7 +1,23 @@ //! Utility module to generate a GraphiQL interface /// Generate the HTML source to show a GraphiQL interface -pub fn graphiql_source(graphql_endpoint_url: &str) -> String { +/// +/// The subscriptions endpoint URL can optionally be provided. For example: +/// +/// ``` +/// # use juniper::http::graphiql::graphiql_source; +/// let graphiql = graphiql_source("/graphql", Some("ws://localhost:8080/subscriptions")); +/// ``` +pub fn graphiql_source( + graphql_endpoint_url: &str, + subscriptions_endpoint_url: Option<&str>, +) -> String { + let subscriptions_endpoint = if let Some(sub_url) = subscriptions_endpoint_url { + sub_url + } else { + "" + }; + let stylesheet_source = r#" <style> html, body, #app { @@ -14,6 +30,10 @@ pub fn graphiql_source(graphql_endpoint_url: &str) -> String { "#; let fetcher_source = r#" <script> + if (usingSubscriptions) { + var subscriptionsClient = new window.SubscriptionsTransportWs.SubscriptionClient(GRAPHQL_SUBSCRIPTIONS_URL, { reconnect: true }); + } + function graphQLFetcher(params) { return fetch(GRAPHQL_URL, { method: 'post', @@ -33,9 +53,12 @@ pub fn graphiql_source(graphql_endpoint_url: &str) -> String { } }); } + + var fetcher = usingSubscriptions ? window.GraphiQLSubscriptionsFetcher.graphQLFetcher(subscriptionsClient, graphQLFetcher) : graphQLFetcher; + ReactDOM.render( React.createElement(GraphiQL, { - fetcher: graphQLFetcher, + fetcher, }), document.querySelector('#app')); </script> @@ -53,16 +76,22 @@ pub fn graphiql_source(graphql_endpoint_url: &str) -> String { <body> <div id="app"></div> <script src="//cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.js"></script> + <script src="//unpkg.com/subscriptions-transport-ws@0.8.3/browser/client.js"></script> + <script src="//unpkg.com/graphiql-subscriptions-fetcher@0.0.2/browser/client.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script> <script src="//cdn.jsdelivr.net/npm/graphiql@0.17.5/graphiql.min.js"></script> <script>var GRAPHQL_URL = '{graphql_url}';</script> + <script>var usingSubscriptions = {using_subscriptions};</script> + <script>var GRAPHQL_SUBSCRIPTIONS_URL = '{graphql_subscriptions_url}';</script> {fetcher_source} </body> </html> "#, graphql_url = graphql_endpoint_url, stylesheet_source = stylesheet_source, - fetcher_source = fetcher_source + fetcher_source = fetcher_source, + graphql_subscriptions_url = subscriptions_endpoint, + using_subscriptions = subscriptions_endpoint_url.is_some(), ) } diff --git a/juniper_hyper/CHANGELOG.md b/juniper_hyper/CHANGELOG.md index 9e139297..1acd9f3b 100644 --- a/juniper_hyper/CHANGELOG.md +++ b/juniper_hyper/CHANGELOG.md @@ -2,6 +2,10 @@ - Compatibility with the latest `juniper`. +## Breaking Changes + +- `juniper_hyper::graphiql` now requires a second parameter for subscriptions + # [[0.5.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_hyper-0.5.2) - Compatibility with the latest `juniper`. diff --git a/juniper_hyper/examples/hyper_server.rs b/juniper_hyper/examples/hyper_server.rs index ff6e586e..0d638d8e 100644 --- a/juniper_hyper/examples/hyper_server.rs +++ b/juniper_hyper/examples/hyper_server.rs @@ -31,7 +31,7 @@ async fn main() { let ctx = ctx.clone(); async move { match (req.method(), req.uri().path()) { - (&Method::GET, "/") => juniper_hyper::graphiql("/graphql").await, + (&Method::GET, "/") => juniper_hyper::graphiql("/graphql", None).await, (&Method::GET, "/graphql") | (&Method::POST, "/graphql") => { juniper_hyper::graphql(root_node, ctx, req).await } diff --git a/juniper_hyper/src/lib.rs b/juniper_hyper/src/lib.rs index 8fe2bda4..710a609c 100644 --- a/juniper_hyper/src/lib.rs +++ b/juniper_hyper/src/lib.rs @@ -114,10 +114,16 @@ async fn parse_post_req<S: ScalarValue>( .map_err(GraphQLRequestError::BodyJSONError) } -pub async fn graphiql(graphql_endpoint: &str) -> Result<Response<Body>, hyper::Error> { +pub async fn graphiql( + graphql_endpoint: &str, + subscriptions_endpoint: Option<&str>, +) -> Result<Response<Body>, hyper::Error> { let mut resp = new_html_response(StatusCode::OK); // XXX: is the call to graphiql_source blocking? - *resp.body_mut() = Body::from(juniper::http::graphiql::graphiql_source(graphql_endpoint)); + *resp.body_mut() = Body::from(juniper::http::graphiql::graphiql_source( + graphql_endpoint, + subscriptions_endpoint, + )); Ok(resp) } diff --git a/juniper_iron/CHANGELOG.md b/juniper_iron/CHANGELOG.md index db3eb30d..83d83dad 100644 --- a/juniper_iron/CHANGELOG.md +++ b/juniper_iron/CHANGELOG.md @@ -2,6 +2,10 @@ - Compatibility with the latest `juniper`. +## Breaking Changes + +- `juniper_iron::GraphiQLHandler::new` now requires a second parameter for subscriptions + # [[0.6.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_iron-0.6.2) - Compatibility with the latest `juniper`. diff --git a/juniper_iron/examples/iron_server.rs b/juniper_iron/examples/iron_server.rs index 20cc5dee..244403a9 100644 --- a/juniper_iron/examples/iron_server.rs +++ b/juniper_iron/examples/iron_server.rs @@ -29,7 +29,7 @@ fn main() { EmptyMutation::<Database>::new(), EmptySubscription::<Database>::new(), ); - let graphiql_endpoint = GraphiQLHandler::new("/graphql"); + let graphiql_endpoint = GraphiQLHandler::new("/graphql", None); mount.mount("/", graphiql_endpoint); mount.mount("/graphql", graphql_endpoint); diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index fc2ba9d1..de704574 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -159,6 +159,7 @@ pub struct GraphQLHandler< /// Handler that renders `GraphiQL` - a graphical query editor interface pub struct GraphiQLHandler { graphql_url: String, + subscription_url: Option<String>, } /// Handler that renders `GraphQL Playground` - a graphical query editor interface @@ -275,9 +276,10 @@ impl GraphiQLHandler { /// /// The provided URL should point to the URL of the attached `GraphQLHandler`. It can be /// relative, so a common value could be `"/graphql"`. - pub fn new(graphql_url: &str) -> GraphiQLHandler { + pub fn new(graphql_url: &str, subscription_url: Option<&str>) -> GraphiQLHandler { GraphiQLHandler { graphql_url: graphql_url.to_owned(), + subscription_url: subscription_url.map(|s| s.to_owned()), } } } @@ -326,7 +328,10 @@ impl Handler for GraphiQLHandler { Ok(Response::with(( content_type, status::Ok, - juniper::http::graphiql::graphiql_source(&self.graphql_url), + juniper::http::graphiql::graphiql_source( + &self.graphql_url, + self.subscription_url.as_deref(), + ), ))) } } diff --git a/juniper_rocket/CHANGELOG.md b/juniper_rocket/CHANGELOG.md index 31c0c22b..5359987b 100644 --- a/juniper_rocket/CHANGELOG.md +++ b/juniper_rocket/CHANGELOG.md @@ -3,6 +3,10 @@ - Compatibility with the latest `juniper`. - Rocket integration does not require default features. +## Breaking Changes + +- `juniper_rocket::graphiql_source` now requires a second parameter for subscriptions + # [[0.5.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_rocket-0.5.2) - Compatibility with the latest `juniper`. diff --git a/juniper_rocket/examples/rocket_server.rs b/juniper_rocket/examples/rocket_server.rs index f352c36e..b0065a3d 100644 --- a/juniper_rocket/examples/rocket_server.rs +++ b/juniper_rocket/examples/rocket_server.rs @@ -11,7 +11,7 @@ type Schema = RootNode<'static, Query, EmptyMutation<Database>, EmptySubscriptio #[rocket::get("/")] fn graphiql() -> content::Html<String> { - juniper_rocket::graphiql_source("/graphql") + juniper_rocket::graphiql_source("/graphql", None) } #[rocket::get("/graphql?<request>")] diff --git a/juniper_rocket/src/lib.rs b/juniper_rocket/src/lib.rs index c4a027c5..8816ac8d 100644 --- a/juniper_rocket/src/lib.rs +++ b/juniper_rocket/src/lib.rs @@ -71,9 +71,13 @@ where pub struct GraphQLResponse(pub Status, pub String); /// Generate an HTML page containing GraphiQL -pub fn graphiql_source(graphql_endpoint_url: &str) -> content::Html<String> { +pub fn graphiql_source( + graphql_endpoint_url: &str, + subscriptions_endpoint: Option<&str>, +) -> content::Html<String> { content::Html(juniper::http::graphiql::graphiql_source( graphql_endpoint_url, + subscriptions_endpoint, )) } diff --git a/juniper_rocket_async/src/lib.rs b/juniper_rocket_async/src/lib.rs index 892311c5..efbb8c30 100644 --- a/juniper_rocket_async/src/lib.rs +++ b/juniper_rocket_async/src/lib.rs @@ -74,6 +74,7 @@ pub struct GraphQLResponse(pub Status, pub String); pub fn graphiql_source(graphql_endpoint_url: &str) -> content::Html<String> { content::Html(juniper::http::graphiql::graphiql_source( graphql_endpoint_url, + None, )) } diff --git a/juniper_warp/CHANGELOG.md b/juniper_warp/CHANGELOG.md index dbfb9967..9cbd277e 100644 --- a/juniper_warp/CHANGELOG.md +++ b/juniper_warp/CHANGELOG.md @@ -9,6 +9,7 @@ to `juniper` to be reused in other http integrations, since this implementation - Update `playground_filter` to support subscription endpoint URLs - Update `warp` to 0.2 - Rename synchronous `execute` to `execute_sync`, add asynchronous `execute` +- `juniper_warp::graphiql_filter` now requires a second parameter for subscriptions # [[0.5.2] 2019-12-16](https://github.com/graphql-rust/juniper/releases/tag/juniper_warp-0.5.2) diff --git a/juniper_warp/examples/warp_server.rs b/juniper_warp/examples/warp_server.rs index a6eb5217..7939c6ee 100644 --- a/juniper_warp/examples/warp_server.rs +++ b/juniper_warp/examples/warp_server.rs @@ -41,7 +41,7 @@ async fn main() { warp::serve( warp::get() .and(warp::path("graphiql")) - .and(juniper_warp::graphiql_filter("/graphql")) + .and(juniper_warp::graphiql_filter("/graphql", None)) .or(homepage) .or(warp::path("graphql").and(graphql_filter)) .with(log), diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index 19f4591a..471d68bc 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -299,20 +299,41 @@ type Response = Pin< /// # use warp::Filter; /// # use juniper_warp::graphiql_filter; /// # -/// let graphiql_route = warp::path("graphiql").and(graphiql_filter("/graphql")); +/// let graphiql_route = warp::path("graphiql").and(graphiql_filter("/graphql", +/// None)); +/// ``` +/// +/// Or with subscriptions support, provide the subscriptions endpoint URL: +/// +/// ``` +/// # extern crate warp; +/// # extern crate juniper_warp; +/// # +/// # use warp::Filter; +/// # use juniper_warp::graphiql_filter; +/// # +/// let graphiql_route = warp::path("graphiql").and(graphiql_filter("/graphql", +/// Some("ws://localhost:8080/subscriptions"))); /// ``` pub fn graphiql_filter( graphql_endpoint_url: &'static str, + subscriptions_endpoint: Option<&'static str>, ) -> warp::filters::BoxedFilter<(warp::http::Response<Vec<u8>>,)> { warp::any() - .map(move || graphiql_response(graphql_endpoint_url)) + .map(move || graphiql_response(graphql_endpoint_url, subscriptions_endpoint)) .boxed() } -fn graphiql_response(graphql_endpoint_url: &'static str) -> warp::http::Response<Vec<u8>> { +fn graphiql_response( + graphql_endpoint_url: &'static str, + subscriptions_endpoint: Option<&'static str>, +) -> warp::http::Response<Vec<u8>> { warp::http::Response::builder() .header("content-type", "text/html;charset=utf-8") - .body(juniper::http::graphiql::graphiql_source(graphql_endpoint_url).into_bytes()) + .body( + juniper::http::graphiql::graphiql_source(graphql_endpoint_url, subscriptions_endpoint) + .into_bytes(), + ) .expect("response is valid") } @@ -568,14 +589,14 @@ mod tests { #[test] fn graphiql_response_does_not_panic() { - graphiql_response("/abcd"); + graphiql_response("/abcd", None); } #[tokio::test] async fn graphiql_endpoint_matches() { let filter = warp::get() .and(warp::path("graphiql")) - .and(graphiql_filter("/graphql")); + .and(graphiql_filter("/graphql", None)); let result = request() .method("GET") .path("/graphiql") @@ -591,7 +612,7 @@ mod tests { let filter = warp::get() .and(warp::path("dogs-api")) .and(warp::path("graphiql")) - .and(graphiql_filter("/dogs-api/graphql")); + .and(graphiql_filter("/dogs-api/graphql", None)); let response = request() .method("GET") .path("/dogs-api/graphiql") @@ -609,6 +630,22 @@ mod tests { assert!(body.contains("<script>var GRAPHQL_URL = '/dogs-api/graphql';</script>")); } + #[tokio::test] + async fn graphiql_endpoint_with_subscription_matches() { + let filter = warp::get().and(warp::path("graphiql")).and(graphiql_filter( + "/graphql", + Some("ws:://localhost:8080/subscriptions"), + )); + let result = request() + .method("GET") + .path("/graphiql") + .header("accept", "text/html") + .filter(&filter) + .await; + + assert!(result.is_ok()); + } + #[tokio::test] async fn playground_endpoint_matches() { let filter = warp::get()