| 1 | use nox_json::{parse, Json}; |
| 2 | |
| 3 | #[test] |
| 4 | fn parses_a_roblox_client_version_response() { |
| 5 | let doc = parse(r#"{"version":"version-4a1d0c9f7b2e4d88","clientVersionUpload":"version-2e3f1a","bootstrapperVersion":"2.688.1092","nextClientVersionUpload":null,"IsRolloutComplete":true}"#).unwrap(); |
| 6 | assert_eq!(doc.str_at("version"), Some("version-4a1d0c9f7b2e4d88")); |
| 7 | assert_eq!(doc.str_at("clientVersionUpload"), Some("version-2e3f1a")); |
| 8 | assert_eq!(doc.str_at("bootstrapperVersion"), Some("2.688.1092")); |
| 9 | assert!(doc.get("nextClientVersionUpload").unwrap().is_null()); |
| 10 | assert_eq!(doc.path("IsRolloutComplete").unwrap().as_bool(), Some(true)); |
| 11 | assert_eq!(doc.str_at("nope"), None); |
| 12 | } |
| 13 | |
| 14 | #[test] |
| 15 | fn handles_escapes_and_unicode() { |
| 16 | let doc = parse(r#"{"a":"line\nbreak\ttab \"q\" \\ \/ \u00e9 \u5b57 \ud83d\ude00","b":"plain é字"}"#).unwrap(); |
| 17 | assert_eq!(doc.str_at("a"), Some("line\nbreak\ttab \"q\" \\ / é 字 😀")); |
| 18 | assert_eq!(doc.str_at("b"), Some("plain é字")); |
| 19 | } |
| 20 | |
| 21 | #[test] |
| 22 | fn numbers_match_expectations() { |
| 23 | let doc = parse(r#"[0,-1,3.5,1e3,-2.5E-2,12345678901,0.0]"#).unwrap(); |
| 24 | let a = doc.as_array().unwrap(); |
| 25 | assert_eq!(a[0].as_f64(), Some(0.0)); |
| 26 | assert_eq!(a[1].as_f64(), Some(-1.0)); |
| 27 | assert_eq!(a[2].as_f64(), Some(3.5)); |
| 28 | assert_eq!(a[3].as_f64(), Some(1000.0)); |
| 29 | assert_eq!(a[4].as_f64(), Some(-0.025)); |
| 30 | assert_eq!(a[5].as_u64(), Some(12345678901)); |
| 31 | assert_eq!(a[1].as_u64(), None, "negative must not become u64"); |
| 32 | } |
| 33 | |
| 34 | #[test] |
| 35 | fn nested_structures() { |
| 36 | let doc = parse(r#"{"a":{"b":{"c":[1,{"d":"deep"}]}}}"#).unwrap(); |
| 37 | assert_eq!(doc.path("a.b.c").unwrap().at(0).unwrap().as_f64(), Some(1.0)); |
| 38 | assert_eq!(doc.path("a.b.c").unwrap().at(1).unwrap().str_at("d"), Some("deep")); |
| 39 | assert_eq!(doc.path("a.b.zzz"), None); |
| 40 | } |
| 41 | |
| 42 | #[test] |
| 43 | fn rejects_malformed_input() { |
| 44 | for bad in [ |
| 45 | "", "{", "}", "[", "[1,", "[1,]", "{\"a\"}", "{\"a\":}", "{a:1}", |
| 46 | "\"unterminated", "tru", "nul", "01x", "--1", "{\"a\":1}x", "[1] [2]", |
| 47 | "\"\\q\"", "\"\\u12\"", "\"\\ud800\"", "\"\\ud800\\u0041\"", |
| 48 | ] { |
| 49 | assert!(parse(bad).is_err(), "{bad:?} should not parse"); |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | #[test] |
| 54 | fn depth_is_bounded() { |
| 55 | let deep = format!("{}1{}", "[".repeat(500), "]".repeat(500)); |
| 56 | assert!(parse(&deep).is_err(), "runaway nesting must be rejected"); |
| 57 | let ok = format!("{}1{}", "[".repeat(20), "]".repeat(20)); |
| 58 | assert!(parse(&ok).is_ok()); |
| 59 | } |
| 60 | |
| 61 | #[test] |
| 62 | fn survives_random_junk_without_panicking() { |
| 63 | let mut seed = 7u64; |
| 64 | let alphabet = b"{}[]\":,0123456789.eE-+ truefalsn\\/\x00\xff"; |
| 65 | for _ in 0..4000 { |
| 66 | let mut s = String::new(); |
| 67 | seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); |
| 68 | let n = (seed >> 33) as usize % 40; |
| 69 | for _ in 0..n { |
| 70 | seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); |
| 71 | s.push(alphabet[(seed >> 33) as usize % alphabet.len()] as char); |
| 72 | } |
| 73 | let _ = parse(&s); |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | #[test] |
| 78 | fn duplicate_keys_keep_the_first() { |
| 79 | let doc = parse(r#"{"k":"first","k":"second"}"#).unwrap(); |
| 80 | assert_eq!(doc.str_at("k"), Some("first")); |
| 81 | match doc { |
| 82 | Json::Obj(kv) => assert_eq!(kv.len(), 2, "both pairs are retained"), |
| 83 | _ => panic!(), |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | #[test] |
| 88 | fn writes_json_that_parses_back_identically() { |
| 89 | let src = r#"{"a":1,"b":[true,false,null,"x\ny"],"c":{"d":-2.5,"e":"é字\"q\""},"f":[],"g":{}}"#; |
| 90 | let doc = parse(src).unwrap(); |
| 91 | let text = doc.to_string(); |
| 92 | let back = parse(&text).unwrap_or_else(|e| panic!("re-parse failed: {e}\n{text}")); |
| 93 | assert_eq!(doc, back, "round trip changed the document"); |
| 94 | assert!(text.contains("\\n"), "control characters must stay escaped"); |
| 95 | } |
| 96 | |
| 97 | #[test] |
| 98 | fn set_updates_or_appends() { |
| 99 | let mut o = Json::obj(); |
| 100 | o.set("a", Json::Num(1.0)); |
| 101 | o.set("b", Json::Str("x".into())); |
| 102 | o.set("a", Json::Num(2.0)); |
| 103 | assert_eq!(o.path("a").unwrap().as_f64(), Some(2.0)); |
| 104 | assert_eq!(o.str_at("b"), Some("x")); |
| 105 | match &o { |
| 106 | Json::Obj(kv) => assert_eq!(kv.len(), 2), |
| 107 | _ => panic!(), |
| 108 | } |
| 109 | } |