Skip to content

Commit 1f2c649

Browse files
committed
Document optional extractors
1 parent a8e6136 commit 1f2c649

File tree

5 files changed

+67
-29
lines changed

5 files changed

+67
-29
lines changed

axum-core/src/extract/option.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use crate::response::IntoResponse;
66

77
use super::{private, FromRequest, FromRequestParts, Request};
88

9-
/// TODO: DOCS
9+
/// Customize the behavior of `Option<Self>` as a [`FromRequestParts`]
10+
/// extractor.
1011
pub trait OptionalFromRequestParts<S>: Sized {
1112
/// If the extractor fails, it will use this "rejection" type.
1213
///
@@ -20,7 +21,7 @@ pub trait OptionalFromRequestParts<S>: Sized {
2021
) -> impl Future<Output = Result<Option<Self>, Self::Rejection>> + Send;
2122
}
2223

23-
/// TODO: DOCS
24+
/// Customize the behavior of `Option<Self>` as a [`FromRequest`] extractor.
2425
pub trait OptionalFromRequest<S, M = private::ViaRequest>: Sized {
2526
/// If the extractor fails, it will use this "rejection" type.
2627
///

axum-extra/src/extract/query.rs

+13
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ use std::fmt;
1818
/// with the `multiple` attribute. Those values can be collected into a `Vec` or other sequential
1919
/// container.
2020
///
21+
/// # `Option<Query<T>>` behavior
22+
///
23+
/// If `Query<T>` itself is used as an extractor and there is no query string in
24+
/// the request URL, `T`'s `Deserialize` implementation is called on an empty
25+
/// string instead.
26+
///
27+
/// You can avoid this by using `Option<Query<T>>`, which gives you `None` in
28+
/// the case that there is no query string in the request URL.
29+
///
30+
/// Note that an empty query string is not the same as no query string, that is
31+
/// `https://example.org/` and `https://example.org/?` are not treated the same
32+
/// in this case.
33+
///
2134
/// # Example
2235
///
2336
/// ```rust,no_run

axum/src/docs/extract.md

+31-26
Original file line numberDiff line numberDiff line change
@@ -200,29 +200,11 @@ async fn handler(
200200
axum enforces this by requiring the last extractor implements [`FromRequest`]
201201
and all others implement [`FromRequestParts`].
202202

203-
# Optional extractors
204-
205-
TODO: Docs, more realistic example
206-
207-
```rust,no_run
208-
use axum::{routing::post, Router};
209-
use axum_extra::{headers::UserAgent, TypedHeader};
210-
use serde_json::Value;
211-
212-
async fn foo(user_agent: Option<TypedHeader<UserAgent>>) {
213-
if let Some(TypedHeader(user_agent)) = user_agent {
214-
// The client sent a user agent
215-
} else {
216-
// No user agent header
217-
}
218-
}
203+
# Handling extractor rejections
219204

220-
let app = Router::new().route("/foo", post(foo));
221-
# let _: Router = app;
222-
```
223-
224-
Wrapping extractors in `Result` makes them optional and gives you the reason
225-
the extraction failed:
205+
If you want to handle the case of an extractor failing within a specific
206+
handler, you can wrap it in `Result`, with the error being the rejection type
207+
of the extractor:
226208

227209
```rust,no_run
228210
use axum::{
@@ -261,10 +243,33 @@ let app = Router::new().route("/users", post(create_user));
261243
# let _: Router = app;
262244
```
263245

264-
Another option is to make use of the optional extractors in [axum-extra] that
265-
either returns `None` if there are no query parameters in the request URI,
266-
or returns `Some(T)` if deserialization was successful.
267-
If the deserialization was not successful, the request is rejected.
246+
# Optional extractors
247+
248+
Some extractors implement [`OptionalFromRequestParts`] in addition to
249+
[`FromRequestParts`], or [`OptionalFromRequest`] in addition to [`FromRequest`].
250+
251+
These extractors can be used inside of `Option`. It depends on the particular
252+
`OptionalFromRequestParts` or `OptionalFromRequest` implementation what this
253+
does: For example for `TypedHeader` from axum-extra, you get `None` if the
254+
header you're trying to extract is not part of the request, but if the header
255+
is present and fails to parse, the request is rejected.
256+
257+
```rust,no_run
258+
use axum::{routing::post, Router};
259+
use axum_extra::{headers::UserAgent, TypedHeader};
260+
use serde_json::Value;
261+
262+
async fn foo(user_agent: Option<TypedHeader<UserAgent>>) {
263+
if let Some(TypedHeader(user_agent)) = user_agent {
264+
// The client sent a user agent
265+
} else {
266+
// No user agent header
267+
}
268+
}
269+
270+
let app = Router::new().route("/foo", post(foo));
271+
# let _: Router = app;
272+
```
268273

269274
# Customizing extractor responses
270275

axum/src/extract/path/mod.rs

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

axum/src/extract/query.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,20 @@ use serde::de::DeserializeOwned;
66
///
77
/// `T` is expected to implement [`serde::Deserialize`].
88
///
9-
/// # Example
9+
/// # `Option<Query<T>>` behavior
10+
///
11+
/// If `Query<T>` itself is used as an extractor and there is no query string in
12+
/// the request URL, `T`'s `Deserialize` implementation is called on an empty
13+
/// string instead.
14+
///
15+
/// You can avoid this by using `Option<Query<T>>`, which gives you `None` in
16+
/// the case that there is no query string in the request URL.
17+
///
18+
/// Note that an empty query string is not the same as no query string, that is
19+
/// `https://example.org/` and `https://example.org/?` are not treated the same
20+
/// in this case.
21+
///
22+
/// # Examples
1023
///
1124
/// ```rust,no_run
1225
/// use axum::{

0 commit comments

Comments
 (0)