tork_core/extract/
path.rs1use std::fmt::Display;
4use std::str::FromStr;
5
6use crate::error::{Error, Result};
7use crate::extract::RequestContext;
8
9pub trait FromPathParam: Sized {
14 fn from_path_param(name: &str, value: &str) -> Result<Self>;
19}
20
21impl<T> FromPathParam for T
22where
23 T: FromStr,
24 T::Err: Display,
25{
26 fn from_path_param(name: &str, value: &str) -> Result<Self> {
27 value
28 .parse::<T>()
29 .map_err(|_| Error::unprocessable(format!("invalid value for path parameter `{name}`")))
30 }
31}
32
33#[doc(hidden)]
39pub fn __extract_path_param<T: FromPathParam>(ctx: &RequestContext, name: &str) -> Result<T> {
40 match ctx.path_param(name) {
41 Some(value) => T::from_path_param(name, value),
42 None => Err(Error::internal(format!(
43 "path parameter `{name}` was not captured by the router"
44 ))),
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use crate::body::box_body;
52 use crate::extract::PathParams;
53 use crate::state::StateMap;
54 use bytes::Bytes;
55 use http_body_util::Full;
56 use std::sync::Arc;
57
58 fn context(params: PathParams) -> RequestContext {
59 let head = http::Request::new(()).into_parts().0;
60 let body = box_body(Full::new(Bytes::new()));
61 RequestContext::new(head, params, Arc::new(StateMap::new()), body)
62 }
63
64 #[test]
65 fn missing_param_is_internal_router_error() {
66 let error =
67 __extract_path_param::<i64>(&context(PathParams::new()), "user_id").unwrap_err();
68
69 assert_eq!(error.kind(), crate::error::ErrorKind::Internal);
70 assert_eq!(
71 error.message(),
72 "path parameter `user_id` was not captured by the router"
73 );
74 }
75
76 #[test]
77 fn invalid_param_is_unprocessable() {
78 let mut params = PathParams::new();
79 params.push("user_id".to_owned(), "abc".to_owned());
80
81 let error = __extract_path_param::<i64>(&context(params), "user_id").unwrap_err();
82 assert_eq!(error.kind(), crate::error::ErrorKind::Unprocessable);
83 assert_eq!(
84 error.message(),
85 "invalid value for path parameter `user_id`"
86 );
87 }
88}