salvo_oapi/extract/parameter/
path.rs

1use std::fmt::{self, Debug, Formatter};
2use std::ops::{Deref, DerefMut};
3
4use salvo_core::extract::{Extractible, Metadata};
5use salvo_core::http::{ParseError, Request};
6use serde::{Deserialize, Deserializer};
7
8use crate::endpoint::EndpointArgRegister;
9use crate::{Components, Operation, Parameter, ParameterIn, ToSchema};
10
11/// Represents the parameters passed by the URI path.
12pub struct PathParam<T>(pub T);
13impl<T> PathParam<T> {
14    /// Consumes self and returns the value of the parameter.
15    pub fn into_inner(self) -> T {
16        self.0
17    }
18}
19
20impl<T> Deref for PathParam<T> {
21    type Target = T;
22
23    fn deref(&self) -> &Self::Target {
24        &self.0
25    }
26}
27
28impl<T> DerefMut for PathParam<T> {
29    fn deref_mut(&mut self) -> &mut Self::Target {
30        &mut self.0
31    }
32}
33
34impl<'de, T> Deserialize<'de> for PathParam<T>
35where
36    T: Deserialize<'de>,
37{
38    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39    where
40        D: Deserializer<'de>,
41    {
42        T::deserialize(deserializer).map(|value| Self(value))
43    }
44}
45
46impl<T> fmt::Debug for PathParam<T>
47where
48    T: Debug,
49{
50    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
51        self.0.fmt(f)
52    }
53}
54
55impl<T> fmt::Display for PathParam<T>
56where
57    T: fmt::Display,
58{
59    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
60        self.0.fmt(f)
61    }
62}
63
64impl<'ex, T> Extractible<'ex> for PathParam<T>
65where
66    T: Deserialize<'ex>,
67{
68    fn metadata() -> &'static Metadata {
69        static METADATA: Metadata = Metadata::new("");
70        &METADATA
71    }
72    #[allow(refining_impl_trait)]
73    async fn extract(_req: &'ex mut Request) -> Result<Self, ParseError> {
74        unimplemented!("path parameter can not be extracted from request")
75    }
76    #[allow(refining_impl_trait)]
77    async fn extract_with_arg(req: &'ex mut Request, arg: &str) -> Result<Self, ParseError> {
78        let value = req.param(arg).ok_or_else(|| {
79            ParseError::other(format!(
80                "path parameter {arg} not found or convert to type failed"
81            ))
82        })?;
83        Ok(Self(value))
84    }
85}
86
87impl<T> EndpointArgRegister for PathParam<T>
88where
89    T: ToSchema,
90{
91    fn register(components: &mut Components, operation: &mut Operation, arg: &str) {
92        let parameter = Parameter::new(arg)
93            .parameter_in(ParameterIn::Path)
94            .description(format!("Get parameter `{arg}` from request url path."))
95            .schema(T::to_schema(components))
96            .required(true);
97        operation.parameters.insert(parameter);
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use assert_json_diff::assert_json_eq;
104    use salvo_core::test::TestClient;
105    use serde_json::json;
106
107    use super::*;
108
109    #[test]
110    fn test_path_param_into_inner() {
111        let param = PathParam::<String>("param".to_owned());
112        assert_eq!("param".to_owned(), param.into_inner());
113    }
114
115    #[test]
116    fn test_path_param_deref() {
117        let param = PathParam::<String>("param".to_owned());
118        assert_eq!(&"param".to_owned(), param.deref())
119    }
120
121    #[test]
122    fn test_path_param_deref_mut() {
123        let mut param = PathParam::<String>("param".to_owned());
124        assert_eq!(&mut "param".to_owned(), param.deref_mut())
125    }
126
127    #[test]
128    fn test_path_param_deserialize() {
129        let param = serde_json::from_str::<PathParam<String>>(r#""param""#).unwrap();
130        assert_eq!(param.0, "param");
131    }
132
133    #[test]
134    fn test_path_param_debug() {
135        let param = PathParam::<String>("param".to_owned());
136        assert_eq!(format!("{param:?}"), r#""param""#);
137    }
138
139    #[test]
140    fn test_path_param_display() {
141        let param = PathParam::<String>("param".to_owned());
142        assert_eq!(format!("{param}"), "param");
143    }
144
145    #[test]
146    fn test_path_param_metadata() {
147        let metadata = PathParam::<String>::metadata();
148        assert_eq!("", metadata.name);
149    }
150
151    #[tokio::test]
152    #[should_panic]
153    async fn test_path_prarm_extract() {
154        let mut req = Request::new();
155        let _ = PathParam::<String>::extract(&mut req).await;
156    }
157
158    #[tokio::test]
159    async fn test_path_prarm_extract_with_value() {
160        let req = TestClient::get("http://127.0.0.1:5801").build_hyper();
161        let schema = req.uri().scheme().cloned().unwrap();
162        let mut req = Request::from_hyper(req, schema);
163        req.params_mut().insert("param", "param".to_owned());
164        let result = PathParam::<String>::extract_with_arg(&mut req, "param").await;
165        assert_eq!(result.unwrap().0, "param");
166    }
167
168    #[tokio::test]
169    #[should_panic]
170    async fn test_path_prarm_extract_with_value_panic() {
171        let req = TestClient::get("http://127.0.0.1:5801").build_hyper();
172        let schema = req.uri().scheme().cloned().unwrap();
173        let mut req = Request::from_hyper(req, schema);
174        let result = PathParam::<String>::extract_with_arg(&mut req, "param").await;
175        assert_eq!(result.unwrap().0, "param");
176    }
177
178    #[test]
179    fn test_path_param_register() {
180        let mut components = Components::new();
181        let mut operation = Operation::new();
182        PathParam::<String>::register(&mut components, &mut operation, "arg");
183
184        assert_json_eq!(
185            operation,
186            json!({
187                "parameters": [
188                    {
189                        "name": "arg",
190                        "in": "path",
191                        "description": "Get parameter `arg` from request url path.",
192                        "required": true,
193                        "schema": {
194                            "type": "string"
195                        }
196                    }
197                ],
198                "responses": {}
199            })
200        )
201    }
202}