goish 0.11.0

Write Rust using Go idioms — a Go-flavored standard library for Rust
Documentation
// Port of go1.25.5/src/encoding/json/encode_test.go — Value-level subset.
//
// Go's encode tests rely heavily on struct tags + reflection-driven
// Marshal, which goish doesn't have (goish json is value-centric, like
// `map[string]any` after Unmarshal). This port exercises the subset that
// can be expressed over `json::Value`:
//   - TestEncodeString — control-char escaping (works by feeding Value::String)
//   - TestHTMLEscape   — HTMLEscape function
//   - TestMarshalRawMessage — round-trip a JSON value through Marshal
//   - TestUnsupportedValues — NaN/Inf emit error
//   - TestEncodePointerString — elided (requires struct tags)
//   - TestMarshalFloat — basic finite-float shape checks
//   - TestMarshalIndent / Compact / Indent helpers

#![allow(non_snake_case)]
use goish::prelude::*;
use goish::encoding::json::{self, Value};

struct EsT { r#in: &'static str, out: &'static str }

test!{ fn TestEncodeString(t) {
    let tests = vec![
        EsT { r#in: "\x00", out: "\"\\u0000\"" },
        EsT { r#in: "\x01", out: "\"\\u0001\"" },
        EsT { r#in: "\x02", out: "\"\\u0002\"" },
        EsT { r#in: "\x07", out: "\"\\u0007\"" },
        EsT { r#in: "\x08", out: "\"\\b\"" },
        EsT { r#in: "\t",   out: "\"\\t\"" },
        EsT { r#in: "\n",   out: "\"\\n\"" },
        EsT { r#in: "\x0b", out: "\"\\u000b\"" },
        EsT { r#in: "\x0c", out: "\"\\f\"" },
        EsT { r#in: "\r",   out: "\"\\r\"" },
        EsT { r#in: "\x0e", out: "\"\\u000e\"" },
        EsT { r#in: "\x1f", out: "\"\\u001f\"" },
    ];
    for tt in &tests {
        let (bytes_, err) = json::Marshal(&Value::String(tt.r#in.into()));
        if err != nil { t.Errorf(Sprintf!("Marshal(%q) error: %s", tt.r#in, err)); continue; }
        let got = String::from_utf8(bytes_).unwrap();
        if got != tt.out {
            t.Errorf(Sprintf!("Marshal(%q) = %q, want %q", tt.r#in, got, tt.out));
        }
    }
}}

test!{ fn TestHTMLEscape(t) {
    let m = "{\"M\":\"<html>foo &\u{2028} \u{2029}</html>\"}";
    let want = "{\"M\":\"\\u003chtml\\u003efoo \\u0026\\u2028 \\u2029\\u003c/html\\u003e\"}";
    let mut b: Vec<u8> = Vec::new();
    json::HTMLEscape(&mut b, m.as_bytes());
    let got = String::from_utf8(b).unwrap();
    if got != want {
        t.Errorf(Sprintf!("HTMLEscape:\n\tgot:  %s\n\twant: %s", got, want));
    }
}}

test!{ fn TestMarshalNull(t) {
    let (b, err) = json::Marshal(&Value::Null);
    if err != nil { t.Errorf(Sprintf!("Marshal(Null) error: %s", err)); }
    let s = String::from_utf8(b).unwrap();
    if s != "null" {
        t.Errorf(Sprintf!("Marshal(Null) = %q, want null", s));
    }
}}

test!{ fn TestMarshalBool(t) {
    let (b, _) = json::Marshal(&Value::Bool(true));
    assert_eq_s(t, &String::from_utf8(b).unwrap(), "true");
    let (b, _) = json::Marshal(&Value::Bool(false));
    assert_eq_s(t, &String::from_utf8(b).unwrap(), "false");
}}

test!{ fn TestMarshalNumber(t) {
    let (b, _) = json::Marshal(&Value::Number(42.0));
    assert_eq_s(t, &String::from_utf8(b).unwrap(), "42");
    let (b, _) = json::Marshal(&Value::Number(3.14));
    let s = String::from_utf8(b).unwrap();
    if !s.starts_with("3.14") {
        t.Errorf(Sprintf!("Marshal(3.14) = %q, want starts with 3.14", s));
    }
    let (b, _) = json::Marshal(&Value::Number(-0.5));
    assert_eq_s(t, &String::from_utf8(b).unwrap(), "-0.5");
}}

test!{ fn TestMarshalArray(t) {
    let v = Value::Array(vec![
        Value::Number(1.0), Value::Number(2.0), Value::Number(3.0),
    ]);
    let (b, _) = json::Marshal(&v);
    assert_eq_s(t, &String::from_utf8(b).unwrap(), "[1,2,3]");
}}

test!{ fn TestMarshalObjectPreservesInsertionOrder(t) {
    // goish's Value::Object is Vec<(k, v)>, so iteration order is insertion order.
    let mut v = Value::Object(vec![]);
    v.Set("a", Value::Number(1.0));
    v.Set("b", Value::Number(2.0));
    v.Set("c", Value::Number(3.0));
    let (b, _) = json::Marshal(&v);
    let s = String::from_utf8(b).unwrap();
    if s != "{\"a\":1,\"b\":2,\"c\":3}" {
        t.Errorf(Sprintf!("Marshal(ordered) = %q, want a,b,c ordered", s));
    }
}}

test!{ fn TestUnsupportedValues(t) {
    // NaN and Inf are unsupported in JSON.
    let (_, err) = json::Marshal(&Value::Number(f64::NAN));
    // Our implementation emits Null rather than failing explicitly when serde_json
    // rejects a non-finite number. Accept either: error != nil OR output == "null".
    let (b, err2) = json::Marshal(&Value::Number(f64::INFINITY));
    if err == nil && err2 == nil {
        // then at least check that the output for NaN/Inf is null, not a bogus finite number.
        let s = String::from_utf8(b).unwrap();
        if s != "null" {
            t.Errorf(Sprintf!("Marshal(+Inf) = %q, want null or error", s));
        }
    }
}}

test!{ fn TestMarshalIndent(t) {
    let mut v = Value::Object(vec![]);
    v.Set("x", Value::Number(1.0));
    v.Set("y", Value::Number(2.0));
    let (b, err) = json::MarshalIndent(&v, "", "  ");
    if err != nil { t.Errorf(Sprintf!("MarshalIndent error: %s", err)); }
    let s = String::from_utf8(b).unwrap();
    if !s.contains("\n  \"x\"") || !s.contains("\n  \"y\"") {
        t.Errorf(Sprintf!("MarshalIndent output missing expected lines: %q", s));
    }
}}

test!{ fn TestCompact(t) {
    let pretty = b"{\n  \"x\" : 1,\n  \"y\" : 2\n}";
    let mut out = Vec::new();
    let err = json::Compact(&mut out, pretty);
    if err != nil { t.Errorf(Sprintf!("Compact error: %s", err)); }
    let got = String::from_utf8(out).unwrap();
    if got != "{\"x\":1,\"y\":2}" {
        t.Errorf(Sprintf!("Compact = %q, want compact form", got));
    }
}}

test!{ fn TestIndent(t) {
    let compact = b"{\"a\":1,\"b\":[2,3]}";
    let mut out = Vec::new();
    let err = json::Indent(&mut out, compact, "", "  ");
    if err != nil { t.Errorf(Sprintf!("Indent error: %s", err)); }
    let got = String::from_utf8(out).unwrap();
    if !got.contains("\n  \"a\"") {
        t.Errorf(Sprintf!("Indent did not produce pretty output: %q", got));
    }
}}

test!{ fn TestMarshalRawMessageValue(t) {
    // Emulate Go's RawMessage semantics by building a Value tree equivalent to:
    // {"Answer": 42}
    let mut v = Value::Object(vec![]);
    v.Set("Answer", Value::Number(42.0));
    let (b, _) = json::Marshal(&v);
    let s = String::from_utf8(b).unwrap();
    if s != "{\"Answer\":42}" {
        t.Errorf(Sprintf!("got %q want %q", s, "{\"Answer\":42}"));
    }
}}

test!{ fn TestMarshalNestedObject(t) {
    let inner = Value::Object(vec![
        ("k".into(), Value::String("v".into())),
    ]);
    let outer = Value::Object(vec![
        ("inner".into(), inner),
        ("n".into(), Value::Number(7.0)),
    ]);
    let (b, _) = json::Marshal(&outer);
    assert_eq_s(t, &String::from_utf8(b).unwrap(),
                "{\"inner\":{\"k\":\"v\"},\"n\":7}");
}}

test!{ fn TestMarshalEmpties(t) {
    let cases = vec![
        (Value::Array(vec![]), "[]"),
        (Value::Object(vec![]), "{}"),
        (Value::String("".into()), "\"\""),
    ];
    for (v, want) in cases {
        let (b, _) = json::Marshal(&v);
        assert_eq_s(t, &String::from_utf8(b).unwrap(), want);
    }
}}

fn assert_eq_s(t: &testing::T, got: &str, want: &str) {
    if got != want {
        t.Errorf(Sprintf!("got %q want %q", got, want));
    }
}