diff --git a/juniper_axum/CHANGELOG.md b/juniper_axum/CHANGELOG.md index 94ae9800..a7d6f3c2 100644 --- a/juniper_axum/CHANGELOG.md +++ b/juniper_axum/CHANGELOG.md @@ -10,7 +10,7 @@ All user visible changes to `juniper_axum` crate will be documented in this file ### Initialized -- Dependent on 0.6 version of [`axum` crate]. ([#1088]) +- Dependent on 0.7 version of [`axum` crate]. ([#1088], [#1224]) - Dependent on 0.16 version of [`juniper` crate]. ([#1088]) - Dependent on 0.4 version of [`juniper_graphql_ws` crate]. ([#1088]) @@ -28,6 +28,7 @@ All user visible changes to `juniper_axum` crate will be documented in this file [#986]: /../../issues/986 [#1088]: /../../pull/1088 [#1184]: /../../issues/1184 +[#1224]: /../../pull/1224 diff --git a/juniper_axum/Cargo.toml b/juniper_axum/Cargo.toml index 970708f1..265f915e 100644 --- a/juniper_axum/Cargo.toml +++ b/juniper_axum/Cargo.toml @@ -25,7 +25,7 @@ rustdoc-args = ["--cfg", "docsrs"] subscriptions = ["axum/ws", "juniper_graphql_ws/graphql-ws", "dep:futures"] [dependencies] -axum = "0.6.20" +axum = "0.7" futures = { version = "0.3.22", optional = true } juniper = { version = "0.16.0-dev", path = "../juniper", default-features = false } juniper_graphql_ws = { version = "0.4.0-dev", path = "../juniper_graphql_ws", features = ["graphql-transport-ws"] } @@ -38,9 +38,10 @@ bytes = "1.2" [dev-dependencies] anyhow = "1.0" -axum = { version = "0.6", features = ["macros"] } +axum = { version = "0.7", features = ["http1", "macros", "tokio"] } +futures = "0.3.22" juniper = { version = "0.16.0-dev", path = "../juniper", features = ["expose-test-schema"] } -tokio = { version = "1.20", features = ["macros", "rt-multi-thread", "time"] } +tokio = { version = "1.20", features = ["macros", "net", "rt-multi-thread", "time"] } tokio-stream = "0.1" tokio-tungstenite = "0.20" tower-service = "0.3" diff --git a/juniper_axum/examples/custom.rs b/juniper_axum/examples/custom.rs index a2d88b00..ec3bd3c7 100644 --- a/juniper_axum/examples/custom.rs +++ b/juniper_axum/examples/custom.rs @@ -19,6 +19,7 @@ use juniper_axum::{ extract::JuniperRequest, graphiql, playground, response::JuniperResponse, subscriptions, }; use juniper_graphql_ws::ConnectionConfig; +use tokio::net::TcpListener; type Schema = RootNode<'static, Query, EmptyMutation, Subscription>; @@ -68,7 +69,7 @@ async fn main() { let app = Router::new() .route( "/graphql", - on(MethodFilter::GET | MethodFilter::POST, custom_graphql), + on(MethodFilter::GET.or(MethodFilter::POST), custom_graphql), ) .route("/subscriptions", get(custom_subscriptions)) .route("/graphiql", get(graphiql("/graphql", "/subscriptions"))) @@ -78,9 +79,11 @@ async fn main() { .layer(Extension(database)); let addr = SocketAddr::from(([127, 0, 0, 1], 8080)); - tracing::info!("listening on {addr}"); - axum::Server::bind(&addr) - .serve(app.into_make_service()) + let listener = TcpListener::bind(addr) .await - .unwrap_or_else(|e| panic!("failed to run `axum::Server`: {e}")); + .unwrap_or_else(|e| panic!("failed to listen on {addr}: {e}")); + tracing::info!("listening on {addr}"); + axum::serve(listener, app) + .await + .unwrap_or_else(|e| panic!("failed to run `axum::serve`: {e}")); } diff --git a/juniper_axum/examples/simple.rs b/juniper_axum/examples/simple.rs index 9ccace30..0250714a 100644 --- a/juniper_axum/examples/simple.rs +++ b/juniper_axum/examples/simple.rs @@ -11,7 +11,7 @@ use futures::stream::{BoxStream, StreamExt as _}; use juniper::{graphql_object, graphql_subscription, EmptyMutation, FieldError, RootNode}; use juniper_axum::{graphiql, graphql, playground, ws}; use juniper_graphql_ws::ConnectionConfig; -use tokio::time::interval; +use tokio::{net::TcpListener, time::interval}; use tokio_stream::wrappers::IntervalStream; #[derive(Clone, Copy, Debug)] @@ -65,7 +65,7 @@ async fn main() { .route( "/graphql", on( - MethodFilter::GET | MethodFilter::POST, + MethodFilter::GET.or(MethodFilter::POST), graphql::>, ), ) @@ -79,9 +79,11 @@ async fn main() { .layer(Extension(Arc::new(schema))); let addr = SocketAddr::from(([127, 0, 0, 1], 8080)); - tracing::info!("listening on {addr}"); - axum::Server::bind(&addr) - .serve(app.into_make_service()) + let listener = TcpListener::bind(addr) .await - .unwrap_or_else(|e| panic!("failed to run `axum::Server`: {e}")); + .unwrap_or_else(|e| panic!("failed to listen on {addr}: {e}")); + tracing::info!("listening on {addr}"); + axum::serve(listener, app) + .await + .unwrap_or_else(|e| panic!("failed to run `axum::serve`: {e}")); } diff --git a/juniper_axum/src/extract.rs b/juniper_axum/src/extract.rs index 2ce807c8..fe087f67 100644 --- a/juniper_axum/src/extract.rs +++ b/juniper_axum/src/extract.rs @@ -71,14 +71,14 @@ where S: ScalarValue; #[async_trait] -impl FromRequest for JuniperRequest +impl FromRequest for JuniperRequest where S: ScalarValue, State: Sync, Query: FromRequestParts, - Json>: FromRequest, - > as FromRequest>::Rejection: fmt::Display, - String: FromRequest, + Json>: FromRequest, + > as FromRequest>::Rejection: fmt::Display, + String: FromRequest, { type Rejection = Response; @@ -96,7 +96,9 @@ where .into_response() })?; - match (req.method(), content_type) { + // TODO: Move into `match` expression directly once MSRV is bumped higher than 1.74. + let method = req.method(); + match (method, content_type) { (&Method::GET, _) => req .extract_parts::>() .await @@ -180,13 +182,8 @@ impl TryFrom for GraphQLRequest { #[cfg(test)] mod juniper_request_tests { - use std::fmt; - - use axum::{ - body::{Body, Bytes, HttpBody}, - extract::FromRequest as _, - http::Request, - }; + use axum::{body::Body, extract::FromRequest as _, http::Request}; + use futures::TryStreamExt as _; use juniper::{ graphql_input_value, http::{GraphQLBatchRequest, GraphQLRequest}, @@ -279,18 +276,15 @@ mod juniper_request_tests { } } - /// Converts the provided [`HttpBody`] into a [`String`]. - async fn display_body(mut body: B) -> String - where - B: HttpBody + Unpin, - B::Error: fmt::Display, - { - let mut body_bytes = vec![]; - while let Some(bytes) = body.data().await { - body_bytes.extend( - bytes.unwrap_or_else(|e| panic!("failed to represent `Body` as `Bytes`: {e}")), - ); - } - String::from_utf8(body_bytes).unwrap_or_else(|e| panic!("not UTF-8 body: {e}")) + /// Converts the provided [`Body`] into a [`String`]. + async fn display_body(body: Body) -> String { + String::from_utf8( + body.into_data_stream() + .map_ok(|bytes| bytes.to_vec()) + .try_concat() + .await + .unwrap(), + ) + .unwrap_or_else(|e| panic!("not UTF-8 body: {e}")) } } diff --git a/juniper_axum/tests/http_test_suite.rs b/juniper_axum/tests/http_test_suite.rs index 06571bcb..0e13ca2d 100644 --- a/juniper_axum/tests/http_test_suite.rs +++ b/juniper_axum/tests/http_test_suite.rs @@ -1,12 +1,13 @@ use std::sync::Arc; use axum::{ - body::{Body, HttpBody as _}, + body::Body, http::Request, response::Response, routing::{get, post}, Extension, Router, }; +use futures::TryStreamExt as _; use juniper::{ http::tests::{run_http_test_suite, HttpIntegration, TestResponse}, tests::fixtures::starwars::schema::{Database, Query}, @@ -97,12 +98,15 @@ async fn into_test_response(resp: Response) -> TestResponse { }) .unwrap_or_default(); - let mut body = resp.into_body(); - let mut body_bytes = vec![]; - while let Some(bytes) = body.data().await { - body_bytes.extend(bytes.unwrap()); - } - let body = String::from_utf8(body_bytes).unwrap_or_else(|e| panic!("not UTF-8 body: {e}")); + let body = String::from_utf8( + resp.into_body() + .into_data_stream() + .map_ok(|bytes| bytes.to_vec()) + .try_concat() + .await + .unwrap(), + ) + .unwrap_or_else(|e| panic!("not UTF-8 body: {e}")); TestResponse { status_code, diff --git a/juniper_axum/tests/ws_test_suite.rs b/juniper_axum/tests/ws_test_suite.rs index 61447906..01bc071e 100644 --- a/juniper_axum/tests/ws_test_suite.rs +++ b/juniper_axum/tests/ws_test_suite.rs @@ -1,9 +1,6 @@ #![cfg(not(windows))] -use std::{ - net::{SocketAddr, TcpListener}, - sync::Arc, -}; +use std::{net::SocketAddr, sync::Arc}; use anyhow::anyhow; use axum::{routing::get, Extension, Router}; @@ -15,7 +12,10 @@ use juniper::{ }; use juniper_axum::subscriptions; use juniper_graphql_ws::ConnectionConfig; -use tokio::{net::TcpStream, time::timeout}; +use tokio::{ + net::{TcpListener, TcpStream}, + time::timeout, +}; use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; type Schema = RootNode<'static, Query, EmptyMutation, Subscription>; @@ -49,15 +49,13 @@ impl TestApp { } async fn run(self, messages: Vec) -> Result<(), anyhow::Error> { - let listener = TcpListener::bind("0.0.0.0:0".parse::().unwrap()).unwrap(); + let listener = TcpListener::bind("0.0.0.0:0".parse::().unwrap()) + .await + .unwrap(); let addr = listener.local_addr().unwrap(); tokio::spawn(async move { - axum::Server::from_tcp(listener) - .unwrap() - .serve(self.0.into_make_service()) - .await - .unwrap(); + axum::serve(listener, self.0).await.unwrap(); }); let (mut websocket, _) = connect_async(format!("ws://{}/subscriptions", addr)) diff --git a/juniper_warp/tests/http_test_suite.rs b/juniper_warp/tests/http_test_suite.rs index da07eead..6ea9d88c 100644 --- a/juniper_warp/tests/http_test_suite.rs +++ b/juniper_warp/tests/http_test_suite.rs @@ -39,7 +39,7 @@ impl TestWarpIntegration { let rt = tokio::runtime::Runtime::new() .unwrap_or_else(|e| panic!("failed to create `tokio::Runtime`: {e}")); rt.block_on(async move { - make_test_response(req.filter(&self.filter).await.unwrap_or_else(|rejection| { + into_test_response(req.filter(&self.filter).await.unwrap_or_else(|rejection| { let code = if rejection.is_not_found() { http::StatusCode::NOT_FOUND } else if let Some(body::BodyDeserializeError { .. }) = rejection.find() { @@ -101,7 +101,7 @@ impl HttpIntegration for TestWarpIntegration { } } -async fn make_test_response(resp: reply::Response) -> TestResponse { +async fn into_test_response(resp: reply::Response) -> TestResponse { let (parts, body) = resp.into_parts(); let status_code = parts.status.as_u16().into();