Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Axum integration #1088

Merged
merged 35 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bc7f4a8
Add Error to Connection Stream
btielen Jul 15, 2022
11ecc25
Add post_with_variables test
btielen Jul 22, 2022
2932e6e
Decouple test and implementation
btielen Jul 23, 2022
719eb97
Add juniper_axum
btielen Jul 24, 2022
183302c
Clippy and format
btielen Jul 24, 2022
f9326e5
Merge branch 'master' into axum
ilslv Aug 3, 2022
59ab64a
Corrections
ilslv Aug 3, 2022
7414b4c
Update juniper_axum/README.md
LegNeato Aug 28, 2023
e1a1104
Update juniper_warp/src/lib.rs
LegNeato Aug 28, 2023
fbef4c0
Merge branch 'master' into axum
tyranron Nov 7, 2023
b5d5be8
Revert unrelevant changes
tyranron Nov 7, 2023
f5b0d70
Add to CI
tyranron Nov 7, 2023
2691dc4
Tune up deps
tyranron Nov 7, 2023
5f50474
Refactor solution, vol.1
tyranron Nov 7, 2023
224e30c
Refactor solution, vol.2 [skip ci]
tyranron Nov 7, 2023
072717e
Get rid of `GetQueryVariables`
tyranron Nov 7, 2023
aa03df3
Get rid of `JsonRequestBody` [skip ci]
tyranron Nov 7, 2023
baea7e6
Parametrize with `ScalarValue`
tyranron Nov 8, 2023
8961631
Fix `extract` unit tests
tyranron Nov 8, 2023
2a819fd
Rework `graphql-ws` subscriptions
tyranron Nov 8, 2023
ac97a3a
Fix doc tests
tyranron Nov 8, 2023
4600686
Add `graphql-transport-ws` impl
tyranron Nov 8, 2023
3b3e7e3
Impl auto-selection between new `graphql-transport-ws` and old `graph…
tyranron Nov 8, 2023
4d89d51
Add ready-to-go `graphql` handler
tyranron Nov 8, 2023
ccac700
Add ready-to-go `ws`, `graphql_ws` and `graphql_transport_ws` handlers
tyranron Nov 8, 2023
fa73e0b
Fix docs
tyranron Nov 8, 2023
5491556
Add `release.toml` [skip ci]
tyranron Nov 8, 2023
90bcc3c
Update workspace
tyranron Nov 8, 2023
cac125f
Rework `simple` example
tyranron Nov 8, 2023
afa9521
Rework `strawars` example as `custom`
tyranron Nov 8, 2023
e2561c7
Reworking integration tests, vol.1
tyranron Nov 8, 2023
8ba8de5
Reworking integration tests, vol.2
tyranron Nov 8, 2023
c44f5cd
Fill-up CHANGELOG
tyranron Nov 8, 2023
c9cf94e
Fix tests
tyranron Nov 8, 2023
bb6dcaa
Try fix MSRV
tyranron Nov 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ members = [
"juniper_graphql_ws",
"juniper_warp",
"juniper_actix",
"juniper_axum",
"tests/codegen",
"tests/integration",
]
39 changes: 38 additions & 1 deletion juniper/src/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,9 @@ pub mod tests {
println!(" - test_get_with_variables");
test_get_with_variables(integration);

println!(" - test_post_with_variables");
test_post_with_variables(integration);

println!(" - test_simple_post");
test_simple_post(integration);

Expand Down Expand Up @@ -510,6 +513,40 @@ pub mod tests {
);
}

fn test_post_with_variables<T: HttpIntegration>(integration: &T) {
let response = integration.post_json(
"/",
r#"{
"query": "query($id: String!) { human(id: $id) { id, name, appearsIn, homePlanet } }",
"variables": { "id": "1000" }
}"#,
);

assert_eq!(response.status_code, 200);
assert_eq!(response.content_type, "application/json");

assert_eq!(
unwrap_json_response(&response),
serde_json::from_str::<Json>(
r#"{
"data": {
"human": {
"appearsIn": [
"NEW_HOPE",
"EMPIRE",
"JEDI"
],
"homePlanet": "Tatooine",
"name": "Luke Skywalker",
"id": "1000"
}
}
}"#
)
.expect("Invalid JSON constant in test")
);
}

fn test_simple_post<T: HttpIntegration>(integration: &T) {
let response = integration.post_json("/", r#"{"query": "{hero{name}}"}"#);

Expand Down Expand Up @@ -690,7 +727,7 @@ pub mod tests {
r#"{
"type":"connection_error",
"payload":{
"message":"serde error: expected value at line 1 column 1"
"message":"expected value at line 1 column 1"
}
}"#
.into(),
Expand Down
2 changes: 1 addition & 1 deletion juniper_actix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ pub mod subscriptions {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Serde(e) => write!(f, "serde error: {e}"),
Self::Serde(e) => write!(f, "{e}"),
Self::UnexpectedClientMessage => {
write!(f, "unexpected message received from client")
}
Expand Down
26 changes: 26 additions & 0 deletions juniper_axum/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
`juniper_axum` changelog
========================

All user visible changes to `juniper_axum` crate will be documented in this file. This project uses [Semantic Versioning 2.0.0].




## master

### BC Breaks
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just remove this section and put something like:

  • Initial release


- Switched to 0.16 version of [`juniper` crate].




## Previous releases

See [old CHANGELOG](/../../blob/juniper_warp-v0.0.0/juniper_axum/CHANGELOG.md).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mentions warp and should be deleted.





[`juniper` crate]: https://docs.rs/juniper
[Semantic Versioning 2.0.0]: https://semver.org
32 changes: 32 additions & 0 deletions juniper_axum/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "juniper_axum"
version = "0.1.0"
edition = "2021"
rust-version = "1.62"
description = "`juniper` GraphQL integration with `axum`."
license = "BSD-2-Clause"
authors = ["Benno Tielen <[email protected]>"]
documentation = "https://docs.rs/juniper_axum"
homepage = "https://github.com/graphql-rust/juniper/tree/master/juniper_axum"
repository = "https://github.com/graphql-rust/juniper"
readme = "README.md"
categories = ["asynchronous", "web-programming", "web-programming::http-server"]
keywords = ["graphql", "juniper", "axum", "websocket"]
exclude = ["/release.toml"]

[dependencies]
axum = { version = "0.5.11", features = ["ws"]}
juniper = { path = "../juniper" }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a version required?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@btielen in the same way as other integrations have. The version bump is automated with cargo-release. See release.toml files for examples.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in the PR comments already, this should have a version and wired up to the release machinery.

serde = "1.0"
serde_json = "1.0"
juniper_graphql_ws = { path="../juniper_graphql_ws" }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

futures = "0.3"

[dev-dependencies]
tokio = { version = "1.20", features = ["full"] }
tokio-tungstenite = "0.17.2"
tokio-stream = "0.1.9"
tower = "0.4.13"
hyper = "0.14.20"
juniper = { path = "../juniper", features = ["expose-test-schema"]}
anyhow = "1.0"
25 changes: 25 additions & 0 deletions juniper_axum/LICENCE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
BSD 2-Clause License

Copyright (c) 2022, Benno Tielen
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.
93 changes: 93 additions & 0 deletions juniper_axum/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
`juniper_axum` crate
====================

[![Crates.io](https://img.shields.io/crates/v/juniper_axum.svg?maxAge=2592000)](https://crates.io/crates/juniper_warp)
[![Documentation](https://docs.rs/juniper_warp/badge.svg)](https://docs.rs/juniper_warp)
LegNeato marked this conversation as resolved.
Show resolved Hide resolved
[![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)

- [Changelog](https://github.com/graphql-rust/juniper/blob/master/juniper_axum/CHANGELOG.md)

[`axum`] web server integration for [`juniper`] ([GraphQL] implementation for [Rust]).

## Getting started

The best way to get started is to examine the `simple` example in the `examples` directory. To execute
this example run

`cargo run --example simple`

Open your browser and navigate to `127.0.0.1:3000`. A GraphQL Playground opens. The
following commands are available in the playground.

```graphql
{
add(a: 2, b: 40)
}
```

```graphql
subscription {
count
}
```

## Queries and mutations
This crate provides an extractor and response for axum to work with juniper.

```rust,ignore
use juniper_axum::response::JuniperResponse;

let app: Router<Body> = Router::new()
.route("/graphql", post(graphql))
.layer(Extension(schema))
.layer(Extension(context));

async fn graphql(
JuniperRequest(request): JuniperRequest,
Extension(schema): Extension<Arc<Schema>>,
Extension(context): Extension<Arc<Context>>
) -> JuniperResponse {
JuniperResponse(request.execute(&schema, &context).await)
}
```

## Subscriptions
This crate provides a helper function to easily work with graphql subscriptions over a websocket.
```rust,ignore
use juniper_axum::subscription::handle_graphql_socket;

let app: Router = Router::new()
.route("/subscriptions", get(juniper_subscriptions))
.layer(Extension(schema))
.layer(Extension(context));

pub async fn juniper_subscriptions(
Extension(schema): Extension<Arc<Schema>>,
Extension(context): Extension<Context>,
ws: WebSocketUpgrade,
) -> Response {
ws.protocols(["graphql-ws", "graphql-transport-ws"])
.max_frame_size(1024)
.max_message_size(1024)
.max_send_queue(100)
.on_upgrade(|socket| handle_graphql_socket(socket, schema, context))
}
```



## License

This project is licensed under [BSD 2-Clause License](https://github.com/graphql-rust/juniper/blob/master/juniper_axum/LICENSE).




[`juniper`]: https://docs.rs/juniper
[`juniper_axum`]: https://docs.rs/juniper_axum
[`axum`]: https://docs.rs/axum
[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_warp/examples/warp_server.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix URL.

92 changes: 92 additions & 0 deletions juniper_axum/examples/simple.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use std::{net::SocketAddr, pin::Pin, sync::Arc, time::Duration};

use axum::{
extract::WebSocketUpgrade,
response::Response,
routing::{get, post},
Extension, Router,
};
use futures::{Stream, StreamExt};

use juniper::{graphql_object, graphql_subscription, EmptyMutation, FieldError, RootNode};
use juniper_axum::{
extract::JuniperRequest, playground, response::JuniperResponse,
subscriptions::handle_graphql_socket,
};

#[derive(Clone)]
pub struct Context;
impl juniper::Context for Context {}

#[derive(Clone, Copy, Debug)]
pub struct Query;

#[graphql_object(context = Context)]
impl Query {
/// Add two numbers a and b
fn add(a: i32, b: i32) -> i32 {
a + b
}
}

pub struct Subscription;
type NumberStream = Pin<Box<dyn Stream<Item = Result<i32, FieldError>> + Send>>;
type AppSchema = RootNode<'static, Query, EmptyMutation<Context>, Subscription>;

#[graphql_subscription(context = Context)]
impl Subscription {
/// Count seconds
async fn count() -> NumberStream {
let mut value = 0;
let stream = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
Duration::from_secs(1),
))
.map(move |_| {
value += 1;
Ok(value)
});
Box::pin(stream)
}
}

#[tokio::main]
async fn main() {
let schema = Arc::new(AppSchema::new(Query, EmptyMutation::new(), Subscription));

let context = Context;

let app = Router::new()
.route("/", get(|| playground("/graphql", Some("/subscriptions"))))
.route("/graphql", post(graphql))
.route("/subscriptions", get(juniper_subscriptions))
.layer(Extension(schema))
.layer(Extension(context));

// run it
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}

pub async fn juniper_subscriptions(
Extension(schema): Extension<Arc<AppSchema>>,
Extension(context): Extension<Context>,
ws: WebSocketUpgrade,
) -> Response {
ws.protocols(["graphql-ws"])
.max_frame_size(1024)
.max_message_size(1024)
.max_send_queue(100)
.on_upgrade(|socket| handle_graphql_socket(socket, schema, context))
}

async fn graphql(
JuniperRequest(request): JuniperRequest,
Extension(schema): Extension<Arc<AppSchema>>,
Extension(context): Extension<Context>,
) -> JuniperResponse {
JuniperResponse(request.execute(&schema, &context).await)
}
Loading