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| PathParam(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() -> &'ex 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 {} not found or convert to type failed",
81                arg
82            ))
83        })?;
84        Ok(Self(value))
85    }
86}
87
88impl<T> EndpointArgRegister for PathParam<T>
89where
90    T: ToSchema,
91{
92    fn register(components: &mut Components, operation: &mut Operation, arg: &str) {
93        let parameter = Parameter::new(arg)
94            .parameter_in(ParameterIn::Path)
95            .description(format!("Get parameter `{arg}` from request url path."))
96            .schema(T::to_schema(components))
97            .required(true);
98        operation.parameters.insert(parameter);
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use assert_json_diff::assert_json_eq;
105    use salvo_core::test::TestClient;
106    use serde_json::json;
107
108    use super::*;
109
110    #[test]
111    fn test_path_param_into_inner() {
112        let param = PathParam::<String>("param".to_string());
113        assert_eq!("param".to_string(), param.into_inner());
114    }
115
116    #[test]
117    fn test_path_param_deref() {
118        let param = PathParam::<String>("param".to_string());
119        assert_eq!(&"param".to_string(), param.deref())
120    }
121
122    #[test]
123    fn test_path_param_deref_mut() {
124        let mut param = PathParam::<String>("param".to_string());
125        assert_eq!(&mut "param".to_string(), param.deref_mut())
126    }
127
128    #[test]
129    fn test_path_param_deserialize() {
130        let param = serde_json::from_str::<PathParam<String>>(r#""param""#).unwrap();
131        assert_eq!(param.0, "param");
132    }
133
134    #[test]
135    fn test_path_param_debug() {
136        let param = PathParam::<String>("param".to_string());
137        assert_eq!(format!("{:?}", param), r#""param""#);
138    }
139
140    #[test]
141    fn test_path_param_display() {
142        let param = PathParam::<String>("param".to_string());
143        assert_eq!(format!("{}", param), "param");
144    }
145
146    #[test]
147    fn test_path_param_metadata() {
148        let metadata = PathParam::<String>::metadata();
149        assert_eq!("", metadata.name);
150    }
151
152    #[tokio::test]
153    #[should_panic]
154    async fn test_path_prarm_extract() {
155        let mut req = Request::new();
156        let _ = PathParam::<String>::extract(&mut req).await;
157    }
158
159    #[tokio::test]
160    async fn test_path_prarm_extract_with_value() {
161        let req = TestClient::get("http://127.0.0.1:5801").build_hyper();
162        let schema = req.uri().scheme().cloned().unwrap();
163        let mut req = Request::from_hyper(req, schema);
164        req.params_mut().insert("param", "param".to_string());
165        let result = PathParam::<String>::extract_with_arg(&mut req, "param").await;
166        assert_eq!(result.unwrap().0, "param");
167    }
168
169    #[tokio::test]
170    #[should_panic]
171    async fn test_path_prarm_extract_with_value_panic() {
172        let req = TestClient::get("http://127.0.0.1:5801").build_hyper();
173        let schema = req.uri().scheme().cloned().unwrap();
174        let mut req = Request::from_hyper(req, schema);
175        let result = PathParam::<String>::extract_with_arg(&mut req, "param").await;
176        assert_eq!(result.unwrap().0, "param");
177    }
178
179    #[test]
180    fn test_path_param_register() {
181        let mut components = Components::new();
182        let mut operation = Operation::new();
183        PathParam::<String>::register(&mut components, &mut operation, "arg");
184
185        assert_json_eq!(
186            operation,
187            json!({
188                "parameters": [
189                    {
190                        "name": "arg",
191                        "in": "path",
192                        "description": "Get parameter `arg` from request url path.",
193                        "required": true,
194                        "schema": {
195                            "type": "string"
196                        }
197                    }
198                ],
199                "responses": {}
200            })
201        )
202    }
203}