use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::Arc;
use indexmap::IndexMap;
use multimap::MultiMap;
use serde::de::value::Error as ValError;
use serde::de::{self, Deserialize, Error as DeError, IntoDeserializer};
use serde::forward_to_deserialize_any;
use serde_json::value::RawValue;
use crate::extract::metadata::{Field, Metadata, Source, SourceFrom, SourceParser};
use crate::http::ParseError;
use crate::http::form::FormData;
use crate::http::header::HeaderMap;
use crate::serde::{CowValue, FlatValue, VecValue};
use crate::{Depot, Request};
type DepotExtractFn = for<'a> fn(&'a Depot, &str) -> Option<Cow<'a, str>>;
static DEPOT_EXTRACTORS: &[DepotExtractFn] = &[
|d, k| d.get::<String>(k).ok().map(|v| Cow::Borrowed(v.as_str())),
|d, k| d.get::<&'static str>(k).ok().map(|v| Cow::Borrowed(*v)),
|d, k| {
d.get::<Arc<String>>(k)
.ok()
.map(|v| Cow::Borrowed(v.as_str()))
},
|d, k| d.get::<Arc<str>>(k).ok().map(|v| Cow::Borrowed(&**v)),
|d, k| d.get::<i8>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<i16>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<i32>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<i64>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<i128>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<isize>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<u8>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<u16>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<u32>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<u64>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<u128>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<usize>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<f32>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<f64>(k).ok().map(|v| Cow::Owned(v.to_string())),
|d, k| d.get::<bool>(k).ok().map(|v| Cow::Owned(v.to_string())),
];
fn get_depot_value<'a>(depot: &'a Depot, key: &str) -> Option<Cow<'a, str>> {
for extractor in DEPOT_EXTRACTORS {
if let Some(value) = extractor(depot, key) {
return Some(value);
}
}
None
}
pub async fn from_request<'de, T>(
req: &'de mut Request,
depot: &'de mut Depot,
metadata: &'de Metadata,
) -> Result<T, ParseError>
where
T: Deserialize<'de>,
{
if let Some(ctype) = req.content_type() {
match ctype.subtype() {
mime::WWW_FORM_URLENCODED | mime::FORM_DATA => {
if metadata.has_body_required() {
let _ = req.form_data().await;
}
}
mime::JSON => {
if metadata.has_body_required() {
let _ = req.payload().await;
}
}
_ => {}
}
}
Ok(T::deserialize(RequestDeserializer::new(
req, depot, metadata,
)?)?)
}
#[derive(Clone, Debug)]
pub(crate) enum Payload<'a> {
FormData(&'a FormData),
JsonStr(&'a str),
JsonMap(HashMap<&'a str, &'a RawValue>),
}
impl Payload<'_> {
#[allow(dead_code)]
pub(crate) fn is_form_data(&self) -> bool {
matches!(*self, Self::FormData(_))
}
pub(crate) fn is_json_str(&self) -> bool {
matches!(*self, Self::JsonStr(_))
}
pub(crate) fn is_json_map(&self) -> bool {
matches!(*self, Self::JsonMap(_))
}
}
#[derive(Debug)]
pub(crate) struct RequestDeserializer<'de> {
params: &'de IndexMap<String, String>,
queries: &'de MultiMap<String, String>,
#[cfg(feature = "cookie")]
cookies: &'de cookie::CookieJar,
headers: &'de HeaderMap,
payload: Option<Payload<'de>>,
metadata: &'de Metadata,
depot: &'de Depot,
field_index: isize,
field_flatten: bool,
field_source: Option<Source>,
field_str_value: Option<Cow<'de, str>>,
field_vec_value: Option<Vec<CowValue<'de>>>,
}
impl<'de> RequestDeserializer<'de> {
pub(crate) fn new(
request: &'de Request,
depot: &'de Depot,
metadata: &'de Metadata,
) -> Result<Self, ParseError> {
let mut payload = None;
if metadata.has_body_required()
&& let Some(ctype) = request.content_type()
{
match ctype.subtype() {
mime::WWW_FORM_URLENCODED | mime::FORM_DATA => {
payload = request.form_data.get().map(Payload::FormData);
}
mime::JSON => {
if let Some(data) = request.payload.get()
&& !data.is_empty()
{
payload = match serde_json::from_slice::<HashMap<&str, &RawValue>>(data) {
Ok(map) => Some(Payload::JsonMap(map)),
Err(e) => {
tracing::warn!(error = ?e, "`RequestDeserializer` serde parse json payload failed");
Some(Payload::JsonStr(std::str::from_utf8(data)?))
}
};
}
}
_ => {}
}
}
Ok(RequestDeserializer {
params: request.params(),
queries: request.queries(),
headers: request.headers(),
#[cfg(feature = "cookie")]
cookies: request.cookies(),
payload,
metadata,
depot,
field_index: -1,
field_flatten: false,
field_source: None,
field_str_value: None,
field_vec_value: None,
})
}
fn real_parser(&self, source: Source) -> SourceParser {
let mut parser = source.parser;
if parser == SourceParser::Smart {
if source.from == SourceFrom::Body {
if let Some(payload) = &self.payload {
if payload.is_json_map() || payload.is_json_str() {
parser = SourceParser::Json;
} else {
parser = SourceParser::MultiMap;
}
} else {
parser = SourceParser::MultiMap;
}
} else if source.from == SourceFrom::Query || source.from == SourceFrom::Header {
parser = SourceParser::Flat;
} else {
parser = SourceParser::MultiMap;
}
}
parser
}
fn deserialize_value<T>(&mut self, seed: T) -> Result<T::Value, ValError>
where
T: de::DeserializeSeed<'de>,
{
if self.field_flatten {
let field = self
.metadata
.fields
.get(self.field_index as usize)
.expect("field must exist.");
let metadata = field.metadata.expect("field's metadata must exist");
seed.deserialize(RequestDeserializer {
params: self.params,
queries: self.queries,
headers: self.headers,
#[cfg(feature = "cookie")]
cookies: self.cookies,
payload: self.payload.clone(),
metadata,
depot: self.depot,
field_index: -1,
field_flatten: false,
field_source: None,
field_str_value: None,
field_vec_value: None,
})
} else {
let source = self
.field_source
.take()
.expect("`MapAccess::next_value` called before next_key");
let parser = self.real_parser(source);
if source.from == SourceFrom::Body && parser == SourceParser::Json {
let value = self
.field_str_value
.take()
.expect("MapAccess::next_value called before next_key");
let Cow::Borrowed(s) = value else {
return Err(ValError::custom("JSON body value must be borrowed"));
};
let mut de = serde_json::Deserializer::new(serde_json::de::StrRead::new(s));
seed.deserialize(&mut de)
.map_err(|_| ValError::custom("parse value error"))
} else if let Some(value) = self.field_str_value.take() {
seed.deserialize(CowValue(value))
} else if let Some(value) = self.field_vec_value.take() {
if source.from == SourceFrom::Query || source.from == SourceFrom::Header {
seed.deserialize(FlatValue(value))
} else {
seed.deserialize(VecValue(value.into_iter()))
}
} else {
Err(ValError::custom("parse value error"))
}
}
}
#[allow(unreachable_patterns)]
fn fill_value(&mut self, field: &'de Field) -> bool {
if field.flatten {
self.field_flatten = true;
return true;
}
let sources = if !field.sources.is_empty() {
&field.sources
} else if !self.metadata.default_sources.is_empty() {
&self.metadata.default_sources
} else {
tracing::error!("no sources for field {}", field.decl_name);
return false;
};
let field_name = if let Some(rename) = field.rename {
rename
} else if let Some(serde_rename) = field.serde_rename {
serde_rename
} else if let Some(rename_all) = self.metadata.rename_all {
&*rename_all.apply_to_field(field.decl_name)
} else if let Some(serde_rename_all) = self.metadata.serde_rename_all {
&*serde_rename_all.apply_to_field(field.decl_name)
} else {
field.decl_name
};
for source in sources.iter().cloned() {
match source.from {
SourceFrom::Param => {
let mut value = self.params.get(field_name);
if value.is_none() {
for alias in &field.aliases {
value = self.params.get(*alias);
if value.is_some() {
break;
}
}
}
if let Some(value) = value {
self.field_str_value = Some(Cow::Borrowed(value));
self.field_source = Some(source);
return true;
}
}
SourceFrom::Query => {
let mut value = self.queries.get_vec(field_name);
if value.is_none() {
for alias in &field.aliases {
value = self.queries.get_vec(*alias);
if value.is_some() {
break;
}
}
}
if let Some(value) = value {
self.field_vec_value =
Some(value.iter().map(|v| CowValue(v.into())).collect());
self.field_source = Some(source);
return true;
}
}
SourceFrom::Header => {
let mut value = None;
if self.headers.contains_key(field_name) {
value = Some(self.headers.get_all(field_name))
} else {
for alias in &field.aliases {
if self.headers.contains_key(*alias) {
value = Some(self.headers.get_all(*alias));
break;
}
}
};
if let Some(value) = value {
self.field_vec_value = Some(
value
.iter()
.map(|v| CowValue(Cow::from(v.to_str().unwrap_or_default())))
.collect(),
);
self.field_source = Some(source);
return true;
}
}
#[cfg(feature = "cookie")]
SourceFrom::Cookie => {
let mut value = None;
if let Some(cookie) = self.cookies.get(field_name.as_ref()) {
value = Some(cookie.value());
} else {
for alias in &field.aliases {
if let Some(cookie) = self.cookies.get(alias) {
value = Some(cookie.value());
break;
}
}
};
if let Some(value) = value {
self.field_str_value = Some(Cow::Borrowed(value));
self.field_source = Some(source);
return true;
}
}
SourceFrom::Depot => {
let mut value = get_depot_value(self.depot, field_name);
if value.is_none() {
for alias in &field.aliases {
value = get_depot_value(self.depot, alias);
if value.is_some() {
break;
}
}
}
if let Some(value) = value {
self.field_str_value = Some(value);
self.field_source = Some(source);
return true;
}
}
SourceFrom::Body => {
let parser = self.real_parser(source);
match parser {
SourceParser::Json => {
if let Some(payload) = &self.payload {
match payload {
Payload::FormData(form_data) => {
let mut value = form_data.fields.get(field_name);
if value.is_none() {
for alias in &field.aliases {
value = form_data.fields.get(*alias);
if value.is_some() {
break;
}
}
}
if let Some(value) = value {
self.field_str_value = Some(Cow::Borrowed(value));
self.field_source = Some(source);
return true;
}
return false;
}
Payload::JsonMap(map) => {
let mut value = map.get(field_name);
if value.is_none() {
for alias in &field.aliases {
value = map.get(alias);
if value.is_some() {
break;
}
}
}
if let Some(value) = value {
self.field_str_value = Some(Cow::Borrowed(value.get()));
self.field_source = Some(source);
return true;
}
return false;
}
Payload::JsonStr(value) => {
self.field_str_value = Some(Cow::Borrowed(*value));
self.field_source = Some(source);
return true;
}
}
} else {
return false;
}
}
SourceParser::MultiMap => {
if let Some(Payload::FormData(form_data)) = self.payload {
let mut value = form_data.fields.get_vec(field_name);
if value.is_none() {
for alias in &field.aliases {
value = form_data.fields.get_vec(*alias);
if value.is_some() {
break;
}
}
}
if let Some(value) = value {
self.field_vec_value = Some(
value.iter().map(|v| CowValue(Cow::from(v))).collect(),
);
self.field_source = Some(source);
return true;
}
}
return false;
}
_ => {
panic!("unsupported source parser: {parser:?}");
}
}
}
}
}
false
}
fn next(&mut self) -> Option<Cow<'_, str>> {
while self.field_index < self.metadata.fields.len() as isize - 1 {
self.field_index += 1;
let field = &self.metadata.fields[self.field_index as usize];
self.field_flatten = field.flatten;
self.field_str_value = None;
self.field_vec_value = None;
if self.fill_value(field) {
return field.serde_rename.map(Cow::from).or_else(|| {
if let Some(serde_rename_all) = self.metadata.serde_rename_all {
Some(Cow::Owned(serde_rename_all.apply_to_field(field.decl_name)))
} else {
Some(Cow::from(field.decl_name))
}
});
}
}
None
}
}
impl<'de> de::Deserializer<'de> for RequestDeserializer<'de> {
type Error = ValError;
#[inline]
fn deserialize_any<V>(mut self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_map(&mut self)
}
#[inline]
fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
self.deserialize_seq(visitor)
}
forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
bytes byte_buf option unit unit_struct newtype_struct tuple_struct map seq
struct enum identifier ignored_any
}
}
impl<'de> de::MapAccess<'de> for RequestDeserializer<'de> {
type Error = ValError;
#[inline]
fn next_key_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
where
T: de::DeserializeSeed<'de>,
{
match self.next() {
Some(key) => seed.deserialize(key.into_deserializer()).map(Some),
None => Ok(None),
}
}
#[inline]
fn next_value_seed<T>(&mut self, seed: T) -> Result<T::Value, Self::Error>
where
T: de::DeserializeSeed<'de>,
{
self.deserialize_value(seed)
}
#[inline]
fn next_entry_seed<TK, TV>(
&mut self,
kseed: TK,
vseed: TV,
) -> Result<Option<(TK::Value, TV::Value)>, Self::Error>
where
TK: de::DeserializeSeed<'de>,
TV: de::DeserializeSeed<'de>,
{
match self.next() {
Some(key) => {
let key = kseed.deserialize(key.into_deserializer())?;
let value = self.deserialize_value(vseed)?;
Ok(Some((key, value)))
}
None => Ok(None),
}
}
}
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use crate::Depot;
use crate::macros::Extractible;
use crate::test::TestClient;
#[tokio::test]
async fn test_de_request_from_query() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "query")))]
struct RequestData {
q1: String,
q2: i64,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
.query("q1", "q1v")
.query("q2", "23")
.build();
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
q1: "q1v".to_owned(),
q2: 23
}
);
}
#[tokio::test]
async fn test_de_request_from_query_untagged_multi() {
#[derive(Clone, Copy, Deserialize, Eq, PartialEq, Debug)]
enum DBState {
#[serde(alias = "open")]
Open,
#[serde(alias = "closed_cooperative")]
ClosedCooperative,
}
#[derive(Deserialize, Eq, PartialEq, Debug)]
#[serde(untagged)]
enum State {
Single(DBState),
Multiple(Vec<DBState>),
}
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "query")))]
struct ByStateParams {
state: State,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test")
.query("state", "open")
.query("state", "closed_cooperative")
.build();
let mut depot = Depot::new();
let data: ByStateParams = req.extract(&mut depot).await.unwrap();
assert_eq!(
data.state,
State::Multiple(vec![DBState::Open, DBState::ClosedCooperative])
);
}
#[tokio::test]
async fn test_de_request_with_lifetime() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "query")))]
struct RequestData<'a> {
#[salvo(extract(source(from = "param"), source(from = "query")))]
#[salvo(extract(source(from = "body")))]
q1: &'a str,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
.query("q1", "q1v")
.query("q2", "23")
.build();
let mut depot = Depot::new();
let data: RequestData<'_> = req.extract(&mut depot).await.unwrap();
assert_eq!(data, RequestData { q1: "q1v" });
}
#[tokio::test]
async fn test_de_request_with_rename() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "query")))]
struct RequestData<'a> {
#[salvo(extract(source(from = "param"), source(from = "query"), rename = "abc"))]
q1: &'a str,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
.query("abc", "q1v")
.build();
let mut depot = Depot::new();
let data: RequestData<'_> = req.extract(&mut depot).await.unwrap();
assert_eq!(data, RequestData { q1: "q1v" });
}
#[tokio::test]
async fn test_de_request_with_serde_alias() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "query")))]
struct RequestData {
#[serde(alias = "alias1", alias = "alias2")]
q1: String,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
.query("alias1", "q1v")
.build();
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
q1: "q1v".to_owned()
}
);
let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
.query("alias2", "q2v")
.build();
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
q1: "q2v".to_owned()
}
);
}
#[tokio::test]
async fn test_de_request_with_rename_all() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "query"), rename_all = "PascalCase"))]
struct RequestData<'a> {
first_name: &'a str,
#[salvo(extract(rename = "lastName"))]
last_name: &'a str,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
.query("FirstName", "chris")
.query("lastName", "young")
.build();
let mut depot = Depot::new();
let data: RequestData<'_> = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
first_name: "chris",
last_name: "young"
}
);
}
#[tokio::test]
async fn test_de_request_with_multi_sources() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "query")))]
struct RequestData<'a> {
#[salvo(extract(source(from = "param")))]
#[salvo(extract(alias = "param1"))]
p1: String,
#[salvo(extract(source(from = "param"), alias = "param2"))]
p2: &'a str,
#[salvo(extract(source(from = "param"), alias = "param3"))]
p3: usize,
q1: String,
q2: i64,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
.query("q1", "q1v")
.query("q2", "23")
.build();
req.params.insert("param1", "param1v".into());
req.params.insert("p2", "921".into());
req.params.insert("p3", "89785".into());
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
p1: "param1v".into(),
p2: "921",
p3: 89785,
q1: "q1v".into(),
q2: 23
}
);
}
#[tokio::test]
async fn test_de_request_with_json_vec() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "body", parse = "json")))]
struct RequestData<'a> {
#[salvo(extract(source(from = "param")))]
p2: &'a str,
users: Vec<User>,
}
#[derive(Deserialize, Serialize, Eq, PartialEq, Debug)]
struct User {
id: i64,
name: String,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
.json(&vec![
User {
id: 1,
name: "chris".into(),
},
User {
id: 2,
name: "young".into(),
},
])
.build();
req.params.insert("p2", "921".into());
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
p2: "921",
users: vec![
User {
id: 1,
name: "chris".into()
},
User {
id: 2,
name: "young".into()
}
]
}
);
}
#[tokio::test]
async fn test_de_request_with_json_bool() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "body", parse = "json")))]
struct RequestData<'a> {
#[salvo(extract(source(from = "param")))]
p2: &'a str,
b: bool,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
.json(&true)
.build();
req.params.insert("p2", "921".into());
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(data, RequestData { p2: "921", b: true });
}
#[tokio::test]
async fn test_de_request_with_json_str() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "body", parse = "json")))]
struct RequestData<'a> {
#[salvo(extract(source(from = "param")))]
p2: &'a str,
s: &'a str,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
.json(&"abcd-good")
.build();
req.params.insert("p2", "921".into());
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
p2: "921",
s: "abcd-good"
}
);
}
#[tokio::test]
async fn test_de_request_with_form_json_str() {
#[derive(Deserialize, Eq, PartialEq, Debug)]
struct User<'a> {
name: &'a str,
age: usize,
}
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "body", parse = "json")))]
struct RequestData<'a> {
#[salvo(extract(source(from = "param")))]
p2: &'a str,
user: User<'a>,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test/1234/param2v")
.raw_form(r#"user={"name": "chris", "age": 20}"#)
.build();
req.params.insert("p2", "921".into());
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
p2: "921",
user: User {
name: "chris",
age: 20
}
}
);
}
#[tokio::test]
async fn test_de_request_with_extract_rename_all() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(rename_all = "kebab-case", default_source(from = "query")))]
struct RequestData {
full_name: String,
#[salvo(extract(rename = "currAge"))]
curr_age: usize,
}
let mut req = TestClient::get(
"http://127.0.0.1:8698/test/1234/param2v?full-name=chris+young&currAge=20",
)
.build();
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
full_name: "chris young".into(),
curr_age: 20
}
);
}
#[tokio::test]
async fn test_de_request_with_serde_rename_all() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "query")))]
#[serde(rename_all = "kebab-case")]
struct RequestData {
full_name: String,
#[salvo(extract(rename = "currAge"))]
curr_age: usize,
}
let mut req = TestClient::get(
"http://127.0.0.1:8698/test/1234/param2v?full-name=chris+young&currAge=20",
)
.build();
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
full_name: "chris young".into(),
curr_age: 20
}
);
}
#[tokio::test]
async fn test_de_request_with_both_rename_all() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(rename_all = "kebab-case", default_source(from = "query")))]
#[serde(rename_all = "camelCase")]
struct RequestData {
full_name: String,
#[salvo(extract(rename = "currAge"))]
curr_age: usize,
}
let mut req = TestClient::get(
"http://127.0.0.1:8698/test/1234/param2v?full-name=chris+young&currAge=20",
)
.build();
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
full_name: "chris young".into(),
curr_age: 20
}
);
}
#[tokio::test]
async fn test_de_request_url_array() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "query")))]
struct RequestData {
ids: Vec<String>,
}
let mut req =
TestClient::get("http://127.0.0.1:8698/test/1234/param2v?ids=[3,2,11]").build();
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
ids: vec!["3".to_owned(), "2".to_owned(), "11".to_owned()]
}
);
let mut req = TestClient::get(
r#"http://127.0.0.1:8698/test/1234/param2v?ids=['3', '2',"11","1,2"]"#,
)
.build();
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
ids: vec![
"3".to_owned(),
"2".to_owned(),
"11".to_owned(),
"1,2".to_owned()
]
}
);
}
#[tokio::test]
async fn test_de_request_url_array2() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "query")))]
struct RequestData {
ids: Vec<i64>,
}
let mut req =
TestClient::get("http://127.0.0.1:8698/test/1234/param2v?ids=[3,2,11]").build();
let mut depot = Depot::new();
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
ids: vec![3, 2, 11]
}
);
}
#[tokio::test]
async fn test_de_request_from_depot() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "depot")))]
struct RequestData {
user_id: String,
username: String,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test").build();
let mut depot = Depot::new();
depot.insert("user_id", "12345".to_owned());
depot.insert("username", "alice".to_owned());
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
user_id: "12345".to_owned(),
username: "alice".to_owned()
}
);
}
#[tokio::test]
async fn test_de_request_from_depot_with_alias() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "depot")))]
struct RequestData {
#[salvo(extract(alias = "uid"))]
user_id: String,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test").build();
let mut depot = Depot::new();
depot.insert("uid", "12345".to_owned());
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
user_id: "12345".to_owned()
}
);
}
#[tokio::test]
async fn test_de_request_from_depot_and_query() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
struct RequestData {
#[salvo(extract(source(from = "depot")))]
user_id: String,
#[salvo(extract(source(from = "query")))]
page: i64,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test?page=5").build();
let mut depot = Depot::new();
depot.insert("user_id", "12345".to_owned());
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
user_id: "12345".to_owned(),
page: 5
}
);
}
#[tokio::test]
#[allow(clippy::approx_constant)]
async fn test_de_request_from_depot_with_primitives() {
#[derive(Deserialize, Extractible, PartialEq, Debug)]
#[salvo(extract(default_source(from = "depot")))]
struct RequestData {
int_val: i64,
uint_val: u32,
float_val: f64,
bool_val: bool,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test").build();
let mut depot = Depot::new();
depot.insert("int_val", -42i64);
depot.insert("uint_val", 100u32);
depot.insert("float_val", 3.14f64);
depot.insert("bool_val", true);
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
int_val: -42,
uint_val: 100,
float_val: 3.14,
bool_val: true
}
);
}
#[tokio::test]
async fn test_de_request_from_depot_with_static_str() {
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "depot")))]
struct RequestData {
message: String,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test").build();
let mut depot = Depot::new();
depot.insert("message", "hello world");
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
message: "hello world".to_owned()
}
);
}
#[tokio::test]
async fn test_de_request_from_depot_with_arc_string() {
use std::sync::Arc;
#[derive(Deserialize, Extractible, Eq, PartialEq, Debug)]
#[salvo(extract(default_source(from = "depot")))]
struct RequestData {
user_id: String,
username: String,
}
let mut req = TestClient::get("http://127.0.0.1:8698/test").build();
let mut depot = Depot::new();
depot.insert("user_id", Arc::new("12345".to_owned()));
depot.insert("username", Arc::<str>::from("alice"));
let data: RequestData = req.extract(&mut depot).await.unwrap();
assert_eq!(
data,
RequestData {
user_id: "12345".to_owned(),
username: "alice".to_owned()
}
);
}
}