From d7103e2ef2e648fef8888575d36818171ea256c3 Mon Sep 17 00:00:00 2001 From: Kai Ren Date: Fri, 10 Nov 2023 04:52:24 +0100 Subject: [PATCH] Remove `iron` integration (#1210) --- .github/workflows/ci.yml | 3 - Cargo.toml | 1 - README.md | 8 +- book/src/README.md | 5 +- book/src/SUMMARY.md | 1 - book/src/quickstart.md | 1 - book/src/servers/index.md | 1 - book/src/servers/iron.md | 122 ------- book/src/servers/official.md | 1 - book/src/types/objects/using_contexts.md | 3 +- juniper/README.md | 3 - juniper/release.toml | 11 - juniper/src/executor/mod.rs | 2 +- juniper_iron/CHANGELOG.md | 26 -- juniper_iron/Cargo.toml | 34 -- juniper_iron/LICENSE | 25 -- juniper_iron/README.md | 46 --- juniper_iron/examples/iron_server.rs | 39 --- juniper_iron/release.toml | 18 - juniper_iron/src/lib.rs | 410 ----------------------- 20 files changed, 6 insertions(+), 754 deletions(-) delete mode 100644 book/src/servers/iron.md delete mode 100644 juniper_iron/CHANGELOG.md delete mode 100644 juniper_iron/Cargo.toml delete mode 100644 juniper_iron/LICENSE delete mode 100644 juniper_iron/README.md delete mode 100644 juniper_iron/examples/iron_server.rs delete mode 100644 juniper_iron/release.toml delete mode 100644 juniper_iron/src/lib.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fda357262..fb649b667 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -152,7 +152,6 @@ jobs: - juniper_actix - juniper_axum - juniper_hyper - #- juniper_iron - juniper_rocket - juniper_warp os: @@ -205,7 +204,6 @@ jobs: - juniper_actix - juniper_axum - juniper_hyper - - juniper_iron - juniper_rocket - juniper_warp os: @@ -332,7 +330,6 @@ jobs: - juniper_actix - juniper_axum - juniper_hyper - - juniper_iron - juniper_rocket - juniper_warp runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index ea4d8e2c7..67c76dd0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ "juniper_codegen", "juniper", "juniper_hyper", - "juniper_iron", "juniper_rocket", "juniper_subscriptions", "juniper_graphql_ws", diff --git a/README.md b/README.md index 3aa641e5b..b10a67871 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ GraphQL schemas as convenient as Rust will allow. Juniper does not include a web server - instead it provides building blocks to make integration with existing servers straightforward. It optionally provides a -pre-built integration for the [Actix][actix], [Hyper][hyper], [Iron][iron], [Rocket], and [Warp][warp] frameworks, including +pre-built integration for the [Actix][actix], [Hyper][hyper], [Rocket], and [Warp][warp] frameworks, including embedded [Graphiql][graphiql] and [GraphQL Playground][playground] for easy debugging. - [Cargo crate](https://crates.io/crates/juniper) @@ -42,7 +42,7 @@ For specific information about macros, types and the Juniper api, the You can also check out the [Star Wars schema][test_schema_rs] to see a complex example including polymorphism with traits and interfaces. For an example of web framework integration, -see the [actix][actix_examples], [hyper][hyper_examples], [rocket][rocket_examples], [iron][iron_examples], and [warp][warp_examples] examples folders. +see the [actix][actix_examples], [axum][axum_examples], [hyper][hyper_examples], [rocket][rocket_examples], and [warp][warp_examples] examples folders. ## Features @@ -82,7 +82,6 @@ your Schemas automatically. - [axum][axum] - [hyper][hyper] - [rocket][rocket] -- [iron][iron] - [warp][warp] ## Guides & Examples @@ -98,16 +97,15 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. [graphql]: http://graphql.org [graphiql]: https://github.com/graphql/graphiql [playground]: https://github.com/prisma/graphql-playground -[iron]: https://github.com/iron/iron [graphql_spec]: https://spec.graphql.org/October2021 [schema_language]: https://graphql.org/learn/schema/#type-language [schema_approach]: https://blog.logrocket.com/code-first-vs-schema-first-development-graphql/ [test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/fixtures/starwars/schema.rs [tokio]: https://github.com/tokio-rs/tokio [actix_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_actix/examples +[axum_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_axum/examples [hyper_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_hyper/examples [rocket_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_rocket/examples -[iron_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_iron/examples [hyper]: https://hyper.rs [rocket]: https://rocket.rs [book]: https://graphql-rust.github.io diff --git a/book/src/README.md b/book/src/README.md index 367f16cd5..0b0692b1c 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -12,7 +12,7 @@ GraphQL schemas as convenient as possible as Rust will allow. Juniper does not include a web server - instead it provides building blocks to make integration with existing servers straightforward. It optionally provides a -pre-built integration for the [Hyper][hyper], [Iron][iron], [Rocket], and [Warp][warp] frameworks, including +pre-built integration for the [Hyper][hyper], [Rocket], and [Warp][warp] frameworks, including embedded [Graphiql][graphiql] for easy debugging. - [Cargo crate](https://crates.io/crates/juniper) @@ -47,7 +47,6 @@ your Schemas automatically. - [hyper][hyper] - [rocket][rocket] -- [iron][iron] - [warp][warp] ## API Stability @@ -56,13 +55,11 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. [graphql]: http://graphql.org [graphiql]: https://github.com/graphql/graphiql -[iron]: https://github.com/iron/iron [graphql_spec]: https://spec.graphql.org/October2021 [test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/schema.rs [tokio]: https://github.com/tokio-rs/tokio [hyper_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_hyper/examples [rocket_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_rocket/examples -[iron_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_iron/examples [hyper]: https://hyper.rs [rocket]: https://rocket.rs [book]: https://graphql-rust.github.io diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 24ea171ff..eb4013ae9 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -21,7 +21,6 @@ - [Official Server Integrations](servers/official.md) - [Hyper](servers/hyper.md) - [Warp](servers/warp.md) - [Rocket](servers/rocket.md) - - [Iron](servers/iron.md) - [Hyper](servers/hyper.md) - [Third Party Integrations](servers/third-party.md) diff --git a/book/src/quickstart.md b/book/src/quickstart.md index 770b86a61..a8633414e 100644 --- a/book/src/quickstart.md +++ b/book/src/quickstart.md @@ -205,6 +205,5 @@ fn main() { [hyper]: servers/hyper.md [warp]: servers/warp.md [rocket]: servers/rocket.md -[iron]: servers/iron.md [tutorial]: ./tutorial.html [graphql_object]: https://docs.rs/juniper/latest/juniper/macro.graphql_object.html diff --git a/book/src/servers/index.md b/book/src/servers/index.md index a2ca6d6c5..4f03e0d98 100644 --- a/book/src/servers/index.md +++ b/book/src/servers/index.md @@ -9,7 +9,6 @@ third-party integration crates that will get you there. - [Official Server Integrations](official.md) - [Warp](warp.md) - [Rocket](rocket.md) - - [Iron](iron.md) - [Hyper](hyper.md) - [Third Party Integrations](third-party.md) - [Actix-Web](https://github.com/actix/examples/tree/master/graphql/juniper) diff --git a/book/src/servers/iron.md b/book/src/servers/iron.md deleted file mode 100644 index 38782c368..000000000 --- a/book/src/servers/iron.md +++ /dev/null @@ -1,122 +0,0 @@ -# Integrating with Iron - -[Iron] is a library that's been around for a while in the Rust sphere but lately -hasn't seen much of development. Nevertheless, it's still a solid library with a -familiar request/response/middleware architecture that works on Rust's stable -channel. - -Juniper's Iron integration is contained in the `juniper_iron` crate: - -!FILENAME Cargo.toml - -```toml -[dependencies] -juniper = "0.16.0" -juniper_iron = "0.8.0" -``` - -Included in the source is a [small -example](https://github.com/graphql-rust/juniper_iron/blob/master/examples/iron_server.rs) -which sets up a basic GraphQL and [GraphiQL] handler. - -## Basic integration - -Let's start with a minimal schema and just get a GraphQL endpoint up and -running. We use [mount] to attach the GraphQL handler at `/graphql`. - -The `context_factory` function will be executed on every request and can be used -to set up database connections, read session token information from cookies, and -set up other global data that the schema might require. - -In this example, we won't use any global data so we just return an empty value. - -```rust,ignore -extern crate juniper; -extern crate juniper_iron; -extern crate iron; -extern crate mount; - -use mount::Mount; -use iron::prelude::*; -use juniper::EmptyMutation; -use juniper_iron::GraphQLHandler; - -fn context_factory(_: &mut Request) -> IronResult<()> { - Ok(()) -} - -struct Root; - -#[juniper::graphql_object] -impl Root { - fn foo() -> String { - "Bar".into() - } -} - -# #[allow(unreachable_code, unused_variables)] -fn main() { - let mut mount = Mount::new(); - - let graphql_endpoint = GraphQLHandler::new( - context_factory, - Root, - EmptyMutation::<()>::new(), - ); - - mount.mount("/graphql", graphql_endpoint); - - let chain = Chain::new(mount); - -# return; - Iron::new(chain).http("0.0.0.0:8080").unwrap(); -} -``` - -## Accessing data from the request - -If you want to access e.g. the source IP address of the request from a field -resolver, you need to pass this data using Juniper's [context feature](../types/objects/using_contexts.md). - -```rust,ignore -# extern crate juniper; -# extern crate juniper_iron; -# extern crate iron; -# use iron::prelude::*; -use std::net::SocketAddr; - -struct Context { - remote_addr: SocketAddr, -} - -impl juniper::Context for Context {} - -fn context_factory(req: &mut Request) -> IronResult { - Ok(Context { - remote_addr: req.remote_addr - }) -} - -struct Root; - -#[juniper::graphql_object( - Context = Context, -)] -impl Root { - field my_addr(context: &Context) -> String { - format!("Hello, you're coming from {}", context.remote_addr) - } -} - -# fn main() { -# let _graphql_endpoint = juniper_iron::GraphQLHandler::new( -# context_factory, -# Root, -# juniper::EmptyMutation::::new(), -# ); -# } -``` - -[iron]: https://github.com/iron/iron -[graphiql]: https://github.com/graphql/graphiql -[mount]: https://github.com/iron/mount diff --git a/book/src/servers/official.md b/book/src/servers/official.md index d5e2878ae..690fc2fca 100644 --- a/book/src/servers/official.md +++ b/book/src/servers/official.md @@ -5,5 +5,4 @@ libraries. - [Warp](warp.md) - [Rocket](rocket.md) -- [Iron](iron.md) - [Hyper](hyper.md) diff --git a/book/src/types/objects/using_contexts.md b/book/src/types/objects/using_contexts.md index c917e087c..b63f90b80 100644 --- a/book/src/types/objects/using_contexts.md +++ b/book/src/types/objects/using_contexts.md @@ -4,8 +4,7 @@ The context type is a feature in Juniper that lets field resolvers access global data, most commonly database connections or authentication information. The context is usually created from a _context factory_. How this is defined is specific to the framework integration you're using, so check out the -documentation for either the [Iron](../../servers/iron.md) or [Rocket](../../servers/rocket.md) -integration. +documentation for [Rocket](../../servers/rocket.md) integration. In this chapter, we'll show you how to define a context type and use it in field resolvers. Let's say that we have a simple user database in a `HashMap`: diff --git a/juniper/README.md b/juniper/README.md index b72bbd6c3..9f108d3dd 100644 --- a/juniper/README.md +++ b/juniper/README.md @@ -60,7 +60,6 @@ As an exception to other [GraphQL] libraries for other languages, [Juniper] buil - [`actix-web`] ([`juniper_actix`] crate) - [`axum`] ([`juniper_axum`] crate) - [`hyper`] ([`juniper_hyper`] crate) -- [`iron`] ([`juniper_iron`] crate) - [`rocket`] ([`juniper_rocket`] crate) - [`warp`] ([`juniper_warp`] crate) @@ -90,11 +89,9 @@ This project is licensed under [BSD 2-Clause License](https://github.com/graphql [`juniper_actix`]: https://docs.rs/juniper_actix [`juniper_axum`]: https://docs.rs/juniper_axum [`juniper_hyper`]: https://docs.rs/juniper_hyper -[`juniper_iron`]: https://docs.rs/juniper_iron [`juniper_rocket`]: https://docs.rs/juniper_rocket [`juniper_warp`]: https://docs.rs/juniper_warp [`hyper`]: https://docs.rs/hyper -[`iron`]: https://docs.rs/iron [`rocket`]: https://docs.rs/rocket [`rust_decimal`]: https://docs.rs/rust_decimal [`time`]: https://docs.rs/time diff --git a/juniper/release.toml b/juniper/release.toml index fe1d02018..5da76ef0c 100644 --- a/juniper/release.toml +++ b/juniper/release.toml @@ -19,11 +19,6 @@ exactly = 1 search = "juniper = \"[^\"]+\"" replace = "juniper = \"{{version}}\"" [[pre-release-replacements]] -file = "../book/src/servers/iron.md" -exactly = 1 -search = "juniper = \"[^\"]+\"" -replace = "juniper = \"{{version}}\"" -[[pre-release-replacements]] file = "../book/src/servers/rocket.md" exactly = 1 search = "juniper = \"[^\"]+\"" @@ -58,12 +53,6 @@ exactly = 2 search = "juniper = \\{ version = \"[^\"]+\"" replace = "juniper = { version = \"{{version}}\"" -[[pre-release-replacements]] -file = "../juniper_iron/Cargo.toml" -exactly = 2 -search = "juniper = \\{ version = \"[^\"]+\"" -replace = "juniper = { version = \"{{version}}\"" - [[pre-release-replacements]] file = "../juniper_rocket/Cargo.toml" exactly = 2 diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index d89ec1e2c..83f69446a 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -626,7 +626,7 @@ where /// Access the current context /// /// You usually provide the context when calling the top-level `execute` - /// function, or using the context factory in the Iron integration. + /// function, or using the context factory. pub fn context(&self) -> &'r CtxT { self.context } diff --git a/juniper_iron/CHANGELOG.md b/juniper_iron/CHANGELOG.md deleted file mode 100644 index 467e15688..000000000 --- a/juniper_iron/CHANGELOG.md +++ /dev/null @@ -1,26 +0,0 @@ -`juniper_iron` changelog -======================== - -All user visible changes to `juniper_iron` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0]. - - - - -## master - -### BC Breaks - -- Switched to 0.16 version of [`juniper` crate]. - - - - -## Previous releases - -See [old CHANGELOG](/../../blob/juniper_iron-v0.7.6/juniper_iron/CHANGELOG.md). - - - - -[`juniper` crate]: https://docs.rs/juniper -[Semantic Versioning 2.0.0]: https://semver.org diff --git a/juniper_iron/Cargo.toml b/juniper_iron/Cargo.toml deleted file mode 100644 index 5e1877e16..000000000 --- a/juniper_iron/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "juniper_iron" -version = "0.8.0-dev" -edition = "2021" -rust-version = "1.73" -description = "`juniper` GraphQL integration with `iron`." -license = "BSD-2-Clause" -authors = [ - "Magnus Hallin ", - "Christoph Herzog ", -] -documentation = "https://docs.rs/juniper_iron" -homepage = "https://github.com/graphql-rust/juniper/tree/master/juniper_iron" -repository = "https://github.com/graphql-rust/juniper" -readme = "README.md" -categories = ["web-programming", "web-programming::http-server"] -keywords = ["apollo", "graphql", "iron", "juniper"] -exclude = ["/examples/", "/release.toml"] - -[dependencies] -futures = "0.3.22" -iron = ">= 0.5, < 0.7" -juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } -serde_json = "1.0.18" -urlencoded = ">= 0.5, < 0.7" - -[dev-dependencies] -iron-test = "0.6" -juniper = { version = "0.16.0-dev", path = "../juniper", features = ["expose-test-schema"] } -logger = "0.4" -mount = "0.4" -percent-encoding = "2.0" -router = "0.6" -url = "2.0" diff --git a/juniper_iron/LICENSE b/juniper_iron/LICENSE deleted file mode 100644 index f1c4508b1..000000000 --- a/juniper_iron/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -BSD 2-Clause License - -Copyright (c) 2016-2022, 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. diff --git a/juniper_iron/README.md b/juniper_iron/README.md deleted file mode 100644 index 4d964ef04..000000000 --- a/juniper_iron/README.md +++ /dev/null @@ -1,46 +0,0 @@ -`juniper_iron` crate -==================== - -[![Crates.io](https://img.shields.io/crates/v/juniper_iron.svg?maxAge=2592000)](https://crates.io/crates/juniper_iron) -[![Documentation](https://docs.rs/juniper_iron/badge.svg)](https://docs.rs/juniper_iron) -[![CI](https://github.com/graphql-rust/juniper/workflows/CI/badge.svg?branch=master "CI")](https://github.com/graphql-rust/juniper/actions?query=workflow%3ACI+branch%3Amaster) -[![Rust 1.73+](https://img.shields.io/badge/rustc-1.73+-lightgray.svg "Rust 1.73+")](https://blog.rust-lang.org/2023/10/05/Rust-1.73.0.html) - -- [Changelog](https://github.com/graphql-rust/juniper/blob/master/juniper_iron/CHANGELOG.md) - -[`iron`] web framework integration for [`juniper`] ([GraphQL] implementation for [Rust]). - - - - -## Documentation - -For documentation, including guides and examples, check out [Juniper Book]. - -A basic usage example can also be found in the [API docs][`juniper_iron`]. - - - - -## Examples - -Check [`examples/iron_server.rs`][1] for example code of a working [`iron`] server with [GraphQL] handlers. - - - - -## License - -This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/master/juniper_iron/LICENSE). - - - - -[`iron`]: https://docs.rs/iron -[`juniper`]: https://docs.rs/juniper -[`juniper_iron`]: https://docs.rs/juniper_iron -[GraphQL]: http://graphql.org -[Juniper Book]: https://graphql-rust.github.io -[Rust]: https://www.rust-lang.org - -[1]: https://github.com/graphql-rust/juniper/blob/master/juniper_iron/examples/iron_server.rs diff --git a/juniper_iron/examples/iron_server.rs b/juniper_iron/examples/iron_server.rs deleted file mode 100644 index 218af60b6..000000000 --- a/juniper_iron/examples/iron_server.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::env; - -use iron::prelude::*; -use juniper::{ - tests::fixtures::starwars::schema::{Database, Query}, - DefaultScalarValue, EmptyMutation, EmptySubscription, -}; -use juniper_iron::{GraphQLHandler, GraphiQLHandler}; -use logger::Logger; -use mount::Mount; - -fn context_factory(_: &mut Request) -> IronResult { - Ok(Database::new()) -} - -fn main() { - let mut mount = Mount::new(); - - let graphql_endpoint = >::new( - context_factory, - Query, - EmptyMutation::::new(), - EmptySubscription::::new(), - ); - let graphiql_endpoint = GraphiQLHandler::new("/graphql", None); - - mount.mount("/", graphiql_endpoint); - mount.mount("/graphql", graphql_endpoint); - - let (logger_before, logger_after) = Logger::new(None); - - let mut chain = Chain::new(mount); - chain.link_before(logger_before); - chain.link_after(logger_after); - - let host = env::var("LISTEN").unwrap_or_else(|_| "0.0.0.0:8080".into()); - println!("GraphQL server started on {host}"); - Iron::new(chain).http(host.as_str()).unwrap(); -} diff --git a/juniper_iron/release.toml b/juniper_iron/release.toml deleted file mode 100644 index b965a7831..000000000 --- a/juniper_iron/release.toml +++ /dev/null @@ -1,18 +0,0 @@ -[[pre-release-replacements]] -file = "../book/src/servers/iron.md" -exactly = 1 -search = "juniper_iron = \"[^\"]+\"" -replace = "juniper_iron = \"{{version}}\"" - -[[pre-release-replacements]] -file = "CHANGELOG.md" -max = 1 -min = 0 -search = "## master" -replace = "## [{{version}}] ยท {{date}}\n[{{version}}]: /../../tree/{{crate_name}}-v{{version}}/{{crate_name}}" - -[[pre-release-replacements]] -file = "README.md" -exactly = 3 -search = "graphql-rust/juniper/blob/[^/]+/" -replace = "graphql-rust/juniper/blob/{{crate_name}}-v{{version}}/" diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs deleted file mode 100644 index b8829614f..000000000 --- a/juniper_iron/src/lib.rs +++ /dev/null @@ -1,410 +0,0 @@ -#![doc = include_str!("../README.md")] - -use std::{error::Error, fmt, io::Read, ops::Deref as _}; - -use iron::{ - headers::ContentType, - itry, method, - middleware::Handler, - mime::{Mime, TopLevel}, - prelude::*, - status, -}; -use juniper::{ - http, http::GraphQLBatchRequest, DefaultScalarValue, GraphQLType, InputValue, RootNode, - ScalarValue, -}; -use serde_json::error::Error as SerdeError; -use urlencoded::{UrlDecodingError, UrlEncodedQuery}; - -/// Handler that executes `GraphQL` queries in the given schema -/// -/// The handler responds to GET requests and POST requests only. In GET -/// requests, the query should be supplied in the `query` URL parameter, e.g. -/// `http://localhost:3000/graphql?query={hero{name}}`. -/// -/// POST requests support both queries and variables. POST a JSON document to -/// this endpoint containing the field `"query"` and optionally `"variables"`. -/// The variables should be a JSON object containing the variable to value -/// mapping. -pub struct GraphQLHandler< - 'a, - CtxFactory, - Query, - Mutation, - Subscription, - CtxT, - S = DefaultScalarValue, -> where - S: ScalarValue, - CtxFactory: Fn(&mut Request) -> IronResult + Send + Sync + 'static, - CtxT: 'static, - Query: GraphQLType + Send + Sync + 'static, - Mutation: GraphQLType + Send + Sync + 'static, - Subscription: GraphQLType + Send + Sync + 'static, -{ - context_factory: CtxFactory, - root_node: RootNode<'a, Query, Mutation, Subscription, S>, -} - -/// Handler that renders `GraphiQL` - a graphical query editor interface -pub struct GraphiQLHandler { - graphql_url: String, - subscription_url: Option, -} - -/// Handler that renders `GraphQL Playground` - a graphical query editor interface -pub struct PlaygroundHandler { - graphql_url: String, - subscription_url: Option, -} - -fn get_single_value(mut values: Vec) -> IronResult { - if values.len() == 1 { - Ok(values.remove(0)) - } else { - Err(GraphQLIronError::InvalidData("Duplicate URL query parameter").into()) - } -} - -fn parse_url_param(params: Option>) -> IronResult> { - if let Some(values) = params { - get_single_value(values).map(Some) - } else { - Ok(None) - } -} - -fn parse_variable_param(params: Option>) -> IronResult>> -where - S: ScalarValue, -{ - params - .map(|vals| { - serde_json::from_str::>(get_single_value(vals)?.as_ref()) - .map_err(|e| GraphQLIronError::Serde(e).into()) - }) - .transpose() -} - -impl<'a, CtxFactory, Query, Mutation, Subscription, CtxT, S> - GraphQLHandler<'a, CtxFactory, Query, Mutation, Subscription, CtxT, S> -where - S: ScalarValue + Send + Sync + 'static, - CtxFactory: Fn(&mut Request) -> IronResult + Send + Sync + 'static, - CtxT: Send + Sync + 'static, - Query: GraphQLType + Send + Sync + 'static, - Mutation: GraphQLType + Send + Sync + 'static, - Subscription: GraphQLType + Send + Sync + 'static, -{ - /// Build a new GraphQL handler - /// - /// The context factory will receive the Iron request object and is - /// expected to construct a context object for the given schema. This can - /// be used to construct e.g. database connections or similar data that - /// the schema needs to execute the query. - pub fn new( - context_factory: CtxFactory, - query: Query, - mutation: Mutation, - subscription: Subscription, - ) -> Self { - GraphQLHandler { - context_factory, - root_node: RootNode::new_with_scalar_value(query, mutation, subscription), - } - } - - fn handle_get(&self, req: &mut Request) -> IronResult> { - let url_query = req - .get_mut::() - .map_err(GraphQLIronError::Url)?; - - let query = parse_url_param(url_query.remove("query"))? - .ok_or(GraphQLIronError::InvalidData("No query provided"))?; - let operation_name = parse_url_param(url_query.remove("operationName"))?; - let variables = parse_variable_param(url_query.remove("variables"))?; - - Ok(GraphQLBatchRequest::Single(http::GraphQLRequest::new( - query, - operation_name, - variables, - ))) - } - - fn handle_post_json(&self, req: &mut Request) -> IronResult> { - let mut payload = String::new(); - itry!(req.body.read_to_string(&mut payload)); - - Ok( - serde_json::from_str::>(payload.as_str()) - .map_err(GraphQLIronError::Serde)?, - ) - } - - fn handle_post_graphql(&self, req: &mut Request) -> IronResult> { - let mut payload = String::new(); - itry!(req.body.read_to_string(&mut payload)); - - Ok(GraphQLBatchRequest::Single(http::GraphQLRequest::new( - payload, None, None, - ))) - } - - fn execute_sync( - &self, - context: &CtxT, - request: GraphQLBatchRequest, - ) -> IronResult { - let response = request.execute_sync(&self.root_node, context); - let content_type = "application/json".parse::().unwrap(); - let json = serde_json::to_string_pretty(&response).unwrap(); - let status = if response.is_ok() { - status::Ok - } else { - status::BadRequest - }; - Ok(Response::with((content_type, status, json))) - } -} - -impl GraphiQLHandler { - /// Build a new GraphiQL handler targeting the specified URL. - /// - /// 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, subscription_url: Option<&str>) -> GraphiQLHandler { - GraphiQLHandler { - graphql_url: graphql_url.into(), - subscription_url: subscription_url.map(Into::into), - } - } -} - -impl PlaygroundHandler { - /// Build a new GraphQL Playground handler targeting the specified URL. - /// - /// 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, subscription_url: Option<&str>) -> PlaygroundHandler { - PlaygroundHandler { - graphql_url: graphql_url.into(), - subscription_url: subscription_url.map(Into::into), - } - } -} - -impl Handler - for GraphQLHandler<'static, CtxFactory, Query, Mutation, Subscription, CtxT, S> -where - S: ScalarValue + Sync + Send + 'static, - CtxFactory: Fn(&mut Request) -> IronResult + Send + Sync + 'static, - CtxT: Send + Sync + 'static, - Query: GraphQLType + Send + Sync + 'static, - Mutation: GraphQLType + Send + Sync + 'static, - Subscription: GraphQLType + Send + Sync + 'static, -{ - fn handle(&self, req: &mut Request) -> IronResult { - let context = (self.context_factory)(req)?; - - let graphql_request = match req.method { - method::Get => self.handle_get(req)?, - method::Post => match req.headers.get::().map(ContentType::deref) { - Some(Mime(TopLevel::Application, sub_lvl, _)) => match sub_lvl.as_str() { - "json" => self.handle_post_json(req)?, - "graphql" => self.handle_post_graphql(req)?, - _ => return Ok(Response::with(status::BadRequest)), - }, - _ => return Ok(Response::with(status::BadRequest)), - }, - _ => return Ok(Response::with(status::MethodNotAllowed)), - }; - - self.execute_sync(&context, graphql_request) - } -} - -impl Handler for GraphiQLHandler { - fn handle(&self, _: &mut Request) -> IronResult { - let content_type = "text/html; charset=utf-8".parse::().unwrap(); - - Ok(Response::with(( - content_type, - status::Ok, - juniper::http::graphiql::graphiql_source( - &self.graphql_url, - self.subscription_url.as_deref(), - ), - ))) - } -} - -impl Handler for PlaygroundHandler { - fn handle(&self, _: &mut Request) -> IronResult { - let content_type = "text/html; charset=utf-8".parse::().unwrap(); - - Ok(Response::with(( - content_type, - status::Ok, - juniper::http::playground::playground_source( - &self.graphql_url, - self.subscription_url.as_deref(), - ), - ))) - } -} - -#[derive(Debug)] -enum GraphQLIronError { - Serde(SerdeError), - Url(UrlDecodingError), - InvalidData(&'static str), -} - -impl fmt::Display for GraphQLIronError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - GraphQLIronError::Serde(err) => fmt::Display::fmt(err, f), - GraphQLIronError::Url(err) => fmt::Display::fmt(err, f), - GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, f), - } - } -} - -impl Error for GraphQLIronError { - fn cause(&self) -> Option<&dyn Error> { - match *self { - GraphQLIronError::Serde(ref err) => Some(err), - GraphQLIronError::Url(ref err) => Some(err), - GraphQLIronError::InvalidData(_) => None, - } - } -} - -impl From for IronError { - fn from(err: GraphQLIronError) -> IronError { - let message = err.to_string(); - IronError::new(err, (status::BadRequest, message)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use iron::{ - headers::ContentType, - mime::{Mime, SubLevel, TopLevel}, - Handler, Headers, Url, - }; - use iron_test::{request, response}; - use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; - - use juniper::{ - http::tests as http_tests, - tests::fixtures::starwars::schema::{Database, Query}, - DefaultScalarValue, EmptyMutation, EmptySubscription, - }; - - use super::GraphQLHandler; - - /// https://url.spec.whatwg.org/#query-state - const QUERY_ENCODE_SET: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>'); - - // This is ugly but it works. `iron_test` just dumps the path/url in headers - // and newer `hyper` doesn't allow unescaped "{" or "}". - fn fixup_url(url: &str) -> String { - let url = Url::parse(&format!("http://localhost:3000{url}")).expect("url to parse"); - let path: String = url - .path() - .iter() - .map(|x| x.to_string()) - .collect::>() - .join("/"); - format!( - "http://localhost:3000{path}?{}", - utf8_percent_encode(url.query().unwrap_or(""), QUERY_ENCODE_SET), - ) - } - - struct TestIronIntegration; - - impl http_tests::HttpIntegration for TestIronIntegration { - fn get(&self, url: &str) -> http_tests::TestResponse { - request::get(&fixup_url(url), Headers::new(), &make_handler()) - .map(make_test_response) - .unwrap_or_else(make_test_error_response) - } - - fn post_json(&self, url: &str, body: &str) -> http_tests::TestResponse { - let mut headers = Headers::new(); - headers.set(ContentType::json()); - request::post(&fixup_url(url), headers, body, &make_handler()) - .map(make_test_response) - .unwrap_or_else(make_test_error_response) - } - - fn post_graphql(&self, url: &str, body: &str) -> http_tests::TestResponse { - let mut headers = Headers::new(); - headers.set(ContentType(Mime( - TopLevel::Application, - SubLevel::Ext("graphql".into()), - vec![], - ))); - request::post(&fixup_url(url), headers, body, &make_handler()) - .map(make_test_response) - .unwrap_or_else(make_test_error_response) - } - } - - #[test] - fn test_iron_integration() { - let integration = TestIronIntegration; - - http_tests::run_http_test_suite(&integration); - } - - fn context_factory(_: &mut Request) -> IronResult { - Ok(Database::new()) - } - - fn make_test_error_response(_: IronError) -> http_tests::TestResponse { - // For now all errors return the same status code. - // `juniper_iron` users can choose to do something different if desired. - http_tests::TestResponse { - status_code: 400, - body: None, - content_type: "application/json".into(), - } - } - - fn make_test_response(response: Response) -> http_tests::TestResponse { - let status_code = response - .status - .expect("No status code returned from handler") - .to_u16() as i32; - let content_type = String::from_utf8( - response - .headers - .get_raw("content-type") - .expect("No content type header from handler")[0] - .clone(), - ) - .expect("Content-type header invalid UTF-8"); - let body = response::extract_body_to_string(response); - - http_tests::TestResponse { - status_code, - body: Some(body), - content_type, - } - } - - fn make_handler() -> Box { - Box::new(>::new( - context_factory, - Query, - EmptyMutation::::new(), - EmptySubscription::::new(), - )) - } -}