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