salvo_oapi/extract/parameter/
path.rs1use 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
11pub struct PathParam<T>(pub T);
13impl<T> PathParam<T> {
14 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}