Remove iron integration (#1210)

This commit is contained in:
Kai Ren 2023-11-10 04:52:24 +01:00 committed by GitHub
parent e75cf26995
commit d7103e2ef2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 6 additions and 754 deletions

View file

@ -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

View file

@ -5,7 +5,6 @@ members = [
"juniper_codegen",
"juniper",
"juniper_hyper",
"juniper_iron",
"juniper_rocket",
"juniper_subscriptions",
"juniper_graphql_ws",

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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<Context> {
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::<Context>::new(),
# );
# }
```
[iron]: https://github.com/iron/iron
[graphiql]: https://github.com/graphql/graphiql
[mount]: https://github.com/iron/mount

View file

@ -5,5 +5,4 @@ libraries.
- [Warp](warp.md)
- [Rocket](rocket.md)
- [Iron](iron.md)
- [Hyper](hyper.md)

View file

@ -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`:

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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 <mhallin@fastmail.com>",
"Christoph Herzog <chris@theduke.at>",
]
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"

View file

@ -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.

View file

@ -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

View file

@ -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<Database> {
Ok(Database::new())
}
fn main() {
let mut mount = Mount::new();
let graphql_endpoint = <GraphQLHandler<_, _, _, _, _, DefaultScalarValue>>::new(
context_factory,
Query,
EmptyMutation::<Database>::new(),
EmptySubscription::<Database>::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();
}

View file

@ -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}}/"

View file

@ -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<CtxT> + Send + Sync + 'static,
CtxT: 'static,
Query: GraphQLType<S, Context = CtxT> + Send + Sync + 'static,
Mutation: GraphQLType<S, Context = CtxT> + Send + Sync + 'static,
Subscription: GraphQLType<S, Context = CtxT> + 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<String>,
}
/// Handler that renders `GraphQL Playground` - a graphical query editor interface
pub struct PlaygroundHandler {
graphql_url: String,
subscription_url: Option<String>,
}
fn get_single_value<T>(mut values: Vec<T>) -> IronResult<T> {
if values.len() == 1 {
Ok(values.remove(0))
} else {
Err(GraphQLIronError::InvalidData("Duplicate URL query parameter").into())
}
}
fn parse_url_param(params: Option<Vec<String>>) -> IronResult<Option<String>> {
if let Some(values) = params {
get_single_value(values).map(Some)
} else {
Ok(None)
}
}
fn parse_variable_param<S>(params: Option<Vec<String>>) -> IronResult<Option<InputValue<S>>>
where
S: ScalarValue,
{
params
.map(|vals| {
serde_json::from_str::<InputValue<S>>(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<CtxT> + Send + Sync + 'static,
CtxT: Send + Sync + 'static,
Query: GraphQLType<S, Context = CtxT, TypeInfo = ()> + Send + Sync + 'static,
Mutation: GraphQLType<S, Context = CtxT, TypeInfo = ()> + Send + Sync + 'static,
Subscription: GraphQLType<S, Context = CtxT, TypeInfo = ()> + 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<GraphQLBatchRequest<S>> {
let url_query = req
.get_mut::<UrlEncodedQuery>()
.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<GraphQLBatchRequest<S>> {
let mut payload = String::new();
itry!(req.body.read_to_string(&mut payload));
Ok(
serde_json::from_str::<GraphQLBatchRequest<S>>(payload.as_str())
.map_err(GraphQLIronError::Serde)?,
)
}
fn handle_post_graphql(&self, req: &mut Request) -> IronResult<GraphQLBatchRequest<S>> {
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<S>,
) -> IronResult<Response> {
let response = request.execute_sync(&self.root_node, context);
let content_type = "application/json".parse::<Mime>().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<CtxFactory, Query, Mutation, Subscription, CtxT, S> Handler
for GraphQLHandler<'static, CtxFactory, Query, Mutation, Subscription, CtxT, S>
where
S: ScalarValue + Sync + Send + 'static,
CtxFactory: Fn(&mut Request) -> IronResult<CtxT> + Send + Sync + 'static,
CtxT: Send + Sync + 'static,
Query: GraphQLType<S, Context = CtxT, TypeInfo = ()> + Send + Sync + 'static,
Mutation: GraphQLType<S, Context = CtxT, TypeInfo = ()> + Send + Sync + 'static,
Subscription: GraphQLType<S, Context = CtxT, TypeInfo = ()> + Send + Sync + 'static,
{
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let context = (self.context_factory)(req)?;
let graphql_request = match req.method {
method::Get => self.handle_get(req)?,
method::Post => match req.headers.get::<ContentType>().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<Response> {
let content_type = "text/html; charset=utf-8".parse::<Mime>().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<Response> {
let content_type = "text/html; charset=utf-8".parse::<Mime>().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<GraphQLIronError> 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::<Vec<_>>()
.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<Database> {
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<dyn Handler> {
Box::new(<GraphQLHandler<_, _, _, _, _, DefaultScalarValue>>::new(
context_factory,
Query,
EmptyMutation::<Database>::new(),
EmptySubscription::<Database>::new(),
))
}
}