Remove iron
integration (#1210)
This commit is contained in:
parent
e75cf26995
commit
d7103e2ef2
20 changed files with 6 additions and 754 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -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
|
||||
|
|
|
@ -5,7 +5,6 @@ members = [
|
|||
"juniper_codegen",
|
||||
"juniper",
|
||||
"juniper_hyper",
|
||||
"juniper_iron",
|
||||
"juniper_rocket",
|
||||
"juniper_subscriptions",
|
||||
"juniper_graphql_ws",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -5,5 +5,4 @@ libraries.
|
|||
|
||||
- [Warp](warp.md)
|
||||
- [Rocket](rocket.md)
|
||||
- [Iron](iron.md)
|
||||
- [Hyper](hyper.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`:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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"
|
|
@ -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.
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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}}/"
|
|
@ -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(),
|
||||
))
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue