midiserde 0.1.1

When mini isn't enough and serde is too much
Documentation
//! Tests for #[mini(flatten)] attribute.
//!
//! Run: `cargo test -p midiserde --features full flatten`

use midiserde::{from_value, json, to_value, Deserialize, Serialize};
use std::collections::HashMap;

// --- Use case 1: Capture unknown fields ---

#[derive(Debug, Deserialize, Serialize)]
struct UserWithExtra {
    id: String,
    username: String,
    #[mini(flatten)]
    extra: HashMap<String, miniserde::json::Value>,
}

#[test]
fn flatten_capture_unknown_fields() {
    let json_str = r#"{"id": "123", "username": "alice", "mascot": "Ferris", "score": 42}"#;
    let value: miniserde::json::Value = json::from_str(json_str).unwrap();
    let v: UserWithExtra = from_value(&value).unwrap();
    assert_eq!(v.id, "123");
    assert_eq!(v.username, "alice");
    assert!(v.extra.contains_key("mascot"));
    assert!(v.extra.contains_key("score"));
    assert_eq!(v.extra.len(), 2);
}

#[test]
fn flatten_capture_roundtrip() {
    let original = UserWithExtra {
        id: "456".into(),
        username: "bob".into(),
        extra: [("mascot".into(), miniserde::json::Value::String("crab".into()))]
            .into_iter()
            .collect(),
    };
    let value = to_value(&original);
    let restored: UserWithExtra = from_value(&value).unwrap();
    assert_eq!(original.id, restored.id);
    assert_eq!(original.username, restored.username);
    assert_eq!(original.extra.len(), restored.extra.len());
}

// --- Use case 2: Factor out grouped keys ---

#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct Pagination {
    limit: u64,
    offset: u64,
    total: u64,
}

#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct UsersPaginated {
    users: Vec<String>,
    #[mini(flatten)]
    pagination: Pagination,
}

#[test]
fn flatten_factor_out_struct() {
    let json_str = r#"{"users": ["a", "b"], "limit": 100, "offset": 200, "total": 1053}"#;
    let value: miniserde::json::Value = json::from_str(json_str).unwrap();
    let v: UsersPaginated = from_value(&value).unwrap();
    assert_eq!(v.users, vec!["a", "b"]);
    assert_eq!(v.pagination.limit, 100);
    assert_eq!(v.pagination.offset, 200);
    assert_eq!(v.pagination.total, 1053);
}

// --- Use case 3: Combination (pagination + extra fields) ---
// No field list needed: accepts_key routes keys automatically

#[derive(Debug, Deserialize, Serialize)]
struct UsersWithExtra {
    users: Vec<String>,
    #[mini(flatten)]
    pagination: Pagination,
    #[mini(flatten)]
    extra: HashMap<String, miniserde::json::Value>,
}

#[test]
fn flatten_combination() {
    let json_str = r#"{"users": ["a"], "limit": 10, "offset": 0, "total": 1, "mascot": "Ferris"}"#;
    let value: miniserde::json::Value = json::from_str(json_str).unwrap();
    let v: UsersWithExtra = from_value(&value).unwrap();
    assert_eq!(v.users, vec!["a"]);
    assert_eq!(v.pagination.limit, 10);
    assert_eq!(v.pagination.offset, 0);
    assert_eq!(v.pagination.total, 1);
    assert!(v.extra.contains_key("mascot"));
}

#[test]
fn flatten_combination_roundtrip() {
    let original = UsersWithExtra {
        users: vec!["x".into(), "y".into()],
        pagination: Pagination {
            limit: 10,
            offset: 0,
            total: 25,
        },
        extra: [
            ("mascot".into(), miniserde::json::Value::String("Ferris".into())),
            (
                "score".into(),
                miniserde::json::Value::Number(miniserde::json::Number::F64(42.0)),
            ),
        ]
        .into_iter()
        .collect(),
    };
    let value = to_value(&original);
    let restored: UsersWithExtra = from_value(&value).unwrap();
    assert_eq!(original.users, restored.users);
    assert_eq!(original.pagination.limit, restored.pagination.limit);
    assert_eq!(original.pagination.offset, restored.pagination.offset);
    assert_eq!(original.pagination.total, restored.pagination.total);
    assert_eq!(original.extra.len(), restored.extra.len());
    assert!(restored.extra.contains_key("mascot"));
    assert!(restored.extra.contains_key("score"));
}

#[test]
fn flatten_factor_out_roundtrip() {
    let original = UsersPaginated {
        users: vec!["x".into(), "y".into()],
        pagination: Pagination {
            limit: 10,
            offset: 0,
            total: 25,
        },
    };
    let value = to_value(&original);
    let restored: UsersPaginated = from_value(&value).unwrap();
    assert_eq!(original.users, restored.users);
    assert_eq!(original.pagination.limit, restored.pagination.limit);
    assert_eq!(original.pagination.offset, restored.pagination.offset);
    assert_eq!(original.pagination.total, restored.pagination.total);
}

// --- Use case 4: Multiple struct flattens (no field lists needed) ---

#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct Metadata {
    source: String,
    version: u64,
}

#[derive(Debug, Deserialize, Serialize)]
struct FullResponse {
    id: String,
    #[mini(flatten)]
    pagination: Pagination,
    #[mini(flatten)]
    metadata: Metadata,
}

#[test]
fn flatten_multi_struct() {
    let json_str = r#"{"id":"resp-1","limit":50,"offset":10,"total":500,"source":"api","version":3}"#;
    let value: miniserde::json::Value = json::from_str(json_str).unwrap();
    let v: FullResponse = from_value(&value).unwrap();
    assert_eq!(v.id, "resp-1");
    assert_eq!(v.pagination.limit, 50);
    assert_eq!(v.pagination.offset, 10);
    assert_eq!(v.pagination.total, 500);
    assert_eq!(v.metadata.source, "api");
    assert_eq!(v.metadata.version, 3);
}

#[test]
fn flatten_multi_struct_roundtrip() {
    let original = FullResponse {
        id: "resp-42".into(),
        pagination: Pagination {
            limit: 25,
            offset: 0,
            total: 100,
        },
        metadata: Metadata {
            source: "internal".into(),
            version: 7,
        },
    };
    let value = to_value(&original);
    let restored: FullResponse = from_value(&value).unwrap();
    assert_eq!(original.id, restored.id);
    assert_eq!(original.pagination, restored.pagination);
    assert_eq!(original.metadata, restored.metadata);
}

// --- Use case 5: Multi struct + map flatten ---

#[derive(Debug, Deserialize, Serialize)]
struct FullResponseWithExtra {
    id: String,
    #[mini(flatten)]
    pagination: Pagination,
    #[mini(flatten)]
    metadata: Metadata,
    #[mini(flatten)]
    extra: HashMap<String, miniserde::json::Value>,
}

#[test]
fn flatten_multi_struct_plus_map() {
    let json_str = r#"{"id":"r1","limit":10,"offset":0,"total":42,"source":"api","version":1,"debug":"true"}"#;
    let value: miniserde::json::Value = json::from_str(json_str).unwrap();
    let v: FullResponseWithExtra = from_value(&value).unwrap();
    assert_eq!(v.id, "r1");
    assert_eq!(v.pagination.limit, 10);
    assert_eq!(v.metadata.source, "api");
    assert_eq!(v.metadata.version, 1);
    assert!(v.extra.contains_key("debug"));
    assert_eq!(v.extra.len(), 1);
}

#[test]
fn flatten_multi_struct_plus_map_roundtrip() {
    let original = FullResponseWithExtra {
        id: "r2".into(),
        pagination: Pagination {
            limit: 5,
            offset: 10,
            total: 50,
        },
        metadata: Metadata {
            source: "webhook".into(),
            version: 2,
        },
        extra: [("trace_id".into(), miniserde::json::Value::String("abc-123".into()))]
            .into_iter()
            .collect(),
    };
    let value = to_value(&original);
    let restored: FullResponseWithExtra = from_value(&value).unwrap();
    assert_eq!(original.id, restored.id);
    assert_eq!(original.pagination, restored.pagination);
    assert_eq!(original.metadata, restored.metadata);
    assert_eq!(original.extra.len(), restored.extra.len());
    assert!(restored.extra.contains_key("trace_id"));
}