asun 1.0.1

ASUN (Array-Schema Unified Notation) - A high-performance, token-efficient serde data format with schema-data separation
Documentation
#[cfg(test)]
mod format_validation {
    use asun::{decode, encode_pretty, encode_pretty_typed};
    use serde::{Deserialize, Serialize};

    #[derive(Debug, Serialize, Deserialize, PartialEq)]
    struct FmtUser {
        id: i64,
        name: String,
    }

    #[derive(Debug, Serialize, Deserialize, PartialEq)]
    struct Score {
        id: i64,
        value: f64,
        label: String,
    }

    #[derive(Debug, Serialize, Deserialize, PartialEq)]
    struct Inner {
        x: i64,
        label: String,
    }

    #[derive(Debug, Serialize, Deserialize, PartialEq)]
    struct Outer {
        id: i64,
        inner: Inner,
    }

    const BAD_FMT: &str = "{id@int, name@str}:\n  (1, Alice),\n  (2, Bob),\n  (3, Carol)";
    const GOOD_FMT: &str = "[{id@int, name@str}]:\n  (1, Alice),\n  (2, Bob),\n  (3, Carol)";

    // -------------------------------------------------------------------------
    // Invalid format tests
    // -------------------------------------------------------------------------

    #[test]
    fn test_bad_format_as_vec() {
        let result = decode::<Vec<FmtUser>>(BAD_FMT);
        assert!(result.is_err(), "should reject {{schema}}: format for Vec");
    }

    #[test]
    fn test_bad_format_as_single_trailing_rows() {
        let result = decode::<FmtUser>(BAD_FMT);
        assert!(result.is_err(), "should reject trailing rows");
    }

    #[test]
    fn test_bad_format_extra_tuple() {
        let bad = "{id@int,name@str}:(10,Dave),(11,Eve)";
        let result = decode::<FmtUser>(bad);
        assert!(
            result.is_err(),
            "should reject trailing tuple after single struct"
        );
    }

    #[test]
    fn test_bad_format_many_rows_no_bracket() {
        let bad = "{id,name}:(1,A),(2,B),(3,C),(4,D),(5,E)";
        let result = decode::<Vec<FmtUser>>(bad);
        assert!(
            result.is_err(),
            "should reject {{schema}}: without [] for Vec"
        );
    }

    #[test]
    fn test_good_format_as_vec() {
        let result = decode::<Vec<FmtUser>>(GOOD_FMT);
        assert!(result.is_ok(), "should accept [{{}}]: format for Vec");
        let users = result.unwrap();
        assert_eq!(users.len(), 3);
        assert_eq!(users[0].name, "Alice");
        assert_eq!(users[2].name, "Carol");
    }

    #[test]
    fn test_good_format_untyped() {
        let good = "[{id,name}]:\n  (1,Alice),\n  (2,Bob),\n  (3,Carol)";
        let result = decode::<Vec<FmtUser>>(good);
        assert!(
            result.is_ok(),
            "should accept untyped [{{}}]: format for Vec"
        );
        let users = result.unwrap();
        assert_eq!(users.len(), 3);
        assert_eq!(users[1].name, "Bob");
    }

    #[test]
    fn test_good_format_single_struct() {
        // {id@int, name@str}: (1, Alice) — single struct, one tuple: MUST succeed
        let good = "{id@int, name@str}:(1,Alice)";
        let result = decode::<FmtUser>(good);
        assert!(result.is_ok(), "should accept single struct with one tuple");
        let u = result.unwrap();
        assert_eq!(u.id, 1);
        assert_eq!(u.name, "Alice");
    }

    #[test]
    fn test_good_format_vec_single_item() {
        // [{id@int, name@str}]: (1, Alice) — array schema with one tuple: MUST succeed
        let good = "[{id@int, name@str}]:(1,Alice)";
        let result = decode::<Vec<FmtUser>>(good);
        assert!(result.is_ok(), "should accept [{{}}]: with single tuple");
        let users = result.unwrap();
        assert_eq!(users.len(), 1);
        assert_eq!(users[0].id, 1);
        assert_eq!(users[0].name, "Alice");
    }

    // -------------------------------------------------------------------------
    // encodePretty -> decode roundtrip tests
    // -------------------------------------------------------------------------

    #[test]
    fn test_pretty_simple_roundtrip() {
        let u = FmtUser {
            id: 42,
            name: "Alice".into(),
        };
        let pretty = encode_pretty(&u).unwrap();
        let u2: FmtUser = decode(&pretty).unwrap();
        assert_eq!(u, u2);
    }

    #[test]
    fn test_pretty_typed_roundtrip() {
        let u = FmtUser {
            id: 7,
            name: "Bob".into(),
        };
        let pretty = encode_pretty_typed(&u).unwrap();
        assert!(pretty.contains("@int") || pretty.contains("@str"));
        let u2: FmtUser = decode(&pretty).unwrap();
        assert_eq!(u, u2);
    }

    #[test]
    fn test_pretty_vec_roundtrip() {
        let users = vec![
            FmtUser {
                id: 1,
                name: "Alice".into(),
            },
            FmtUser {
                id: 2,
                name: "Bob".into(),
            },
            FmtUser {
                id: 3,
                name: "Carol".into(),
            },
        ];
        let pretty = encode_pretty(&users).unwrap();
        assert!(pretty.contains('\n'), "expected multi-line output");
        let users2: Vec<FmtUser> = decode(&pretty).unwrap();
        assert_eq!(users, users2);
    }

    #[test]
    fn test_pretty_score_slice_roundtrip() {
        let scores = vec![
            Score {
                id: 1,
                value: 95.5,
                label: "excellent".into(),
            },
            Score {
                id: 2,
                value: 72.3,
                label: "good".into(),
            },
            Score {
                id: 3,
                value: 40.0,
                label: "fail".into(),
            },
        ];
        let pretty = encode_pretty(&scores).unwrap();
        let scores2: Vec<Score> = decode(&pretty).unwrap();
        assert_eq!(scores, scores2);
        assert_eq!(scores2[0].label, "excellent");
        assert!((scores2[0].value - 95.5).abs() < 1e-9);
    }

    #[test]
    fn test_pretty_nested_roundtrip() {
        let o = Outer {
            id: 5,
            inner: Inner {
                x: 10,
                label: "test".into(),
            },
        };
        let pretty = encode_pretty(&o).unwrap();
        let o2: Outer = decode(&pretty).unwrap();
        assert_eq!(o, o2);
    }

    #[test]
    fn test_pretty_large_vec_roundtrip() {
        let users: Vec<FmtUser> = (1..=50)
            .map(|i| FmtUser {
                id: i,
                name: format!("user{}", i),
            })
            .collect();
        let pretty = encode_pretty(&users).unwrap();
        let users2: Vec<FmtUser> = decode(&pretty).unwrap();
        assert_eq!(users, users2);
        assert_eq!(users2.last().unwrap().id, 50);
    }

    #[test]
    fn test_pretty_typed_vec_roundtrip() {
        let users = vec![
            FmtUser {
                id: 1,
                name: "Alice".into(),
            },
            FmtUser {
                id: 2,
                name: "Bob".into(),
            },
        ];
        let pretty = encode_pretty_typed(&users).unwrap();
        assert!(pretty.contains("@int") || pretty.contains("@str"));
        let users2: Vec<FmtUser> = decode(&pretty).unwrap();
        assert_eq!(users, users2);
    }
}