serde_html_form 0.4.0

(De-)serialization support for the `application/x-www-form-urlencoded` format
Documentation
use alloc::{
    collections::{BTreeMap, BTreeSet},
    string::{String, ToString as _},
    vec::Vec,
};

use insta::{assert_compact_debug_snapshot, assert_snapshot};
use serde::Deserialize;

#[derive(Deserialize, Debug, PartialEq)]
struct NewType<T>(T);

#[test]
fn deserialize_newtype_i32() {
    assert_compact_debug_snapshot!(
        super::from_str::<Vec<(String, NewType<i32>)>>("field=11"),
        @r#"Ok([("field", NewType(11))])"#
    );
}

#[test]
fn deserialize_option_newtype_i32() {
    // This is a limitation of the current empty-as-none hack that exists for
    // optional numbers (and booleans): It doesn't work for newtypes.
    //
    // This should be fine as this is a somewhat niche use case. serde
    // attributes can be used to work arounds it.
    assert_compact_debug_snapshot!(
        super::from_str::<Vec<(String, Option<NewType<i32>>)>>("field="),
        @r#"Err(Error("cannot parse integer from empty string"))"#
    );
}

#[test]
fn deserialize_option_newtype_i32_with_helper() {
    #[allow(dead_code)]
    #[derive(Debug, Deserialize)]
    struct Form {
        #[serde(deserialize_with = "crate::de::empty_as_none")]
        field: Option<NewType<i32>>,
    }

    assert_compact_debug_snapshot!(
        super::from_str::<Form>("field="),
        @"Ok(Form { field: None })"
    );
}

#[test]
fn deserialize_bytes() {
    assert_compact_debug_snapshot!(
        super::from_bytes::<Vec<(String, i32)>>(b"first=23&last=42"),
        @r#"Ok([("first", 23), ("last", 42)])"#
    );
}

#[test]
fn deserialize_str() {
    assert_compact_debug_snapshot!(
        super::from_str::<Vec<(String, i32)>>("first=23&last=42"),
        @r#"Ok([("first", 23), ("last", 42)])"#
    );
}

#[test]
fn deserialize_borrowed_str() {
    assert_compact_debug_snapshot!(
        super::from_str::<Vec<(&str, i32)>>("first=23&last=42"),
        @r#"Ok([("first", 23), ("last", 42)])"#
    );
}

#[test]
fn deserialize_option() {
    assert_compact_debug_snapshot!(
        super::from_str::<Vec<(String, Option<i32>)>>("first=23&last=42"),
        @r#"Ok([("first", Some(23)), ("last", Some(42))])"#
    );
}

#[test]
fn deserialize_empty_string() {
    assert_compact_debug_snapshot!(
        super::from_str::<Vec<(String, &str)>>("first="),
        @r#"Ok([("first", "")])"#
    );
}

#[test]
fn deserialize_optional_string_empty() {
    assert_compact_debug_snapshot!(
        super::from_str::<Vec<(&str, Option<&str>)>>("foo=&bar="),
        @r#"Ok([("foo", Some("")), ("bar", Some(""))])"#
    );
}

#[test]
fn deserialize_map() {
    assert_compact_debug_snapshot!(
        super::from_str::<BTreeMap<String, i32>>("first=23&second=42"),
        @r#"Ok({"first": 23, "second": 42})"#
    );
}

#[test]
fn deserialize_map_vec() {
    assert_compact_debug_snapshot!(
        super::from_str::<BTreeMap<String, Vec<i32>>>("first=23&second=42&first=1"),
        @r#"Ok({"first": [23, 1], "second": [42]})"#
    );
}

#[test]
fn deserialize_tuple_list() {
    assert_compact_debug_snapshot!(
        super::from_str::<Vec<(String, i32)>>("foo=1&bar=2&foo=3"),
        @r#"Ok([("foo", 1), ("bar", 2), ("foo", 3)])"#
    );
}

#[test]
fn deserialize_vec_strings() {
    #[derive(Deserialize, PartialEq, Debug)]
    struct Form {
        value: Vec<String>,
    }

    assert_compact_debug_snapshot!(
        super::from_str::<Form>("value=&value=abc"),
        @r#"Ok(Form { value: ["", "abc"] })"#
    );
}

#[test]
fn deserialize_option_vec() {
    #[derive(Deserialize, PartialEq, Debug)]
    struct Form {
        value: Option<Vec<String>>,
    }

    assert_compact_debug_snapshot!(super::from_str::<Form>(""), @"Ok(Form { value: None })");
    assert_compact_debug_snapshot!(
        super::from_str::<Form>("value=abc"),
        @r#"Ok(Form { value: Some(["abc"]) })"#
    );
    assert_compact_debug_snapshot!(
        super::from_str::<Form>("value=abc&value=def"),
        @r#"Ok(Form { value: Some(["abc", "def"]) })"#
    );
}

#[test]
fn deserialize_option_vec_int() {
    #[derive(Deserialize, PartialEq, Debug)]
    struct Form {
        value: Option<Vec<i32>>,
    }

    assert_compact_debug_snapshot!(super::from_str::<Form>(""), @"Ok(Form { value: None })");
    assert_compact_debug_snapshot!(
        super::from_str::<Form>("value=0"),
        @"Ok(Form { value: Some([0]) })"
    );
    assert_compact_debug_snapshot!(
        super::from_str::<Form>("value=3&value=-1"),
        @"Ok(Form { value: Some([3, -1]) })"
    );
}

#[test]
fn deserialize_option_no_value() {
    #[derive(Deserialize, PartialEq, Debug)]
    struct Form {
        value: Option<f64>,
    }

    assert_compact_debug_snapshot!(
        super::from_str::<Form>("value="),
        @"Ok(Form { value: None })"
    );
}

#[test]
fn deserialize_vec_options_no_value() {
    #[derive(Deserialize, PartialEq, Debug)]
    struct Form {
        value: Vec<Option<f64>>,
    }

    assert_compact_debug_snapshot!(
        super::from_str::<Form>("value=&value=&value="),
        @"Ok(Form { value: [None, None, None] })"
    );
}

#[test]
fn deserialize_vec_options_some_values() {
    #[derive(Deserialize, PartialEq, Debug)]
    struct Form {
        value: Vec<Option<f64>>,
    }

    assert_compact_debug_snapshot!(
        super::from_str::<Form>("value=&value=4&value="),
        @"Ok(Form { value: [None, Some(4.0), None] })"
    );
}

#[test]
fn deserialize_option_vec_no_value() {
    #[derive(Deserialize, PartialEq, Debug)]
    struct Form {
        value: Option<Vec<f64>>,
    }

    assert_snapshot!(
        super::from_str::<Form>("value=&value=&value=").unwrap_err().to_string(),
        @"cannot parse float from empty string"
    );
}

#[test]
fn deserialize_option_vec_with_values() {
    #[derive(Deserialize, PartialEq, Debug)]
    struct Form {
        value: Option<Vec<f64>>,
    }

    assert_compact_debug_snapshot!(
        super::from_str::<Form>("value=3&value=4&value=5"),
        @"Ok(Form { value: Some([3.0, 4.0, 5.0]) })"
    );
}

#[test]
fn deserialize_no_value_err() {
    #[derive(Deserialize, PartialEq, Debug)]
    struct Form {
        value: f64,
    }

    assert_snapshot!(
        super::from_str::<Form>("value=").unwrap_err().to_string(),
        @"cannot parse float from empty string"
    );
}

#[test]
fn deserialize_unit() {
    assert_compact_debug_snapshot!(super::from_str::<()>(""), @"Ok(())");
    assert_compact_debug_snapshot!(super::from_str::<()>("&"), @"Ok(())");
    assert_compact_debug_snapshot!(super::from_str::<()>("&&"), @"Ok(())");
    assert_snapshot!(super::from_str::<()>("first=23").unwrap_err(), @"invalid length 1, expected 0 elements in map");
}

#[derive(Deserialize, Debug, PartialEq, Eq)]
enum X {
    A,
    B,
    C,
}

#[test]
fn deserialize_unit_enum() {
    assert_compact_debug_snapshot!(
        super::from_str::<Vec<(String, X)>>("one=A&two=B&three=C"),
        @r#"Ok([("one", A), ("two", B), ("three", C)])"#
    );
}

#[test]
fn deserialize_unit_type() {
    assert_compact_debug_snapshot!(super::from_str::<()>(""), @"Ok(())");
}

#[derive(Debug, Deserialize, PartialEq)]
#[serde(tag = "action", rename_all = "lowercase")]
enum Foo {
    A(A),
}

#[derive(Debug, Deserialize, PartialEq)]
struct A {
    foo: String,
}

#[test]
fn deserialize_internally_tagged_enum() {
    assert_compact_debug_snapshot!(
        super::from_str::<Foo>("action=a&foo=hello"),
        @r#"Ok(A(A { foo: "hello" }))"#
    );
}

#[test]
fn deserialize_optional_list_empty_value() {
    #[allow(dead_code)]
    #[derive(Debug, Deserialize)]
    struct Form {
        foo: Option<Vec<String>>,
    }

    assert_compact_debug_snapshot!(
        super::from_str::<Form>("foo="),
        @r#"Ok(Form { foo: Some([""]) })"#
    );
}

#[test]
fn deserialize_empty_vec_as_none() {
    #[allow(dead_code)]
    #[derive(Debug, Deserialize)]
    struct Form {
        #[serde(deserialize_with = "crate::de::empty_as_none::seq")]
        vec: Vec<Option<String>>,
    }

    assert_compact_debug_snapshot!(
        super::from_str::<Form>("vec=x&vec="),
        @r#"Ok(Form { vec: [Some("x"), None] })"#
    );
}

#[test]
fn deserialize_empty_btreeset_as_none() {
    #[allow(dead_code)]
    #[derive(Debug, Deserialize)]
    struct Form {
        #[serde(deserialize_with = "crate::de::empty_as_none::seq")]
        set: BTreeSet<Option<String>>,
    }

    assert_compact_debug_snapshot!(
        super::from_str::<Form>("set=x&set="),
        @r#"Ok(Form { set: {None, Some("x")} })"#
    );
}