Expand description
Tiny crate to deserialize form style data from query parameters in http GET request. Built upon Serde.
§Able to Deserialize
- Form style simple array (
/users?id=3,4,5
), whose elements’ type T implsFromStr
trait. - Any type (for example, form object:
/users?id=role,admin,firstName,Alex
) that implsFromStr
trait in a certain way (a little complex).
§Sample Code
§Deserialize Vec
use deserialize_form_style_query_parameter::{form_vec_deserialize, option_form_vec_deserialize};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct QueryParams {
id: Option<u32>,
#[serde(deserialize_with = "form_vec_deserialize")]
user_ids: Vec<u8>,
#[serde(deserialize_with = "option_form_vec_deserialize", default)]
user_names: Option<Vec<String>>,
}
let correct_answer = QueryParams{
id: Some(12345),
user_ids: vec![1, 3],
user_names: None,
};
// serde_urlencoded::from_str is executed by axum::extract::Query.
// https://docs.rs/axum/latest/src/axum/extract/query.rs.html#87
// So handler(Query(para)) also works.
let example_params: QueryParams =
serde_urlencoded::from_str("id=12345&user_ids=1,2b,3")
.unwrap();
assert_eq!(example_params, correct_answer);
§Deserialize Object
use deserialize_form_style_query_parameter::pure_from_str;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct Address {
city: String,
postcode: String,
}
impl FromStr for Address {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
// This function might be very complex in actual situations.
let parts: Vec<&str> = s.split(',').collect();
if parts.len() != 4{
return Err(());
}
if !parts[0].eq("city") || !parts[2].eq("postcode") {
return Err(());
}
Ok(Address{
city: parts[1].to_string(),
postcode: parts[3].to_string()
})
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct QueryParams {
id: Option<u32>,
#[serde(deserialize_with = "pure_from_str")]
address: Address,
}
let correct_answer = QueryParams{
id: Some(12345),
address: Address {
city: "Teyvat".to_string(),
postcode: "191919".to_string()
}
};
let example_params: QueryParams =
serde_urlencoded::from_str("id=12345&address=city,Teyvat,postcode,191919")
.unwrap();
assert_eq!(example_params, correct_answer);
§Provided Functions
§fn form_vec_deserialize
A deserialize function for Vec<T: FromStr>
.
Add #[serde(deserialize_with = "form_vec_deserialize")]
above field to enable.
This function use struct FormVecVisitor
to deserialize,
which only accept string, and split it into a Vec<String>
by ‘,’.
Then execute T::from_str(s).ok()
for every items (illegal items will be discarded).
§fn option_form_vec_deserialize
A deserialize function for Option<Vec<T: FromStr>>
.
Add #[serde(deserialize_with = "form_vec_deserialize", default)]
above field to enable.
The default
means this field would be None
if its value is not present.
More details about serde’s field attributes: serde.rs field-attrs.
§fn pure_from_str
A deserialize function for T: FromStr
.
Add #[serde(deserialize_with = "pure_from_str")]
above field to enable.
This function use struct PureFromStrVisitor
to deserialize,
which only accept string, and just call T::from_str
.
An error would be thrown when T::from_str
failed.
§fn option_pure_from_str
A deserialize function for Option<T: FromStr>
.
Add #[serde(deserialize_with = "option_pure_from_str", default)]
above field to enable.
The default
means this field would be None
if its value is not present.
§Adapt Custom Wrappers
Take option_form_vec_deserialize
source code as an example:
use std::str::FromStr;
use serde::Deserializer;
use deserialize_form_style_query_parameter::form_vec_deserialize;
pub fn option_form_vec_deserialize<'de, D, T>(deserializer: D) -> Result<Option<Vec<T>>, D::Error>
where D: Deserializer<'de>,
T: FromStr {
form_vec_deserialize(deserializer).map(Some)
}
Copy and modify it to meet your needs.
§When and Why Use This Crate
§When and Why use form style query
When an API must require form style (swagger spec here), we don’t have any other choice.
§A serious bug
Http GET request in form style are something like http://www.just.example/path?address=99&name=55,66
,
which represents data structure like:
{
"address" = 99,
"name" = [55, 66]
}
For backend, we have to create a handler to handle this GET request, for example:
use axum::extract::Query;
async fn example_api(
token: Token,
Query(my_query_args): Query<MyQueryArgs>
) -> ApiResponse {
println!("get args: {:?}", my_query_args);
// do something
ApiResponse::new()
}
But you will receive a confusing error message as response:
invalid type: string "55,66", expected a sequence
§Why bug
In axum::extract::Query source code,
we found that the from_request_parts
use serde_urlencoded::from_str
to deserialize query parameters.
This crate is not powerful enough to deserialize complex query parameters.
After replacing serde_urlencoded
with serde_qs
, which claiming to be skilled in handling query parameters,
nothing changed. Crate serde_qs
only support array likearr[]=55&arr[]=66
and arr[0]=55&arr[1]=66
.
This might be an unresolved bug.
Very similarly, both of them can’t recognize form style object.
§Why this crate works
Luckily, both serde_urlencoded::from_str
and serde_qs::from_str
recognize form style arrays and objects as Strings, and then execute their deserialize functions.
So this crate provide some deserialize functions, and user could use deserialize_with
to enable them.