Expand description

Tiny crate to deserialize form style data from query parameters in http GET request. Built upon Serde.

§Able to Deserialize

  1. Form style simple array (/users?id=3,4,5), whose elements’ type T impls FromStr trait.
  2. Any type (for example, form object: /users?id=role,admin,firstName,Alex) that impls FromStr 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.

Structs§

Functions§