Skip to content

Commit

Permalink
Merge branch 'tokio-rs:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
zleyyij committed Jul 11, 2024
2 parents 7cde253 + 74eac39 commit 580d6b0
Show file tree
Hide file tree
Showing 37 changed files with 234 additions and 99 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,19 @@ jobs:
crate: [axum, axum-core, axum-extra, axum-macros]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
# Pinned version due to failing `cargo-public-api-crates`.
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2024-06-06
- uses: Swatinem/rust-cache@v2
- name: Install cargo-public-api-crates
run: |
cargo install --git https://github.com/davidpdrsn/cargo-public-api-crates
- name: Build rustdoc
run: |
cargo rustdoc --all-features --manifest-path ${{ matrix.crate }}/Cargo.toml -- -Z unstable-options --output-format json
- name: cargo public-api-crates check
run: cargo public-api-crates --manifest-path ${{ matrix.crate }}/Cargo.toml check
run: cargo public-api-crates --manifest-path ${{ matrix.crate }}/Cargo.toml --skip-build check

test-versions:
needs: check
Expand Down
1 change: 1 addition & 0 deletions ECOSYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ If your project isn't listed here and you would like it to be, please feel free
- [realworld-axum-sqlx](https://github.com/launchbadge/realworld-axum-sqlx): A Rust implementation of the [Realworld] demo app spec using Axum and [SQLx].
See https://github.com/davidpdrsn/realworld-axum-sqlx for a fork with up to date dependencies.
- [Rustapi](https://github.com/ndelvalle/rustapi): RESTful API template using MongoDB
- [axum-postgres-template](https://github.com/koskeller/axum-postgres-template): Production-ready Axum + PostgreSQL application template
- [RUSTfulapi](https://github.com/robatipoor/rustfulapi): Reusable template for building REST Web Services in Rust. Uses Axum HTTP web framework and SeaORM.
- [Jotsy](https://github.com/ohsayan/jotsy): Self-hosted notes app powered by Skytable, Axum and Tokio
- [Svix](https://www.svix.com) ([repository](https://github.com/svix/svix-webhooks)): Enterprise-ready webhook service
Expand Down
13 changes: 12 additions & 1 deletion axum-core/src/macros.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// Private API.
#[cfg(feature = "tracing")]
#[doc(hidden)]
#[macro_export]
macro_rules! __log_rejection {
Expand All @@ -7,7 +8,6 @@ macro_rules! __log_rejection {
body_text = $body_text:expr,
status = $status:expr,
) => {
#[cfg(feature = "tracing")]
{
tracing::event!(
target: "axum::rejection",
Expand All @@ -21,6 +21,17 @@ macro_rules! __log_rejection {
};
}

#[cfg(not(feature = "tracing"))]
#[doc(hidden)]
#[macro_export]
macro_rules! __log_rejection {
(
rejection_type = $ty:ident,
body_text = $body_text:expr,
status = $status:expr,
) => {};
}

/// Private API.
#[doc(hidden)]
#[macro_export]
Expand Down
2 changes: 1 addition & 1 deletion axum-core/src/response/append_headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use std::fmt;
/// )
/// }
/// ```
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct AppendHeaders<I>(pub I);

Expand Down
3 changes: 2 additions & 1 deletion axum-extra/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ version = "0.9.3"
default = ["tracing", "multipart"]

async-read-body = ["dep:tokio-util", "tokio-util?/io", "dep:tokio"]
attachment = ["dep:tracing"]
cookie = ["dep:cookie"]
cookie-private = ["cookie", "cookie?/private"]
cookie-signed = ["cookie", "cookie?/signed"]
Expand All @@ -33,7 +34,7 @@ json-lines = [
multipart = ["dep:multer", "dep:fastrand"]
protobuf = ["dep:prost"]
query = ["dep:serde_html_form"]
tracing = ["dep:tracing", "axum-core/tracing"]
tracing = ["dep:tracing", "axum-core/tracing", "axum/tracing"]
typed-header = ["dep:headers"]
typed-routing = ["dep:axum-macros", "dep:percent-encoding", "dep:serde_html_form", "dep:form_urlencoded"]

Expand Down
3 changes: 1 addition & 2 deletions axum-extra/src/extract/json_deserializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ use std::marker::PhantomData;
/// Additionally, a `JsonRejection` error will be returned, when calling `deserialize` if:
///
/// - The body doesn't contain syntactically valid JSON.
/// - The body contains syntactically valid JSON, but it couldn't be deserialized into the target
/// type.
/// - The body contains syntactically valid JSON, but it couldn't be deserialized into the target type.
/// - Attempting to deserialize escaped JSON into a type that must be borrowed (e.g. `&'a str`).
///
/// ⚠️ `serde` will implicitly try to borrow for `&str` and `&[u8]` types, but will error if the
Expand Down
103 changes: 103 additions & 0 deletions axum-extra/src/response/attachment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use axum::response::IntoResponse;
use http::{header, HeaderMap, HeaderValue};
use tracing::error;

/// A file attachment response.
///
/// This type will set the `Content-Disposition` header to `attachment`. In response a webbrowser
/// will offer to download the file instead of displaying it directly.
///
/// Use the `filename` and `content_type` methods to set the filename or content-type of the
/// attachment. If these values are not set they will not be sent.
///
///
/// # Example
///
/// ```rust
/// use axum::{http::StatusCode, routing::get, Router};
/// use axum_extra::response::Attachment;
///
/// async fn cargo_toml() -> Result<Attachment<String>, (StatusCode, String)> {
/// let file_contents = tokio::fs::read_to_string("Cargo.toml")
/// .await
/// .map_err(|err| (StatusCode::NOT_FOUND, format!("File not found: {err}")))?;
/// Ok(Attachment::new(file_contents)
/// .filename("Cargo.toml")
/// .content_type("text/x-toml"))
/// }
///
/// let app = Router::new().route("/Cargo.toml", get(cargo_toml));
/// let _: Router = app;
/// ```
///
/// # Note
///
/// If you use axum with hyper, hyper will set the `Content-Length` if it is known.
///
#[derive(Debug)]
pub struct Attachment<T> {
inner: T,
filename: Option<HeaderValue>,
content_type: Option<HeaderValue>,
}

impl<T: IntoResponse> Attachment<T> {
/// Creates a new [`Attachment`].
pub fn new(inner: T) -> Self {
Self {
inner,
filename: None,
content_type: None,
}
}

/// Sets the filename of the [`Attachment`].
///
/// This updates the `Content-Disposition` header to add a filename.
pub fn filename<H: TryInto<HeaderValue>>(mut self, value: H) -> Self {
self.filename = if let Ok(filename) = value.try_into() {
Some(filename)
} else {
error!("Attachment filename contains invalid characters");
None
};
self
}

/// Sets the content-type of the [`Attachment`]
pub fn content_type<H: TryInto<HeaderValue>>(mut self, value: H) -> Self {
if let Ok(content_type) = value.try_into() {
self.content_type = Some(content_type);
} else {
error!("Attachment content-type contains invalid characters");
}
self
}
}

impl<T> IntoResponse for Attachment<T>
where
T: IntoResponse,
{
fn into_response(self) -> axum::response::Response {
let mut headers = HeaderMap::new();

if let Some(content_type) = self.content_type {
headers.append(header::CONTENT_TYPE, content_type);
}

let content_disposition = if let Some(filename) = self.filename {
let mut bytes = b"attachment; filename=\"".to_vec();
bytes.extend_from_slice(filename.as_bytes());
bytes.push(b'\"');

HeaderValue::from_bytes(&bytes).expect("This was a HeaderValue so this can not fail")
} else {
HeaderValue::from_static("attachment")
};

headers.append(header::CONTENT_DISPOSITION, content_disposition);

(headers, self.inner).into_response()
}
}
6 changes: 6 additions & 0 deletions axum-extra/src/response/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
#[cfg(feature = "erased-json")]
mod erased_json;

#[cfg(feature = "attachment")]
mod attachment;

#[cfg(feature = "erased-json")]
pub use erased_json::ErasedJson;

#[cfg(feature = "json-lines")]
#[doc(no_inline)]
pub use crate::json_lines::JsonLines;

#[cfg(feature = "attachment")]
pub use attachment::Attachment;

macro_rules! mime_response {
(
$(#[$m:meta])*
Expand Down
10 changes: 5 additions & 5 deletions axum-extra/src/routing/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,12 @@ use serde::Serialize;
///
/// - A `TypedPath` implementation.
/// - A [`FromRequest`] implementation compatible with [`RouterExt::typed_get`],
/// [`RouterExt::typed_post`], etc. This implementation uses [`Path`] and thus your struct must
/// also implement [`serde::Deserialize`], unless it's a unit struct.
/// [`RouterExt::typed_post`], etc. This implementation uses [`Path`] and thus your struct must
/// also implement [`serde::Deserialize`], unless it's a unit struct.
/// - A [`Display`] implementation that interpolates the captures. This can be used to, among other
/// things, create links to known paths and have them verified statically. Note that the
/// [`Display`] implementation for each field must return something that's compatible with its
/// [`Deserialize`] implementation.
/// things, create links to known paths and have them verified statically. Note that the
/// [`Display`] implementation for each field must return something that's compatible with its
/// [`Deserialize`] implementation.
///
/// Additionally the macro will verify the captures in the path matches the fields of the struct.
/// For example this fails to compile since the struct doesn't have a `team_id` field:
Expand Down
7 changes: 5 additions & 2 deletions axum-extra/src/typed_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use axum::{
response::{IntoResponse, IntoResponseParts, Response, ResponseParts},
};
use headers::{Header, HeaderMapExt};
use http::request::Parts;
use http::{request::Parts, StatusCode};
use std::convert::Infallible;

/// Extractor and response that works with typed header values from [`headers`].
Expand Down Expand Up @@ -156,7 +156,10 @@ impl TypedHeaderRejectionReason {

impl IntoResponse for TypedHeaderRejection {
fn into_response(self) -> Response {
(http::StatusCode::BAD_REQUEST, self.to_string()).into_response()
let status = StatusCode::BAD_REQUEST;
let body = self.to_string();
axum_core::__log_rejection!(rejection_type = Self, body_text = body, status = status,);
(status, body).into_response()
}
}

Expand Down
1 change: 0 additions & 1 deletion axum-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ __private = ["syn/visit-mut"]
proc-macro = true

[dependencies]
heck = "0.4"
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = [
Expand Down
2 changes: 1 addition & 1 deletion axum-macros/src/from_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fn expand_field(state: &Ident, idx: usize, field: &Field) -> TokenStream {
};

quote_spanned! {span=>
#[allow(clippy::clone_on_copy)]
#[allow(clippy::clone_on_copy, clippy::clone_on_ref_ptr)]
impl ::axum::extract::FromRef<#state> for #field_ty {
fn from_ref(state: &#state) -> Self {
#body
Expand Down
2 changes: 1 addition & 1 deletion axum-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ use from_request::Trait::{FromRequest, FromRequestParts};
///
/// # Known limitations
///
/// Generics are only supported on tuple structs with exactly on field. Thus this doesn't work
/// Generics are only supported on tuple structs with exactly one field. Thus this doesn't work
///
/// ```compile_fail
/// #[derive(axum_macros::FromRequest)]
Expand Down
9 changes: 9 additions & 0 deletions axum-macros/tests/debug_handler/fail/extension_not_clone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use axum::extract::Extension;
use axum_macros::debug_handler;

struct NonCloneType;

#[debug_handler]
async fn test_extension_non_clone(_: Extension<NonCloneType>) {}

fn main() {}
28 changes: 28 additions & 0 deletions axum-macros/tests/debug_handler/fail/extension_not_clone.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
error[E0277]: the trait bound `NonCloneType: Clone` is not satisfied
--> tests/debug_handler/fail/extension_not_clone.rs:7:38
|
7 | async fn test_extension_non_clone(_: Extension<NonCloneType>) {}
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonCloneType`, which is required by `Extension<NonCloneType>: FromRequest<(), _>`
|
= help: the following other types implement trait `FromRequest<S, M>`:
axum::body::Bytes
Body
Form<T>
Json<T>
axum::http::Request<Body>
RawForm
String
Option<T>
and $N others
= note: required for `Extension<NonCloneType>` to implement `FromRequestParts<()>`
= note: required for `Extension<NonCloneType>` to implement `FromRequest<(), axum_core::extract::private::ViaParts>`
note: required by a bound in `__axum_macros_check_test_extension_non_clone_0_from_request_check`
--> tests/debug_handler/fail/extension_not_clone.rs:7:38
|
7 | async fn test_extension_non_clone(_: Extension<NonCloneType>) {}
| ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `__axum_macros_check_test_extension_non_clone_0_from_request_check`
help: consider annotating `NonCloneType` with `#[derive(Clone)]`
|
4 + #[derive(Clone)]
5 | struct NonCloneType;
|
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ error[E0277]: the trait bound `for<'de> Struct: serde::de::Deserialize<'de>` is
7 | async fn handler(_foo: Json<Struct>) {}
| ^^^^^^^^^^^^ the trait `for<'de> serde::de::Deserialize<'de>` is not implemented for `Struct`, which is required by `Json<Struct>: FromRequest<()>`
|
= note: for local types consider adding `#[derive(serde::Deserialize)]` to your `Struct` type
= note: for types from other crates check whether the crate offers a `serde` feature flag
= help: the following other types implement trait `serde::de::Deserialize<'de>`:
bool
char
Expand All @@ -25,6 +27,8 @@ error[E0277]: the trait bound `for<'de> Struct: serde::de::Deserialize<'de>` is
7 | async fn handler(_foo: Json<Struct>) {}
| ^^^^^^^^^^^^ the trait `for<'de> serde::de::Deserialize<'de>` is not implemented for `Struct`, which is required by `Json<Struct>: FromRequest<()>`
|
= note: for local types consider adding `#[derive(serde::Deserialize)]` to your `Struct` type
= note: for types from other crates check whether the crate offers a `serde` feature flag
= help: the following other types implement trait `serde::de::Deserialize<'de>`:
bool
char
Expand Down
6 changes: 4 additions & 2 deletions axum/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **change:** Avoid cloning `Arc` during deserialization of `Path`
- **added:** `axum::serve::Serve::tcp_nodelay` and `axum::serve::WithGracefulShutdown::tcp_nodelay` ([#2653])
- **added:** `Router::has_routes` function ([#2790])

[#2653]: https://github.com/tokio-rs/axum/pull/2653
[#2790]: https://github.com/tokio-rs/axum/pull/2790

# 0.7.5 (24. March, 2024)

Expand Down Expand Up @@ -574,7 +576,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
```rust
use axum::{Json, http::HeaderMap};
// This wont compile on 0.6 because both `Json` and `String` need to consume
// This won't compile on 0.6 because both `Json` and `String` need to consume
// the request body. You can use either `Json` or `String`, but not both.
async fn handler_1(
json: Json<serde_json::Value>,
Expand Down Expand Up @@ -1160,7 +1162,7 @@ Yanked, as it didn't compile in release mode.
```rust
use axum::{Json, http::HeaderMap};
// This wont compile on 0.6 because both `Json` and `String` need to consume
// This won't compile on 0.6 because both `Json` and `String` need to consume
// the request body. You can use either `Json` or `String`, but not both.
async fn handler_1(
json: Json<serde_json::Value>,
Expand Down
2 changes: 1 addition & 1 deletion axum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ tower-service = "0.3"
axum-macros = { path = "../axum-macros", version = "0.4.1", optional = true }
base64 = { version = "0.21.0", optional = true }
hyper = { version = "1.1.0", optional = true }
hyper-util = { version = "0.1.3", features = ["tokio", "server"], optional = true }
hyper-util = { version = "0.1.3", features = ["tokio", "server", "service"], optional = true }
multer = { version = "3.0.0", optional = true }
serde_json = { version = "1.0", features = ["raw_value"], optional = true }
serde_path_to_error = { version = "0.1.8", optional = true }
Expand Down
1 change: 1 addition & 0 deletions axum/src/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ where
}
}

#[allow(dead_code)]
pub(crate) struct MakeErasedRouter<S> {
pub(crate) router: Router<S>,
pub(crate) into_route: fn(Router<S>, S) -> Route,
Expand Down
3 changes: 1 addition & 2 deletions axum/src/docs/extract.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ Types and traits for extracting data from requests.

A handler function is an async function that takes any number of
"extractors" as arguments. An extractor is a type that implements
[`FromRequest`](crate::extract::FromRequest)
or [`FromRequestParts`](crate::extract::FromRequestParts).
[`FromRequest`] or [`FromRequestParts`].

For example, [`Json`] is an extractor that consumes the request body and
deserializes it as JSON into some target type:
Expand Down
Loading

0 comments on commit 580d6b0

Please sign in to comment.