nextjson 0.1.3

A dependency-free, no_std JSON and CBOR library with a schema-driven, visitor-free design.
Documentation
//! Load testing of derived macros for complex generics and trait bounds.
//!
//! Coverage: multi-type parameters, type parameters with default values, constant generics, `?Sized`,
//! `PhantomData`, nested generic containers, where clauses, custom `bound`, generic enumerations,
//! tuple structs, lifecycle borrowing, etc. The goal is to verify that the impl generated by derive
//! has the same expressive power as serde under the combination of "generics + constraints".

use nextjson::{from_str, to_string, NsonDeserialize, NsonSerialize};

// ---------------------------------------------------------------------------
// 1. Multiple parameter types + default values ​​+ dependency default values
// ---------------------------------------------------------------------------

#[derive(NsonSerialize, NsonDeserialize, Debug, PartialEq)]
struct Multi<T, U = T> {
    t: T,
    u: U,
}

#[test]
fn multi_type_params_with_default() {
    let v = Multi { t: 1_i32, u: 2_i32 };
    assert_eq!(to_string(&v).unwrap(), r#"{"t":1,"u":2}"#);
    let back: Multi<i32> = from_str(r#"{"t":1,"u":2}"#).unwrap();
    assert_eq!(back, v);

    let v2 = Multi {
        t: 1_i32,
        u: "x".to_string(),
    };
    assert_eq!(to_string(&v2).unwrap(), r#"{"t":1,"u":"x"}"#);
    let back2: Multi<i32, String> = from_str(r#"{"t":1,"u":"x"}"#).unwrap();
    assert_eq!(back2, v2);
}

// ---------------------------------------------------------------------------
// 2. Constant generic trait + array field
// ---------------------------------------------------------------------------

#[derive(NsonSerialize, NsonDeserialize, Debug, PartialEq)]
struct Blob<const N: usize> {
    data: [u8; N],
    tag: String,
}

#[test]
fn const_generic_array_field() {
    let v = Blob::<3> {
        data: [1, 2, 3],
        tag: "abc".into(),
    };
    assert_eq!(to_string(&v).unwrap(), r#"{"data":[1,2,3],"tag":"abc"}"#);
    let back: Blob<3> = from_str(r#"{"tag":"abc","data":[1,2,3]}"#).unwrap();
    assert_eq!(back, v);
}

// ---------------------------------------------------------------------------
// 3. PhantomData
// ---------------------------------------------------------------------------

#[derive(NsonSerialize, NsonDeserialize)]
struct Marker<T> {
    _m: core::marker::PhantomData<T>,
    n: i32,
}

#[derive(NsonSerialize, NsonDeserialize)]
struct MarkerPath<T> {
    _m: std::marker::PhantomData<T>,
    n: i32,
}

#[test]
fn phantom_data_field() {
    let v = Marker::<String> {
        _m: core::marker::PhantomData,
        n: 7,
    };
    assert_eq!(to_string(&v).unwrap(), r#"{"n":7}"#);
    let back: Marker<String> = from_str(r#"{"n":7}"#).unwrap();
    assert_eq!(back.n, 7);

    let vp = MarkerPath::<String> {
        _m: std::marker::PhantomData,
        n: 8,
    };
    assert_eq!(to_string(&vp).unwrap(), r#"{"n":8}"#);
    let backp: MarkerPath<String> = from_str(r#"{"n":8}"#).unwrap();
    assert_eq!(backp.n, 8);
}

// ---------------------------------------------------------------------------
// 4. Nested generic containers (Vec<T>, Option<Box<T>>, BTreeMap<String, Vec<T>>)
// ---------------------------------------------------------------------------

#[derive(NsonSerialize, NsonDeserialize, Debug, PartialEq)]
struct Nested<T> {
    map: std::collections::BTreeMap<String, Vec<T>>,
    opt: Option<Box<T>>,
}

#[test]
fn nested_generic_containers() {
    let v = Nested {
        map: std::collections::BTreeMap::from([("k".to_string(), vec![1_i32, 2])]),
        opt: Some(Box::new(3)),
    };
    assert_eq!(to_string(&v).unwrap(), r#"{"map":{"k":[1,2]},"opt":3}"#);
    let back: Nested<i32> = from_str(r#"{"opt":3,"map":{"k":[1,2]}}"#).unwrap();
    assert_eq!(back, v);
}

// ---------------------------------------------------------------------------
// 5. Generic WHERE clause
// ---------------------------------------------------------------------------

// `T` only appears in `PhantomData`, and serialization/deserialization do not require it to implement any trait;
// Use an empty `bound` to turn off the automatically generated per-type-param constraint (same syntax as serde).

#[derive(NsonSerialize, NsonDeserialize)]
#[njson(bound(serialize = "", deserialize = ""))]
struct IterWrap<T>
where
    T: Iterator<Item = i32> + Clone,
{
    items: Vec<i32>,
    _p: core::marker::PhantomData<T>,
}

#[test]
fn complex_where_predicate() {
    let v = IterWrap::<std::vec::IntoIter<i32>> {
        items: vec![1, 2],
        _p: core::marker::PhantomData,
    };
    assert_eq!(to_string(&v).unwrap(), r#"{"items":[1,2]}"#);
    let back: IterWrap<std::vec::IntoIter<i32>> = from_str(r#"{"items":[1,2]}"#).unwrap();
    assert_eq!(back.items, vec![1, 2]);
}

// ---------------------------------------------------------------------------
// 6. Custom bound (must retain the type's own WHERE clause)
// ---------------------------------------------------------------------------

/// The bound required for deserialization is different from that required for serialization: serialization only requires T: NsonSerialize,
/// Deserialization goes through `deserialize_with` and uses the Value, therefore it requires T: NsonDeserialize<'de>.

#[derive(NsonSerialize, NsonDeserialize, Debug, PartialEq)]
#[njson(bound(
    serialize = "T: nextjson::NsonSerialize",
    deserialize = "T: nextjson::NsonDeserialize<'de>"
))]
struct Bounded<T>
where
    T: core::fmt::Debug,
{
    value: T,
}

#[test]
fn custom_bound_keeps_where_clause() {
    let v = Bounded { value: 5_i32 };
    assert_eq!(to_string(&v).unwrap(), r#"{"value":5}"#);
    let back: Bounded<i32> = from_str(r#"{"value":5}"#).unwrap();
    assert_eq!(back, v);
}

// ---------------------------------------------------------------------------
// 7. Generic enumerations (newtype + struct variants)
// ---------------------------------------------------------------------------

#[derive(NsonSerialize, NsonDeserialize, Debug, PartialEq)]
enum GenericEnum<T> {
    Unit,
    One(T),
    Many(Vec<T>),
    Named { a: T, b: Option<T> },
}

#[test]
fn generic_enum_all_shapes() {
    let cases: Vec<(GenericEnum<i32>, &str)> = vec![
        (GenericEnum::Unit, r#"{"Unit":null}"#),
        (GenericEnum::One(1), r#"{"One":1}"#),
        (GenericEnum::Many(vec![1, 2]), r#"{"Many":[1,2]}"#),
        (
            GenericEnum::Named { a: 1, b: Some(2) },
            r#"{"Named":{"a":1,"b":2}}"#,
        ),
    ];
    for (value, expected) in cases {
        assert_eq!(to_string(&value).unwrap(), expected);
        let back: GenericEnum<i32> = from_str(expected).unwrap();
        assert_eq!(back, value);
    }
}

// ---------------------------------------------------------------------------
// 8. Generic tuple structure
// ---------------------------------------------------------------------------

#[derive(NsonSerialize, NsonDeserialize, Debug, PartialEq)]
struct Pair<T, U>(T, U);

#[test]
fn generic_tuple_struct() {
    let v = Pair(1_i32, "x".to_string());
    assert_eq!(to_string(&v).unwrap(), r#"[1,"x"]"#);
    let back: Pair<i32, String> = from_str(r#"[1,"x"]"#).unwrap();
    assert_eq!(back, v);
}

// ---------------------------------------------------------------------------
// 9. ?Sized Generic parameters (Box<T> field)
// ---------------------------------------------------------------------------

#[derive(NsonSerialize, NsonDeserialize)]
struct Unsized<T: ?Sized> {
    b: Box<T>,
}

#[test]
fn unsized_generic_param() {
    let v = Unsized {
        b: Box::new("hi".to_string()),
    };
    assert_eq!(to_string(&v).unwrap(), r#"{"b":"hi"}"#);
    let back: Unsized<String> = from_str(r#"{"b":"hi"}"#).unwrap();
    assert_eq!(back.b, Box::new("hi".to_string()));
}

// ---------------------------------------------------------------------------
// 10. Lifecycle + Borrowing (serde's #[serde(borrow)] semantics)
// ---------------------------------------------------------------------------

#[derive(NsonSerialize, NsonDeserialize, Debug, PartialEq)]
struct Borrowed<'a> {
    #[njson(borrow)]
    name: &'a str,
    id: u32,
}

#[test]
fn borrowed_lifetime_field() {
    let v = Borrowed {
        name: "alice",
        id: 1,
    };
    assert_eq!(to_string(&v).unwrap(), r#"{"name":"alice","id":1}"#);
    let json = r#"{"name":"bob","id":2}"#;
    let back: Borrowed<'_> = from_str(json).unwrap();
    assert_eq!(back, Borrowed { name: "bob", id: 2 });
}

// ---------------------------------------------------------------------------
// 11. Generics + container-level default (serde's #[serde(default)])
// ---------------------------------------------------------------------------

#[derive(NsonSerialize, NsonDeserialize, Debug, PartialEq)]
#[njson(default)]
struct AllDefault<T> {
    a: T,
    b: i32,
}

#[test]
fn container_level_default_generic() {
    let v = AllDefault { a: 1_i32, b: 2 };
    assert_eq!(to_string(&v).unwrap(), r#"{"a":1,"b":2}"#);
    let back: AllDefault<i32> = from_str(r#"{"a":5}"#).unwrap();
    assert_eq!(back, AllDefault { a: 5, b: 0 });
}

// ---------------------------------------------------------------------------
// 12. The path-spelled Option(core::option::Option<T>) must still be treated as an optional field.
// ---------------------------------------------------------------------------

#[derive(NsonSerialize, NsonDeserialize, Debug, PartialEq)]
struct PathOption<T> {
    name: String,
    note: core::option::Option<T>,
}

#[test]
fn path_spelled_option_is_optional() {
    let v = PathOption {
        name: "x".into(),
        note: core::option::Option::Some(1_i32),
    };
    assert_eq!(to_string(&v).unwrap(), r#"{"name":"x","note":1}"#);
    // The note must be accepted when missing (cannot treat the path-spelled Option as a required field).
    let back: PathOption<i32> = from_str(r#"{"name":"x"}"#).unwrap();
    assert_eq!(back.note, core::option::Option::None);
}