volo_http/server/
param.rs

1//! Collections for path params from uri.
2//!
3//! See [`Router::route`][route] and [`PathParamsVec`], [`PathParamsMap`] or [`PathParams`] for
4//! more details.
5//!
6//! [route]: crate::server::route::Router::route
7
8use std::{convert::Infallible, error::Error, fmt, ops::Deref, str::FromStr};
9
10use ahash::AHashMap;
11use bytes::{BufMut, BytesMut};
12use faststr::FastStr;
13use http::{StatusCode, request::Parts};
14use matchit::Params;
15
16use super::{IntoResponse, extract::FromContext};
17use crate::{
18    context::ServerContext, error::BoxError, response::Response, utils::macros::all_the_tuples,
19};
20
21/// Collected params from request uri
22///
23/// # Examples
24///
25/// ```
26/// use volo_http::server::{
27///     param::PathParamsVec,
28///     route::{Router, get},
29/// };
30///
31/// async fn params(params: PathParamsVec) -> String {
32///     params
33///         .into_iter()
34///         .map(|(k, v)| format!("{k}: {v}"))
35///         .collect::<Vec<_>>()
36///         .join("\n")
37/// }
38///
39/// let router: Router = Router::new().route("/user/{uid}/posts/{tid}", get(params));
40/// ```
41#[derive(Clone, Debug, Default)]
42pub struct PathParamsVec {
43    inner: Vec<(FastStr, FastStr)>,
44}
45
46impl PathParamsVec {
47    pub(crate) fn extend(&mut self, params: Params) {
48        self.inner.reserve(params.len());
49
50        let cap = params.iter().map(|(k, v)| k.len() + v.len()).sum();
51        let mut buf = BytesMut::with_capacity(cap);
52
53        for (k, v) in params.iter() {
54            buf.put(k.as_bytes());
55            // SAFETY: The key is a valid string
56            let k = unsafe { FastStr::from_bytes_unchecked(buf.split().freeze()) };
57
58            buf.put(v.as_bytes());
59            // SAFETY: The value is a valid string
60            let v = unsafe { FastStr::from_bytes_unchecked(buf.split().freeze()) };
61
62            self.inner.push((k, v));
63        }
64    }
65
66    pub(crate) fn pop(&mut self) -> Option<(FastStr, FastStr)> {
67        self.inner.pop()
68    }
69}
70
71impl IntoIterator for PathParamsVec {
72    type Item = (FastStr, FastStr);
73    type IntoIter = std::vec::IntoIter<(FastStr, FastStr)>;
74
75    fn into_iter(self) -> Self::IntoIter {
76        self.inner.into_iter()
77    }
78}
79
80impl Deref for PathParamsVec {
81    type Target = [(FastStr, FastStr)];
82
83    fn deref(&self) -> &Self::Target {
84        &self.inner
85    }
86}
87
88impl FromContext for PathParamsVec {
89    type Rejection = Infallible;
90
91    async fn from_context(cx: &mut ServerContext, _: &mut Parts) -> Result<Self, Self::Rejection> {
92        Ok(cx.params().clone())
93    }
94}
95
96/// Map for params from request uri
97///
98/// # Examples
99///
100/// ```
101/// use volo_http::server::{
102///     param::PathParamsMap,
103///     route::{Router, get},
104/// };
105///
106/// async fn params(params: PathParamsMap) -> String {
107///     let uid = params.get("uid").unwrap();
108///     let tid = params.get("tid").unwrap();
109///     format!("uid: {uid}, tid: {tid}")
110/// }
111///
112/// let router: Router = Router::new().route("/user/{uid}/posts/{tid}", get(params));
113/// ```
114#[derive(Debug, Default, Clone)]
115pub struct PathParamsMap {
116    inner: AHashMap<FastStr, FastStr>,
117}
118
119impl Deref for PathParamsMap {
120    type Target = AHashMap<FastStr, FastStr>;
121
122    fn deref(&self) -> &Self::Target {
123        &self.inner
124    }
125}
126
127impl IntoIterator for PathParamsMap {
128    type Item = (FastStr, FastStr);
129    type IntoIter = std::collections::hash_map::IntoIter<FastStr, FastStr>;
130
131    fn into_iter(self) -> Self::IntoIter {
132        self.inner.into_iter()
133    }
134}
135
136impl From<PathParamsVec> for PathParamsMap {
137    fn from(value: PathParamsVec) -> Self {
138        let mut inner = AHashMap::with_capacity(value.inner.len());
139
140        for (k, v) in value.inner.into_iter() {
141            inner.insert(k, v);
142        }
143
144        Self { inner }
145    }
146}
147
148impl FromContext for PathParamsMap {
149    type Rejection = Infallible;
150
151    async fn from_context(cx: &mut ServerContext, _: &mut Parts) -> Result<Self, Self::Rejection> {
152        let params = cx.params();
153        let mut inner = AHashMap::with_capacity(params.len());
154
155        for (k, v) in params.iter() {
156            inner.insert(k.clone(), v.clone());
157        }
158
159        Ok(Self { inner })
160    }
161}
162
163trait FromPathParam: Sized {
164    fn from_path_param(param: &str) -> Result<Self, PathParamsRejection>;
165}
166
167macro_rules! impl_from_path_param {
168    ($ty:ty) => {
169        impl FromPathParam for $ty {
170            fn from_path_param(param: &str) -> Result<Self, PathParamsRejection> {
171                FromStr::from_str(param)
172                    .map_err(Into::into)
173                    .map_err(PathParamsRejection::ParseError)
174            }
175        }
176    };
177}
178
179impl_from_path_param!(bool);
180impl_from_path_param!(u8);
181impl_from_path_param!(u16);
182impl_from_path_param!(u32);
183impl_from_path_param!(u64);
184impl_from_path_param!(usize);
185impl_from_path_param!(i8);
186impl_from_path_param!(i16);
187impl_from_path_param!(i32);
188impl_from_path_param!(i64);
189impl_from_path_param!(isize);
190impl_from_path_param!(char);
191impl_from_path_param!(String);
192impl_from_path_param!(FastStr);
193
194/// Extractor for params from request uri
195///
196/// # Examples
197///
198/// ```
199/// use volo_http::server::{
200///     param::PathParams,
201///     route::{Router, get},
202/// };
203///
204/// async fn params(PathParams((uid, tid)): PathParams<(usize, usize)>) -> String {
205///     format!("uid: {uid}, tid: {tid}")
206/// }
207///
208/// let router: Router = Router::new().route("/user/{uid}/posts/{tid}", get(params));
209/// ```
210#[derive(Debug, Default, Clone)]
211pub struct PathParams<T>(pub T);
212
213impl<T> FromContext for PathParams<T>
214where
215    T: FromPathParam,
216{
217    type Rejection = PathParamsRejection;
218
219    async fn from_context(cx: &mut ServerContext, _: &mut Parts) -> Result<Self, Self::Rejection> {
220        let mut param_iter = cx.params().iter();
221        let t = T::from_path_param(
222            param_iter
223                .next()
224                .ok_or(PathParamsRejection::LengthMismatch)?
225                .1
226                .as_str(),
227        )?;
228        Ok(PathParams(t))
229    }
230}
231
232macro_rules! impl_path_params_extractor {
233    (
234        $($ty:ident),+ $(,)?
235    ) => {
236        #[allow(non_snake_case)]
237        impl<$($ty,)+> FromContext for PathParams<($($ty,)+)>
238        where
239            $(
240                $ty: FromPathParam,
241            )+
242        {
243            type Rejection = PathParamsRejection;
244
245            async fn from_context(
246                cx: &mut ServerContext,
247                _: &mut Parts,
248            ) -> Result<Self, Self::Rejection> {
249                let mut param_iter = cx.params().iter();
250                $(
251                    let $ty = $ty::from_path_param(
252                        param_iter.next().ok_or(PathParamsRejection::LengthMismatch)?.1.as_str(),
253                    )?;
254                )+
255                Ok(PathParams(($($ty,)+)))
256            }
257        }
258    };
259}
260
261all_the_tuples!(impl_path_params_extractor);
262
263/// [`PathParams`] specified rejections
264#[derive(Debug)]
265pub enum PathParamsRejection {
266    /// The number of params does not match the number of idents in [`PathParams`]
267    LengthMismatch,
268    /// Error when parsing a string to the specified type
269    ParseError(BoxError),
270}
271
272impl fmt::Display for PathParamsRejection {
273    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274        match self {
275            Self::LengthMismatch => write!(
276                f,
277                "the number of path params does not match number of types in `PathParams`"
278            ),
279            Self::ParseError(e) => write!(f, "path param parse error: {e}"),
280        }
281    }
282}
283
284impl Error for PathParamsRejection {
285    fn source(&self) -> Option<&(dyn Error + 'static)> {
286        match self {
287            Self::LengthMismatch => None,
288            Self::ParseError(e) => Some(e.as_ref()),
289        }
290    }
291}
292
293impl IntoResponse for PathParamsRejection {
294    fn into_response(self) -> Response {
295        StatusCode::BAD_REQUEST.into_response()
296    }
297}