1use std::sync::OnceLock;
41
42use salvo::extract::metadata::{Metadata, Source, SourceFrom, SourceParser};
43use salvo::extract::Extractible;
44use salvo::http::StatusCode;
45use salvo::prelude::*;
46use vld::schema::VldParse;
47use vld_http_common::coerce_value;
48
49#[derive(Debug)]
61pub struct VldSalvoError {
62 pub error: vld::error::VldError,
64}
65
66impl VldSalvoError {
67 pub fn new(error: vld::error::VldError) -> Self {
69 Self { error }
70 }
71}
72
73impl std::fmt::Display for VldSalvoError {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 write!(f, "Validation failed: {}", self.error)
76 }
77}
78
79impl std::error::Error for VldSalvoError {}
80
81impl From<vld::error::VldError> for VldSalvoError {
82 fn from(error: vld::error::VldError) -> Self {
83 Self { error }
84 }
85}
86
87#[async_trait]
88impl Writer for VldSalvoError {
89 async fn write(mut self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
90 let body = vld_http_common::format_vld_error(&self.error);
91 res.status_code(StatusCode::UNPROCESSABLE_ENTITY);
92 res.render(Json(body));
93 }
94}
95
96fn parse_error(msg: impl std::fmt::Display) -> VldSalvoError {
101 VldSalvoError {
102 error: vld::error::VldError::single(vld::error::IssueCode::ParseError, msg.to_string()),
103 }
104}
105
106pub struct VldJson<T>(pub T);
120
121impl<T> std::ops::Deref for VldJson<T> {
122 type Target = T;
123 fn deref(&self) -> &T {
124 &self.0
125 }
126}
127
128impl<T> std::ops::DerefMut for VldJson<T> {
129 fn deref_mut(&mut self) -> &mut T {
130 &mut self.0
131 }
132}
133
134impl<'ex, T: VldParse + Send> Extractible<'ex> for VldJson<T> {
135 fn metadata() -> &'static Metadata {
136 static META: OnceLock<Metadata> = OnceLock::new();
137 META.get_or_init(|| {
138 Metadata::new("VldJson")
139 .add_default_source(Source::new(SourceFrom::Body, SourceParser::Json))
140 })
141 }
142
143 async fn extract(
144 req: &'ex mut Request,
145 _depot: &'ex mut Depot,
146 ) -> Result<Self, impl Writer + Send + std::fmt::Debug + 'static> {
147 let value: serde_json::Value = req
148 .parse_json()
149 .await
150 .map_err(|e| parse_error(format_args!("Invalid JSON: {e}")))?;
151 T::vld_parse_value(&value)
152 .map(VldJson)
153 .map_err(VldSalvoError::from)
154 }
155}
156
157pub struct VldQuery<T>(pub T);
171
172impl<T> std::ops::Deref for VldQuery<T> {
173 type Target = T;
174 fn deref(&self) -> &T {
175 &self.0
176 }
177}
178
179impl<T> std::ops::DerefMut for VldQuery<T> {
180 fn deref_mut(&mut self) -> &mut T {
181 &mut self.0
182 }
183}
184
185impl<'ex, T: VldParse + Send> Extractible<'ex> for VldQuery<T> {
186 fn metadata() -> &'static Metadata {
187 static META: OnceLock<Metadata> = OnceLock::new();
188 META.get_or_init(|| {
189 Metadata::new("VldQuery")
190 .add_default_source(Source::new(SourceFrom::Query, SourceParser::MultiMap))
191 })
192 }
193
194 async fn extract(
195 req: &'ex mut Request,
196 _depot: &'ex mut Depot,
197 ) -> Result<Self, impl Writer + Send + std::fmt::Debug + 'static> {
198 let qs = req.uri().query().unwrap_or("");
199 let value = vld_http_common::query_string_to_json(qs);
200 T::vld_parse_value(&value)
201 .map(VldQuery)
202 .map_err(VldSalvoError::from)
203 }
204}
205
206pub struct VldForm<T>(pub T);
217
218impl<T> std::ops::Deref for VldForm<T> {
219 type Target = T;
220 fn deref(&self) -> &T {
221 &self.0
222 }
223}
224
225impl<T> std::ops::DerefMut for VldForm<T> {
226 fn deref_mut(&mut self) -> &mut T {
227 &mut self.0
228 }
229}
230
231impl<'ex, T: VldParse + Send> Extractible<'ex> for VldForm<T> {
232 fn metadata() -> &'static Metadata {
233 static META: OnceLock<Metadata> = OnceLock::new();
234 META.get_or_init(|| {
235 Metadata::new("VldForm")
236 .add_default_source(Source::new(SourceFrom::Body, SourceParser::MultiMap))
237 })
238 }
239
240 async fn extract(
241 req: &'ex mut Request,
242 _depot: &'ex mut Depot,
243 ) -> Result<Self, impl Writer + Send + std::fmt::Debug + 'static> {
244 let body_str = req
245 .parse_body::<String>()
246 .await
247 .map_err(|e| parse_error(format_args!("Invalid form body: {e}")))?;
248 let map = vld_http_common::parse_query_string(&body_str);
249 let value = serde_json::Value::Object(map);
250 T::vld_parse_value(&value)
251 .map(VldForm)
252 .map_err(VldSalvoError::from)
253 }
254}
255
256pub struct VldPath<T>(pub T);
277
278impl<T> std::ops::Deref for VldPath<T> {
279 type Target = T;
280 fn deref(&self) -> &T {
281 &self.0
282 }
283}
284
285impl<T> std::ops::DerefMut for VldPath<T> {
286 fn deref_mut(&mut self) -> &mut T {
287 &mut self.0
288 }
289}
290
291impl<'ex, T: VldParse + Send> Extractible<'ex> for VldPath<T> {
292 fn metadata() -> &'static Metadata {
293 static META: OnceLock<Metadata> = OnceLock::new();
294 META.get_or_init(|| {
295 Metadata::new("VldPath")
296 .add_default_source(Source::new(SourceFrom::Param, SourceParser::MultiMap))
297 })
298 }
299
300 async fn extract(
301 req: &'ex mut Request,
302 _depot: &'ex mut Depot,
303 ) -> Result<Self, impl Writer + Send + std::fmt::Debug + 'static> {
304 let mut map = serde_json::Map::new();
305 for (key, value) in req.params().iter() {
306 map.insert(key.clone(), coerce_value(value));
307 }
308 let value = serde_json::Value::Object(map);
309 T::vld_parse_value(&value)
310 .map(VldPath)
311 .map_err(VldSalvoError::from)
312 }
313}
314
315pub struct VldHeaders<T>(pub T);
329
330impl<T> std::ops::Deref for VldHeaders<T> {
331 type Target = T;
332 fn deref(&self) -> &T {
333 &self.0
334 }
335}
336
337impl<T> std::ops::DerefMut for VldHeaders<T> {
338 fn deref_mut(&mut self) -> &mut T {
339 &mut self.0
340 }
341}
342
343impl<'ex, T: VldParse + Send> Extractible<'ex> for VldHeaders<T> {
344 fn metadata() -> &'static Metadata {
345 static META: OnceLock<Metadata> = OnceLock::new();
346 META.get_or_init(|| {
347 Metadata::new("VldHeaders")
348 .add_default_source(Source::new(SourceFrom::Header, SourceParser::MultiMap))
349 })
350 }
351
352 async fn extract(
353 req: &'ex mut Request,
354 _depot: &'ex mut Depot,
355 ) -> Result<Self, impl Writer + Send + std::fmt::Debug + 'static> {
356 let mut map = serde_json::Map::new();
357 for (name, value) in req.headers().iter() {
358 let key = name.as_str().to_lowercase().replace('-', "_");
359 if let Ok(v) = value.to_str() {
360 map.insert(key, coerce_value(v));
361 }
362 }
363 let value = serde_json::Value::Object(map);
364 T::vld_parse_value(&value)
365 .map(VldHeaders)
366 .map_err(VldSalvoError::from)
367 }
368}
369
370pub struct VldCookie<T>(pub T);
381
382impl<T> std::ops::Deref for VldCookie<T> {
383 type Target = T;
384 fn deref(&self) -> &T {
385 &self.0
386 }
387}
388
389impl<T> std::ops::DerefMut for VldCookie<T> {
390 fn deref_mut(&mut self) -> &mut T {
391 &mut self.0
392 }
393}
394
395impl<'ex, T: VldParse + Send> Extractible<'ex> for VldCookie<T> {
396 fn metadata() -> &'static Metadata {
397 static META: OnceLock<Metadata> = OnceLock::new();
398 META.get_or_init(|| {
399 Metadata::new("VldCookie")
400 .add_default_source(Source::new(SourceFrom::Cookie, SourceParser::MultiMap))
401 })
402 }
403
404 async fn extract(
405 req: &'ex mut Request,
406 _depot: &'ex mut Depot,
407 ) -> Result<Self, impl Writer + Send + std::fmt::Debug + 'static> {
408 let cookie_header = req
409 .headers()
410 .get("cookie")
411 .and_then(|v| v.to_str().ok())
412 .unwrap_or("");
413 let value = vld_http_common::cookies_to_json(cookie_header);
414 T::vld_parse_value(&value)
415 .map(VldCookie)
416 .map_err(VldSalvoError::from)
417 }
418}
419
420pub mod prelude {
426 pub use crate::{VldCookie, VldForm, VldHeaders, VldJson, VldPath, VldQuery, VldSalvoError};
427 pub use vld::prelude::*;
428}