Document optional extractors

This commit is contained in:
Jonas Platte 2024-11-30 17:48:20 +01:00
parent a8e61360b0
commit 1f2c649804
No known key found for this signature in database
GPG key ID: 7D261D771D915378
5 changed files with 67 additions and 29 deletions

View file

@ -6,7 +6,8 @@ use crate::response::IntoResponse;
use super::{private, FromRequest, FromRequestParts, Request};
/// TODO: DOCS
/// Customize the behavior of `Option<Self>` as a [`FromRequestParts`]
/// extractor.
pub trait OptionalFromRequestParts<S>: Sized {
/// If the extractor fails, it will use this "rejection" type.
///
@ -20,7 +21,7 @@ pub trait OptionalFromRequestParts<S>: Sized {
) -> impl Future<Output = Result<Option<Self>, Self::Rejection>> + Send;
}
/// TODO: DOCS
/// Customize the behavior of `Option<Self>` as a [`FromRequest`] extractor.
pub trait OptionalFromRequest<S, M = private::ViaRequest>: Sized {
/// If the extractor fails, it will use this "rejection" type.
///

View file

@ -18,6 +18,19 @@ use std::fmt;
/// with the `multiple` attribute. Those values can be collected into a `Vec` or other sequential
/// container.
///
/// # `Option<Query<T>>` behavior
///
/// If `Query<T>` itself is used as an extractor and there is no query string in
/// the request URL, `T`'s `Deserialize` implementation is called on an empty
/// string instead.
///
/// You can avoid this by using `Option<Query<T>>`, which gives you `None` in
/// the case that there is no query string in the request URL.
///
/// Note that an empty query string is not the same as no query string, that is
/// `https://example.org/` and `https://example.org/?` are not treated the same
/// in this case.
///
/// # Example
///
/// ```rust,no_run

View file

@ -200,29 +200,11 @@ async fn handler(
axum enforces this by requiring the last extractor implements [`FromRequest`]
and all others implement [`FromRequestParts`].
# Optional extractors
# Handling extractor rejections
TODO: Docs, more realistic example
```rust,no_run
use axum::{routing::post, Router};
use axum_extra::{headers::UserAgent, TypedHeader};
use serde_json::Value;
async fn foo(user_agent: Option<TypedHeader<UserAgent>>) {
if let Some(TypedHeader(user_agent)) = user_agent {
// The client sent a user agent
} else {
// No user agent header
}
}
let app = Router::new().route("/foo", post(foo));
# let _: Router = app;
```
Wrapping extractors in `Result` makes them optional and gives you the reason
the extraction failed:
If you want to handle the case of an extractor failing within a specific
handler, you can wrap it in `Result`, with the error being the rejection type
of the extractor:
```rust,no_run
use axum::{
@ -261,10 +243,33 @@ let app = Router::new().route("/users", post(create_user));
# let _: Router = app;
```
Another option is to make use of the optional extractors in [axum-extra] that
either returns `None` if there are no query parameters in the request URI,
or returns `Some(T)` if deserialization was successful.
If the deserialization was not successful, the request is rejected.
# Optional extractors
Some extractors implement [`OptionalFromRequestParts`] in addition to
[`FromRequestParts`], or [`OptionalFromRequest`] in addition to [`FromRequest`].
These extractors can be used inside of `Option`. It depends on the particular
`OptionalFromRequestParts` or `OptionalFromRequest` implementation what this
does: For example for `TypedHeader` from axum-extra, you get `None` if the
header you're trying to extract is not part of the request, but if the header
is present and fails to parse, the request is rejected.
```rust,no_run
use axum::{routing::post, Router};
use axum_extra::{headers::UserAgent, TypedHeader};
use serde_json::Value;
async fn foo(user_agent: Option<TypedHeader<UserAgent>>) {
if let Some(TypedHeader(user_agent)) = user_agent {
// The client sent a user agent
} else {
// No user agent header
}
}
let app = Router::new().route("/foo", post(foo));
# let _: Router = app;
```
# Customizing extractor responses

View file

@ -24,6 +24,12 @@ use std::{fmt, sync::Arc};
/// parameters must be valid UTF-8, otherwise `Path` will fail and return a `400
/// Bad Request` response.
///
/// # `Option<Path<T>>` behavior
///
/// You can use `Option<Path<T>>` as an extractor to allow the same handler to
/// be used in a route with parameters that deserialize to `T`, and another
/// route with no parameters at all.
///
/// # Example
///
/// These examples assume the `serde` feature of the [`uuid`] crate is enabled.

View file

@ -6,7 +6,20 @@ use serde::de::DeserializeOwned;
///
/// `T` is expected to implement [`serde::Deserialize`].
///
/// # Example
/// # `Option<Query<T>>` behavior
///
/// If `Query<T>` itself is used as an extractor and there is no query string in
/// the request URL, `T`'s `Deserialize` implementation is called on an empty
/// string instead.
///
/// You can avoid this by using `Option<Query<T>>`, which gives you `None` in
/// the case that there is no query string in the request URL.
///
/// Note that an empty query string is not the same as no query string, that is
/// `https://example.org/` and `https://example.org/?` are not treated the same
/// in this case.
///
/// # Examples
///
/// ```rust,no_run
/// use axum::{