forma_json 0.1.0

JSON serialization and deserialization for forma_core.
Documentation
use forma_derive::{Deserialize, Serialize};
use forma_json::{from_str, to_string_pretty};

// ── Primitives ──────────────────────────────────────────────────────

#[test]
fn pretty_primitives_unchanged() {
    // Scalars should not be affected by pretty-printing.
    assert_eq!(to_string_pretty(&true).unwrap(), "true");
    assert_eq!(to_string_pretty(&42i32).unwrap(), "42");
    assert_eq!(to_string_pretty(&3.14f64).unwrap(), "3.14");
    assert_eq!(to_string_pretty(&"hello").unwrap(), "\"hello\"");
    assert_eq!(to_string_pretty(&()).unwrap(), "null");
    assert_eq!(to_string_pretty(&None::<i32>).unwrap(), "null");
    assert_eq!(to_string_pretty(&Some(42)).unwrap(), "42");
}

// ── Vec / Seq ───────────────────────────────────────────────────────

#[test]
fn pretty_vec() {
    let v = vec![1, 2, 3];
    let expected = "\
[
  1,
  2,
  3
]";
    assert_eq!(to_string_pretty(&v).unwrap(), expected);
}

#[test]
fn pretty_empty_vec() {
    let v: Vec<i32> = vec![];
    assert_eq!(to_string_pretty(&v).unwrap(), "[]");
}

#[test]
fn pretty_nested_vec() {
    let v = vec![vec![1, 2], vec![3]];
    let expected = "\
[
  [
    1,
    2
  ],
  [
    3
  ]
]";
    assert_eq!(to_string_pretty(&v).unwrap(), expected);
}

// ── Tuple ───────────────────────────────────────────────────────────

#[test]
fn pretty_tuple() {
    let t = (1, "two", true);
    let expected = "\
[
  1,
  \"two\",
  true
]";
    assert_eq!(to_string_pretty(&t).unwrap(), expected);
}

// ── Map ─────────────────────────────────────────────────────────────

#[test]
fn pretty_btreemap() {
    use std::collections::BTreeMap;
    let mut m = BTreeMap::new();
    m.insert("a".to_string(), 1);
    m.insert("b".to_string(), 2);
    let expected = "\
{
  \"a\": 1,
  \"b\": 2
}";
    assert_eq!(to_string_pretty(&m).unwrap(), expected);
}

#[test]
fn pretty_empty_map() {
    use std::collections::BTreeMap;
    let m: BTreeMap<String, i32> = BTreeMap::new();
    assert_eq!(to_string_pretty(&m).unwrap(), "{}");
}

// ── Struct ──────────────────────────────────────────────────────────

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Point {
    x: i32,
    y: i32,
}

#[test]
fn pretty_struct() {
    let p = Point { x: 1, y: 2 };
    let expected = "\
{
  \"x\": 1,
  \"y\": 2
}";
    assert_eq!(to_string_pretty(&p).unwrap(), expected);
}

// ── Nested struct ───────────────────────────────────────────────────

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Line {
    start: Point,
    end: Point,
}

#[test]
fn pretty_nested_struct() {
    let line = Line {
        start: Point { x: 0, y: 0 },
        end: Point { x: 10, y: 20 },
    };
    let expected = "\
{
  \"start\": {
    \"x\": 0,
    \"y\": 0
  },
  \"end\": {
    \"x\": 10,
    \"y\": 20
  }
}";
    assert_eq!(to_string_pretty(&line).unwrap(), expected);
}

// ── Enum (unit variant) ─────────────────────────────────────────────

#[derive(Debug, PartialEq, Serialize, Deserialize)]
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    Point,
}

#[test]
fn pretty_enum_unit_variant() {
    assert_eq!(to_string_pretty(&Shape::Point).unwrap(), "\"Point\"");
}

// ── Enum (struct variant) ───────────────────────────────────────────

#[test]
fn pretty_enum_struct_variant() {
    let s = Shape::Circle { radius: 5.0 };
    let expected = "\
{
  \"Circle\": {
    \"radius\": 5
  }
}";
    assert_eq!(to_string_pretty(&s).unwrap(), expected);
}

#[test]
fn pretty_enum_struct_variant_multi() {
    let s = Shape::Rectangle {
        width: 3.0,
        height: 4.0,
    };
    let expected = "\
{
  \"Rectangle\": {
    \"width\": 3,
    \"height\": 4
  }
}";
    assert_eq!(to_string_pretty(&s).unwrap(), expected);
}

// ── Enum (newtype variant) ──────────────────────────────────────────

#[derive(Debug, PartialEq, Serialize, Deserialize)]
enum Value {
    Int(i64),
    Text(String),
}

#[test]
fn pretty_enum_newtype_variant() {
    let v = Value::Int(42);
    let expected = "\
{
  \"Int\": 42
}";
    assert_eq!(to_string_pretty(&v).unwrap(), expected);

    let v = Value::Text("hello".into());
    let expected = "\
{
  \"Text\": \"hello\"
}";
    assert_eq!(to_string_pretty(&v).unwrap(), expected);
}

// ── Enum (tuple variant) ────────────────────────────────────────────

#[derive(Debug, PartialEq, Serialize, Deserialize)]
enum Pair {
    Ints(i32, i32),
}

#[test]
fn pretty_enum_tuple_variant() {
    let p = Pair::Ints(1, 2);
    let expected = "\
{
  \"Ints\": [
    1,
    2
  ]
}";
    assert_eq!(to_string_pretty(&p).unwrap(), expected);
}

// ── Complex roundtrip ───────────────────────────────────────────────

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Person {
    name: String,
    age: u32,
    emails: Vec<String>,
    address: Option<Address>,
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Address {
    street: String,
    city: String,
    zip: String,
}

#[test]
fn pretty_complex_roundtrip() {
    let person = Person {
        name: "Alice".into(),
        age: 30,
        emails: vec!["alice@example.com".into(), "a@b.c".into()],
        address: Some(Address {
            street: "123 Main St".into(),
            city: "Springfield".into(),
            zip: "12345".into(),
        }),
    };
    let json = to_string_pretty(&person).unwrap();
    let expected = "\
{
  \"name\": \"Alice\",
  \"age\": 30,
  \"emails\": [
    \"alice@example.com\",
    \"a@b.c\"
  ],
  \"address\": {
    \"street\": \"123 Main St\",
    \"city\": \"Springfield\",
    \"zip\": \"12345\"
  }
}";
    assert_eq!(json, expected);
    // Verify the pretty-printed output can be parsed back.
    let back: Person = from_str(&json).unwrap();
    assert_eq!(back, person);
}

#[test]
fn pretty_complex_no_address() {
    let person = Person {
        name: "Bob".into(),
        age: 25,
        emails: vec![],
        address: None,
    };
    let expected = "\
{
  \"name\": \"Bob\",
  \"age\": 25,
  \"emails\": [],
  \"address\": null
}";
    assert_eq!(to_string_pretty(&person).unwrap(), expected);
}

// ── to_writer_pretty ────────────────────────────────────────────────

#[test]
fn to_writer_pretty_works() {
    let mut buf = Vec::new();
    forma_json::to_writer_pretty(&mut buf, &vec![1, 2]).unwrap();
    let s = String::from_utf8(buf).unwrap();
    let expected = "\
[
  1,
  2
]";
    assert_eq!(s, expected);
}

// ── Compact output unchanged ────────────────────────────────────────

#[test]
fn compact_unchanged() {
    // Verify the compact serializer still produces the same output.
    let p = Point { x: 1, y: 2 };
    assert_eq!(forma_json::to_string(&p).unwrap(), "{\"x\":1,\"y\":2}");

    let v = vec![1, 2, 3];
    assert_eq!(forma_json::to_string(&v).unwrap(), "[1,2,3]");
}

// ── Custom indent ───────────────────────────────────────────────────

#[test]
fn custom_indent_tab() {
    use forma_core::ser::Serialize;
    let v = vec![1, 2];
    let mut buf = Vec::new();
    let mut ser = forma_json::ser::Serializer::pretty(&mut buf, "\t");
    v.serialize(&mut ser).unwrap();
    let s = String::from_utf8(buf).unwrap();
    let expected = "[\n\t1,\n\t2\n]";
    assert_eq!(s, expected);
}

#[test]
fn custom_indent_four_spaces() {
    use forma_core::ser::Serialize;
    let p = Point { x: 1, y: 2 };
    let mut buf = Vec::new();
    let mut ser = forma_json::ser::Serializer::pretty(&mut buf, "    ");
    p.serialize(&mut ser).unwrap();
    let s = String::from_utf8(buf).unwrap();
    let expected = "\
{
    \"x\": 1,
    \"y\": 2
}";
    assert_eq!(s, expected);
}