1use std::borrow::Cow;
2use std::collections::HashMap;
3
4use indexmap::IndexMap;
5use multimap::MultiMap;
6use serde::de::value::Error as ValError;
7use serde::de::{self, Deserialize, Error as DeError, IntoDeserializer};
8use serde::forward_to_deserialize_any;
9use serde_json::value::RawValue;
10
11use crate::Request;
12use crate::extract::Metadata;
13use crate::extract::metadata::{Field, Source, SourceFrom, SourceParser};
14use crate::http::ParseError;
15use crate::http::form::FormData;
16use crate::http::header::HeaderMap;
17
18use super::{CowValue, FlatValue, VecValue};
19
20pub async fn from_request<'de, T>(
21 req: &'de mut Request,
22 metadata: &'de Metadata,
23) -> Result<T, ParseError>
24where
25 T: Deserialize<'de>,
26{
27 if let Some(ctype) = req.content_type() {
29 match ctype.subtype() {
30 mime::WWW_FORM_URLENCODED | mime::FORM_DATA => {
31 if metadata.has_body_required() {
32 let _ = req.form_data().await;
33 }
34 }
35 mime::JSON => {
36 if metadata.has_body_required() {
37 let _ = req.payload().await;
38 }
39 }
40 _ => {}
41 }
42 }
43 Ok(T::deserialize(RequestDeserializer::new(req, metadata)?)?)
44}
45
46#[derive(Clone, Debug)]
47pub(crate) enum Payload<'a> {
48 FormData(&'a FormData),
49 JsonStr(&'a str),
50 JsonMap(HashMap<&'a str, &'a RawValue>),
51}
52impl Payload<'_> {
53 #[allow(dead_code)]
54 pub(crate) fn is_form_data(&self) -> bool {
55 matches!(*self, Self::FormData(_))
56 }
57 pub(crate) fn is_json_str(&self) -> bool {
58 matches!(*self, Self::JsonStr(_))
59 }
60 pub(crate) fn is_json_map(&self) -> bool {
61 matches!(*self, Self::JsonMap(_))
62 }
63}
64
65#[derive(Debug)]
66pub(crate) struct RequestDeserializer<'de> {
67 params: &'de IndexMap<String, String>,
68 queries: &'de MultiMap<String, String>,
69 #[cfg(feature = "cookie")]
70 cookies: &'de cookie::CookieJar,
71 headers: &'de HeaderMap,
72 payload: Option<Payload<'de>>,
73 metadata: &'de Metadata,
74 field_index: isize,
75 field_flatten: bool,
76 field_source: Option<Source>,
77 field_str_value: Option<&'de str>,
78 field_vec_value: Option<Vec<CowValue<'de>>>,
79}
80
81impl<'de> RequestDeserializer<'de> {
82 pub(crate) fn new(request: &'de Request, metadata: &'de Metadata) -> Result<Self, ParseError> {
84 let mut payload = None;
85
86 if metadata.has_body_required() {
87 if let Some(ctype) = request.content_type() {
88 match ctype.subtype() {
89 mime::WWW_FORM_URLENCODED | mime::FORM_DATA => {
90 payload = request.form_data.get().map(Payload::FormData);
91 }
92 mime::JSON => {
93 if let Some(data) = request.payload.get() {
94 if !data.is_empty() {
95 payload = match serde_json::from_slice::<HashMap<&str, &RawValue>>(
97 data,
98 ) {
99 Ok(map) => Some(Payload::JsonMap(map)),
100 Err(e) => {
101 tracing::warn!(error = ?e, "`RequestDeserializer` serde parse json payload failed");
102 Some(Payload::JsonStr(std::str::from_utf8(data)?))
103 }
104 };
105 }
106 }
107 }
108 _ => {}
109 }
110 }
111 }
112 Ok(RequestDeserializer {
113 params: request.params(),
114 queries: request.queries(),
115 headers: request.headers(),
116 #[cfg(feature = "cookie")]
117 cookies: request.cookies(),
118 payload,
119 metadata,
120 field_index: -1,
121 field_flatten: false,
122 field_source: None,
123 field_str_value: None,
124 field_vec_value: None,
125 })
126 }
127
128 fn real_parser(&self, source: Source) -> SourceParser {
129 let mut parser = source.parser;
130 if parser == SourceParser::Smart {
131 if source.from == SourceFrom::Body {
132 if let Some(payload) = &self.payload {
133 if payload.is_json_map() || payload.is_json_str() {
134 parser = SourceParser::Json;
135 } else {
136 parser = SourceParser::MultiMap;
137 }
138 } else {
139 parser = SourceParser::MultiMap;
140 }
141 } else if source.from == SourceFrom::Query || source.from == SourceFrom::Header {
142 parser = SourceParser::Flat;
143 } else {
144 parser = SourceParser::MultiMap;
145 }
146 }
147 parser
148 }
149
150 fn deserialize_value<T>(&mut self, seed: T) -> Result<T::Value, ValError>
151 where
152 T: de::DeserializeSeed<'de>,
153 {
154 if self.field_flatten {
155 let field = self
156 .metadata
157 .fields
158 .get(self.field_index as usize)
159 .expect("field must exist.");
160 let metadata = field.metadata.expect("field's metadata must exist");
161 seed.deserialize(RequestDeserializer {
162 params: self.params,
163 queries: self.queries,
164 headers: self.headers,
165 #[cfg(feature = "cookie")]
166 cookies: self.cookies,
167 payload: self.payload.clone(),
168 metadata,
169 field_index: -1,
170 field_flatten: false,
171 field_source: None,
172 field_str_value: None,
173 field_vec_value: None,
174 })
175 } else {
176 let source = self
177 .field_source
178 .take()
179 .expect("`MapAccess::next_value` called before next_key");
180
181 let parser = self.real_parser(source);
182 if source.from == SourceFrom::Body && parser == SourceParser::Json {
183 let value = self
185 .field_str_value
186 .expect("MapAccess::next_value called before next_key");
187 let mut value = serde_json::Deserializer::new(serde_json::de::StrRead::new(value));
188
189 seed.deserialize(&mut value)
190 .map_err(|_| ValError::custom("parse value error"))
191 } else if let Some(value) = self.field_str_value.take() {
192 seed.deserialize(CowValue(value.into()))
193 } else if let Some(value) = self.field_vec_value.take() {
194 if source.from == SourceFrom::Query || source.from == SourceFrom::Header {
195 seed.deserialize(FlatValue(value))
196 } else {
197 seed.deserialize(VecValue(value.into_iter()))
198 }
199 } else {
200 Err(ValError::custom("parse value error"))
201 }
202 }
203 }
204
205 #[allow(unreachable_patterns)]
206 fn fill_value(&mut self, field: &'de Field) -> bool {
207 if field.flatten {
208 self.field_flatten = true;
209 return true;
210 }
211 let sources = if !field.sources.is_empty() {
212 &field.sources
213 } else if !self.metadata.default_sources.is_empty() {
214 &self.metadata.default_sources
215 } else {
216 tracing::error!("no sources for field {}", field.decl_name);
217 return false;
218 };
219
220 let field_name = if let Some(rename) = field.rename {
221 rename
222 } else if let Some(serde_rename) = field.serde_rename {
223 serde_rename
224 } else if let Some(rename_all) = self.metadata.rename_all {
225 &*rename_all.apply_to_field(field.decl_name)
226 } else if let Some(serde_rename_all) = self.metadata.serde_rename_all {
227 &*serde_rename_all.apply_to_field(field.decl_name)
228 } else {
229 field.decl_name
230 };
231
232 for source in sources.iter().cloned() {
233 match source.from {
234 SourceFrom::Param => {
235 let mut value = self.params.get(field_name);
236 if value.is_none() {
237 for alias in &field.aliases {
238 value = self.params.get(*alias);
239 if value.is_some() {
240 break;
241 }
242 }
243 }
244 if let Some(value) = value {
245 self.field_str_value = Some(value);
246 self.field_source = Some(source);
247 return true;
248 }
249 }
250 SourceFrom::Query => {
251 let mut value = self.queries.get_vec(field_name);
252 if value.is_none() {
253 for alias in &field.aliases {
254 value = self.queries.get_vec(*alias);
255 if value.is_some() {
256 break;
257 }
258 }
259 }
260 if let Some(value) = value {
261 self.field_vec_value =
262 Some(value.iter().map(|v| CowValue(v.into())).collect());
263 self.field_source = Some(source);
264 return true;
265 }
266 }
267 SourceFrom::Header => {
268 let mut value = None;
269 if self.headers.contains_key(field_name) {
270 value = Some(self.headers.get_all(field_name))
271 } else {
272 for alias in &field.aliases {
273 if self.headers.contains_key(*alias) {
274 value = Some(self.headers.get_all(*alias));
275 break;
276 }
277 }
278 };
279 if let Some(value) = value {
280 self.field_vec_value = Some(
281 value
282 .iter()
283 .map(|v| CowValue(Cow::from(v.to_str().unwrap_or_default())))
284 .collect(),
285 );
286 self.field_source = Some(source);
287 return true;
288 }
289 }
290 #[cfg(feature = "cookie")]
291 SourceFrom::Cookie => {
292 let mut value = None;
293 if let Some(cookie) = self.cookies.get(field_name.as_ref()) {
294 value = Some(cookie.value());
295 } else {
296 for alias in &field.aliases {
297 if let Some(cookie) = self.cookies.get(alias) {
298 value = Some(cookie.value());
299 break;
300 }
301 }
302 };
303 if let Some(value) = value {
304 self.field_str_value = Some(value);
305 self.field_source = Some(source);
306 return true;
307 }
308 }
309 SourceFrom::Body => {
310 let parser = self.real_parser(source);
311 match parser {
312 SourceParser::Json => {
313 if let Some(payload) = &self.payload {
314 match payload {
315 Payload::FormData(form_data) => {
316 let mut value = form_data.fields.get(field_name);
317 if value.is_none() {
318 for alias in &field.aliases {
319 value = form_data.fields.get(*alias);
320 if value.is_some() {
321 break;
322 }
323 }
324 }
325 if let Some(value) = value {
326 self.field_str_value = Some(value);
327 self.field_source = Some(source);
328 return true;
329 }
330 return false;
331 }
332 Payload::JsonMap(map) => {
333 let mut value = map.get(field_name);
334 if value.is_none() {
335 for alias in &field.aliases {
336 value = map.get(alias);
337 if value.is_some() {
338 break;
339 }
340 }
341 }
342 if let Some(value) = value {
343 self.field_str_value = Some(value.get());
344 self.field_source = Some(source);
345 return true;
346 }
347 return false;
348 }
349 Payload::JsonStr(value) => {
350 self.field_str_value = Some(*value);
351 self.field_source = Some(source);
352 return true;
353 }
354 }
355 } else {
356 return false;
357 }
358 }
359 SourceParser::MultiMap => {
360 if let Some(Payload::FormData(form_data)) = self.payload {
361 let mut value = form_data.fields.get_vec(field_name);
362 if value.is_none() {
363 for alias in &field.aliases {
364 value = form_data.fields.get_vec(*alias);
365 if value.is_some() {
366 break;
367 }
368 }
369 }
370 if let Some(value) = value {
371 self.field_vec_value = Some(
372 value.iter().map(|v| CowValue(Cow::from(v))).collect(),
373 );
374 self.field_source = Some(source);
375 return true;
376 }
377 }
378 return false;
379 }
380 _ => {
381 panic!("unsupported source parser: {parser:?}");
382 }
383 }
384 }
385 }
386 }
387 false
388 }
389 fn next(&mut self) -> Option<Cow<'_, str>> {
390 while self.field_index < self.metadata.fields.len() as isize - 1 {
391 self.field_index += 1;
392 let field = &self.metadata.fields[self.field_index as usize];
393 self.field_flatten = field.flatten;
394 self.field_str_value = None;
395 self.field_vec_value = None;
396
397 if self.fill_value(field) {
398 return field.serde_rename.map(Cow::from).or_else(|| {
399 if let Some(serde_rename_all) = self.metadata.serde_rename_all {
400 Some(Cow::Owned(serde_rename_all.apply_to_field(field.decl_name)))
401 } else {
402 Some(Cow::from(field.decl_name))
403 }
404 });
405 }
406 }
407 None
408 }
409}
410
411impl<'de> de::Deserializer<'de> for RequestDeserializer<'de> {
412 type Error = ValError;
413
414 #[inline]
415 fn deserialize_any<V>(mut self, visitor: V) -> Result<V::Value, Self::Error>
416 where
417 V: de::Visitor<'de>,
418 {
419 visitor.visit_map(&mut self)
420 }
421
422 #[inline]
423 fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error>
424 where
425 V: de::Visitor<'de>,
426 {
427 self.deserialize_seq(visitor)
428 }
429
430 forward_to_deserialize_any! {
431 bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
432 bytes byte_buf option unit unit_struct newtype_struct tuple_struct map seq
433 struct enum identifier ignored_any
434 }
435}
436
437impl<'de> de::MapAccess<'de> for RequestDeserializer<'de> {
438 type Error = ValError;
439
440 #[inline]
441 fn next_key_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
442 where
443 T: de::DeserializeSeed<'de>,
444 {
445 match self.next() {
446 Some(key) => seed.deserialize(key.into_deserializer()).map(Some),
447 None => Ok(None),
448 }
449 }
450
451 #[inline]
452 fn next_value_seed<T>(&mut self, seed: T) -> Result<T::Value, Self::Error>
453 where
454 T: de::DeserializeSeed<'de>,
455 {
456 self.deserialize_value(seed)
457 }
458
459 #[inline]
460 fn next_entry_seed<TK, TV>(
461 &mut self,
462 kseed: TK,
463 vseed: TV,
464 ) -> Result<Option<(TK::Value, TV::Value)>, Self::Error>
465 where
466 TK: de::DeserializeSeed<'de>,
467 TV: de::DeserializeSeed<'de>,
468 {
469 match self.next() {
470 Some(key) => {
471 let key = kseed.deserialize(key.into_deserializer())?;
472 let value = self.deserialize_value(vseed)?;
473 Ok(Some((key, value)))
474 }
475 None => Ok(None),
476 }
477 }
478}
479
480#[cfg(test)]
481mod tests {
482 use serde::{Deserialize, Serialize};
483
484 use crate::macros::Extractible;
485 use crate::test::TestClient;
486
487 #[tokio::test]
488 async fn test_de_request_from_query() {
489 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
490 #[salvo(extract(default_source(from = "query")))]
491 struct RequestData {
492 q1: String,
493 q2: i64,
494 }
495 let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
496 .query("q1", "q1v")
497 .query("q2", "23")
498 .build();
499 let data: RequestData = req.extract().await.unwrap();
500 assert_eq!(
501 data,
502 RequestData {
503 q1: "q1v".to_owned(),
504 q2: 23
505 }
506 );
507 }
508
509 #[tokio::test]
510 async fn test_de_request_with_lifetime() {
511 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
512 #[salvo(extract(default_source(from = "query")))]
513 struct RequestData<'a> {
514 #[salvo(extract(source(from = "param"), source(from = "query")))]
515 #[salvo(extract(source(from = "body")))]
516 q1: &'a str,
517 }
521
522 let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
523 .query("q1", "q1v")
524 .query("q2", "23")
525 .build();
526 let data: RequestData<'_> = req.extract().await.unwrap();
527 assert_eq!(data, RequestData { q1: "q1v" });
528 }
529
530 #[tokio::test]
531 async fn test_de_request_with_rename() {
532 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
533 #[salvo(extract(default_source(from = "query")))]
534 struct RequestData<'a> {
535 #[salvo(extract(source(from = "param"), source(from = "query"), rename = "abc"))]
536 q1: &'a str,
537 }
538
539 let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
540 .query("abc", "q1v")
541 .build();
542 let data: RequestData<'_> = req.extract().await.unwrap();
543 assert_eq!(data, RequestData { q1: "q1v" });
544 }
545
546 #[tokio::test]
547 async fn test_de_request_with_rename_all() {
548 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
549 #[salvo(extract(default_source(from = "query"), rename_all = "PascalCase"))]
550 struct RequestData<'a> {
551 first_name: &'a str,
552 #[salvo(extract(rename = "lastName"))]
553 last_name: &'a str,
554 }
555
556 let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
557 .query("FirstName", "chris")
558 .query("lastName", "young")
559 .build();
560 let data: RequestData<'_> = req.extract().await.unwrap();
561 assert_eq!(
562 data,
563 RequestData {
564 first_name: "chris",
565 last_name: "young"
566 }
567 );
568 }
569
570 #[tokio::test]
571 async fn test_de_request_with_multi_sources() {
572 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
573 #[salvo(extract(default_source(from = "query")))]
574 struct RequestData<'a> {
575 #[salvo(extract(source(from = "param")))]
576 #[salvo(extract(alias = "param1"))]
577 p1: String,
578 #[salvo(extract(source(from = "param"), alias = "param2"))]
579 p2: &'a str,
580 #[salvo(extract(source(from = "param"), alias = "param3"))]
581 p3: usize,
582 q1: String,
584 q2: i64,
586 }
589
590 let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
591 .query("q1", "q1v")
592 .query("q2", "23")
593 .build();
594 req.params.insert("param1", "param1v".into());
595 req.params.insert("p2", "921".into());
596 req.params.insert("p3", "89785".into());
597 let data: RequestData = req.extract().await.unwrap();
598 assert_eq!(
599 data,
600 RequestData {
601 p1: "param1v".into(),
602 p2: "921",
603 p3: 89785,
604 q1: "q1v".into(),
605 q2: 23
606 }
607 );
608 }
609
610 #[tokio::test]
611 async fn test_de_request_with_json_vec() {
612 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
613 #[salvo(extract(default_source(from = "body", parse = "json")))]
614 struct RequestData<'a> {
615 #[salvo(extract(source(from = "param")))]
616 p2: &'a str,
617 users: Vec<User>,
618 }
619 #[derive(Deserialize, Serialize, Eq, PartialEq, Debug)]
620 struct User {
621 id: i64,
622 name: String,
623 }
624
625 let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
626 .json(&vec![
627 User {
628 id: 1,
629 name: "chris".into(),
630 },
631 User {
632 id: 2,
633 name: "young".into(),
634 },
635 ])
636 .build();
637 req.params.insert("p2", "921".into());
638 let data: RequestData = req.extract().await.unwrap();
639 assert_eq!(
640 data,
641 RequestData {
642 p2: "921",
643 users: vec![
644 User {
645 id: 1,
646 name: "chris".into()
647 },
648 User {
649 id: 2,
650 name: "young".into()
651 }
652 ]
653 }
654 );
655 }
656
657 #[tokio::test]
658 async fn test_de_request_with_json_bool() {
659 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
660 #[salvo(extract(default_source(from = "body", parse = "json")))]
661 struct RequestData<'a> {
662 #[salvo(extract(source(from = "param")))]
663 p2: &'a str,
664 b: bool,
665 }
666
667 let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
668 .json(&true)
669 .build();
670 req.params.insert("p2", "921".into());
671 let data: RequestData = req.extract().await.unwrap();
672 assert_eq!(data, RequestData { p2: "921", b: true });
673 }
674
675 #[tokio::test]
676 async fn test_de_request_with_json_str() {
677 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
678 #[salvo(extract(default_source(from = "body", parse = "json")))]
679 struct RequestData<'a> {
680 #[salvo(extract(source(from = "param")))]
681 p2: &'a str,
682 s: &'a str,
683 }
684
685 let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
686 .json(&"abcd-good")
687 .build();
688 req.params.insert("p2", "921".into());
689 let data: RequestData = req.extract().await.unwrap();
690 assert_eq!(
691 data,
692 RequestData {
693 p2: "921",
694 s: "abcd-good"
695 }
696 );
697 }
698
699 #[tokio::test]
700 async fn test_de_request_with_form_json_str() {
701 #[derive(Deserialize, Eq, PartialEq, Debug)]
702 struct User<'a> {
703 name: &'a str,
704 age: usize,
705 }
706 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
707 #[salvo(extract(default_source(from = "body", parse = "json")))]
708 struct RequestData<'a> {
709 #[salvo(extract(source(from = "param")))]
710 p2: &'a str,
711 user: User<'a>,
712 }
713 let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
714 .raw_form(r#"user={"name": "chris", "age": 20}"#)
715 .build();
716 req.params.insert("p2", "921".into());
717 let data: RequestData = req.extract().await.unwrap();
718 assert_eq!(
719 data,
720 RequestData {
721 p2: "921",
722 user: User {
723 name: "chris",
724 age: 20
725 }
726 }
727 );
728 }
729
730 #[tokio::test]
731 async fn test_de_request_with_extract_rename_all() {
732 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
733 #[salvo(extract(rename_all = "kebab-case", default_source(from = "query")))]
734 struct RequestData {
735 full_name: String,
736 #[salvo(extract(rename = "currAge"))]
737 curr_age: usize,
738 }
739 let mut req = TestClient::get(
740 "http://127.0.0.1:8698/test/1234/param2v?full-name=chris+young&currAge=20",
741 )
742 .build();
743 let data: RequestData = req.extract().await.unwrap();
744 assert_eq!(
745 data,
746 RequestData {
747 full_name: "chris young".into(),
748 curr_age: 20
749 }
750 );
751 }
752
753 #[tokio::test]
754 async fn test_de_request_with_serde_rename_all() {
755 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
756 #[salvo(extract(default_source(from = "query")))]
757 #[serde(rename_all = "kebab-case")]
758 struct RequestData {
759 full_name: String,
760 #[salvo(extract(rename = "currAge"))]
761 curr_age: usize,
762 }
763 let mut req = TestClient::get(
764 "http://127.0.0.1:8698/test/1234/param2v?full-name=chris+young&currAge=20",
765 )
766 .build();
767 let data: RequestData = req.extract().await.unwrap();
768 assert_eq!(
769 data,
770 RequestData {
771 full_name: "chris young".into(),
772 curr_age: 20
773 }
774 );
775 }
776
777 #[tokio::test]
778 async fn test_de_request_with_both_rename_all() {
779 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
780 #[salvo(extract(rename_all = "kebab-case", default_source(from = "query")))]
781 #[serde(rename_all = "camelCase")]
782 struct RequestData {
783 full_name: String,
784 #[salvo(extract(rename = "currAge"))]
785 curr_age: usize,
786 }
787 let mut req = TestClient::get(
788 "http://127.0.0.1:8698/test/1234/param2v?full-name=chris+young&currAge=20",
789 )
790 .build();
791 let data: RequestData = req.extract().await.unwrap();
792 assert_eq!(
793 data,
794 RequestData {
795 full_name: "chris young".into(),
796 curr_age: 20
797 }
798 );
799 }
800
801 #[tokio::test]
802 async fn test_de_request_url_array() {
803 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
804 #[salvo(extract(default_source(from = "query")))]
805 struct RequestData {
806 ids: Vec<String>,
807 }
808 let mut req =
809 TestClient::get("http://127.0.0.1:8698/test/1234/param2v?ids=[3,2,11]").build();
810 let data: RequestData = req.extract().await.unwrap();
811 assert_eq!(
812 data,
813 RequestData {
814 ids: vec!["3".to_owned(), "2".to_owned(), "11".to_owned()]
815 }
816 );
817 let mut req = TestClient::get(
818 r#"http://127.0.0.1:8698/test/1234/param2v?ids=['3', '2',"11","1,2"]"#,
819 )
820 .build();
821 let data: RequestData = req.extract().await.unwrap();
822 assert_eq!(
823 data,
824 RequestData {
825 ids: vec![
826 "3".to_owned(),
827 "2".to_owned(),
828 "11".to_owned(),
829 "1,2".to_owned()
830 ]
831 }
832 );
833 }
834
835 #[tokio::test]
836 async fn test_de_request_url_array2() {
837 #[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
838 #[salvo(extract(default_source(from = "query")))]
839 struct RequestData {
840 ids: Vec<i64>,
841 }
842 let mut req =
843 TestClient::get("http://127.0.0.1:8698/test/1234/param2v?ids=[3,2,11]").build();
844 let data: RequestData = req.extract().await.unwrap();
845 assert_eq!(
846 data,
847 RequestData {
848 ids: vec![3, 2, 11]
849 }
850 );
851 }
852}