pavex/request/path/path_params.rs
1use pavex_macros::methods;
2use serde::Deserialize;
3
4use crate::request::path::deserializer::PathDeserializer;
5use crate::request::path::errors::{DecodeError, ExtractPathParamsError, InvalidUtf8InPathParam};
6
7use super::RawPathParams;
8
9/// Extract (typed) path parameters from the path of an incoming request.
10///
11/// # Guide
12///
13/// Check out [the guide](https://pavex.dev/docs/guide/request_data/path/path_parameters/)
14/// for more information on how to use this extractor.
15///
16/// # Example
17///
18/// ```rust
19/// use pavex::{get, request::path::PathParams};
20///
21/// // Define a route with a path parameter, `{home_id}`.
22/// // The `PathParams` extractor deserializes the extracted path parameters into
23/// // the type you specified—`Home` in this case.
24/// #[get(path = "/home/{home_id}")]
25/// pub fn get_home(params: &PathParams<Home>) -> String {
26/// format!("The identifier for this home is: {}", params.0.home_id)
27/// }
28///
29/// // The PathParams attribute macro derives the necessary (de)serialization traits.
30/// #[PathParams]
31/// pub struct Home {
32/// // The name of the field must match the name of the path parameter
33/// // used in the route definition.
34/// home_id: u32
35/// }
36/// ```
37///
38/// `home_id` will be set to `1` for an incoming `/home/1` request.
39/// Extraction will fail, instead, if we receive an `/home/abc` request.
40#[doc(alias = "Path")]
41#[doc(alias = "RouteParams")]
42#[doc(alias = "UrlParams")]
43pub struct PathParams<T>(
44 /// The extracted path parameters, deserialized into `T`, the type you specified.
45 pub T,
46);
47
48#[methods]
49impl<T> PathParams<T> {
50 /// The default constructor for [`PathParams`].
51 ///
52 /// If the extraction fails, an [`ExtractPathParamsError`] is returned.
53 #[request_scoped(pavex = crate, id = "PATH_PARAMS_EXTRACT")]
54 pub fn extract<'server, 'request>(
55 params: RawPathParams<'server, 'request>,
56 ) -> Result<Self, ExtractPathParamsError>
57 where
58 T: Deserialize<'request>,
59 // The parameter ids live as long as the server, while the values are tied to the lifecycle
60 // of an incoming request. So it's always true that 'key outlives 'value.
61 'server: 'request,
62 {
63 let mut decoded_params = Vec::with_capacity(params.len());
64 for (id, value) in params.iter() {
65 let decoded_value = value.decode().map_err(|e| {
66 let DecodeError {
67 invalid_raw_segment,
68 source,
69 } = e;
70 ExtractPathParamsError::InvalidUtf8InPathParameter(InvalidUtf8InPathParam {
71 invalid_key: id.into(),
72 invalid_raw_segment,
73 source,
74 })
75 })?;
76 decoded_params.push((id, decoded_value));
77 }
78 let deserializer = PathDeserializer::new(&decoded_params);
79 T::deserialize(deserializer)
80 .map_err(ExtractPathParamsError::PathDeserializationError)
81 .map(PathParams)
82 }
83}