Skip to main content

mini_serve/
extract.rs

1use hyper::Request;
2use serde::de::value::{Error as ValueError, MapDeserializer};
3use serde::de::{self, DeserializeOwned, Deserializer, IntoDeserializer, Visitor};
4
5use crate::error::ServeError;
6use crate::router::{PathParams, QueryParams};
7
8/// Shared empty maps handed out when a request carries no params.
9///
10/// The router and app skip inserting `PathParams` and `QueryParams` when they would be
11/// empty, so most requests never pay to box one. Readers must not be able to tell the
12/// difference between "absent" and "empty", or the saving is paid for with a silent
13/// breakage at every call site — hence these, rather than each reader inventing its own
14/// fallback. `HashMap::new` does not allocate, so neither of these ever does.
15static EMPTY_PATH_PARAMS: std::sync::OnceLock<PathParams> = std::sync::OnceLock::new();
16static EMPTY_QUERY_PARAMS: std::sync::OnceLock<QueryParams> = std::sync::OnceLock::new();
17
18/// Read the request's query parameters, treating a request with no query string as one
19/// with no parameters.
20///
21/// Always returns a map: a request whose query string was absent, empty, or unparseable
22/// is indistinguishable here from one that carried `?` and nothing else.
23///
24/// # Example
25///
26/// ```ignore
27/// use mini_serve::query_params;
28///
29/// let page = query_params(&req).0.get("page").cloned();
30/// ```
31pub fn query_params<B>(req: &Request<B>) -> &QueryParams {
32	req.extensions()
33		.get::<QueryParams>()
34		.unwrap_or_else(|| EMPTY_QUERY_PARAMS.get_or_init(QueryParams::default))
35}
36
37/// Extract path parameters from the request and deserialize into type `T`.
38///
39/// Path parameters are decoded and matched by the router, then deserialized
40/// via serde's `MapDeserializer`. Returns `400 Bad Request` if deserialization
41/// fails (e.g., an unparseable segment for a numeric type) or if the route
42/// captured no parameters at all — asking a param-less route for its params is
43/// a bad request, not a server fault, and it used to report `500`.
44///
45/// # Example
46///
47/// ```ignore
48/// use serde::Deserialize;
49/// use mini_serve::path_params;
50///
51/// #[derive(Deserialize)]
52/// struct ItemId {
53///     id: u64,
54/// }
55///
56/// let item = path_params::<ItemId, _>(req)?;
57/// println!("Item ID: {}", item.id);
58/// ```
59pub fn path_params<T: DeserializeOwned, B>(req: &Request<B>) -> Result<T, ServeError> {
60	let params = req
61		.extensions()
62		.get::<PathParams>()
63		.unwrap_or_else(|| EMPTY_PATH_PARAMS.get_or_init(PathParams::default));
64
65	let pairs = params.0.iter().map(|(k, v)| (k.clone(), ParamValue(v.clone())));
66	let deserializer = MapDeserializer::<_, ValueError>::new(pairs);
67	T::deserialize(deserializer)
68		.map_err(|_| ServeError::new(400, "invalid path parameters"))
69}
70
71/// Deserializes a single path-param string into whatever scalar type the
72/// target struct field asks for, parsing on demand rather than going through
73/// an intermediate query-string representation.
74struct ParamValue(String);
75
76macro_rules! deserialize_parsed {
77	($($method:ident => $visit:ident : $ty:ty),* $(,)?) => {
78		$(
79			fn $method<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
80				match self.0.parse::<$ty>() {
81					Ok(v) => visitor.$visit(v),
82					Err(_) => Err(de::Error::invalid_value(
83						de::Unexpected::Str(&self.0),
84						&stringify!($ty),
85					)),
86				}
87			}
88		)*
89	};
90}
91
92impl<'de> Deserializer<'de> for ParamValue {
93	type Error = ValueError;
94
95	fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
96		visitor.visit_string(self.0)
97	}
98
99	fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
100		visitor.visit_some(self)
101	}
102
103	deserialize_parsed! {
104		deserialize_bool => visit_bool: bool,
105		deserialize_i8 => visit_i8: i8,
106		deserialize_i16 => visit_i16: i16,
107		deserialize_i32 => visit_i32: i32,
108		deserialize_i64 => visit_i64: i64,
109		deserialize_i128 => visit_i128: i128,
110		deserialize_u8 => visit_u8: u8,
111		deserialize_u16 => visit_u16: u16,
112		deserialize_u32 => visit_u32: u32,
113		deserialize_u64 => visit_u64: u64,
114		deserialize_u128 => visit_u128: u128,
115		deserialize_f32 => visit_f32: f32,
116		deserialize_f64 => visit_f64: f64,
117		deserialize_char => visit_char: char,
118	}
119
120	serde::forward_to_deserialize_any! {
121		str string bytes byte_buf unit unit_struct newtype_struct seq tuple
122		tuple_struct map struct enum identifier ignored_any
123	}
124}
125
126impl<'de> IntoDeserializer<'de, ValueError> for ParamValue {
127	type Deserializer = Self;
128
129	fn into_deserializer(self) -> Self {
130		self
131	}
132}
133
134#[cfg(test)]
135#[path = "../tests/unit/extract.rs"]
136mod tests;