oxedyne/fe2o3/fe2o3_jdat/tests/json_strict.rs
9.0 KiB, 1 run
created by r1870400018:61148, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! `Dat::decode_json_strict` against `serde_json`, an RFC 8259 reader that did not come from here: |
| 2 | //! every document it accepts and refuses by hand, and a fuzzed corpus where the two must agree |
| 3 | //! on what is JSON and on what it says. |
| 4 | |
| 5 | use oxedyne_fe2o3_core::prelude::*; |
| 6 | use oxedyne_fe2o3_jdat::{ |
| 7 | prelude::*, |
| 8 | bdat::DecodeLimits, |
| 9 | string::dec::DecoderConfig, |
| 10 | }; |
| 11 | |
| 12 | use serde_json::Value; |
| 13 | |
| 14 | |
| 15 | fn strict(s: &str) -> Outcome<Dat> { |
| 16 | Dat::decode_json_strict(s, &DecodeLimits::text()) |
| 17 | } |
| 18 | |
| 19 | /// Does `d` say what `v` says? Numbers are compared as integers where both are integers, and as |
| 20 | /// the nearest `f64` otherwise, since `serde_json` reads a fraction or an exponent as one. |
| 21 | fn same(d: &Dat, v: &Value) -> bool { |
| 22 | match (d, v) { |
| 23 | (Dat::Opt(o), Value::Null) => o.is_none(), |
| 24 | (Dat::Bool(a), Value::Bool(b)) => a == b, |
| 25 | (Dat::Str(a), Value::String(b)) => a == b, |
| 26 | (Dat::List(a), Value::Array(b)) => |
| 27 | a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| same(x, y)), |
| 28 | (Dat::Map(a), Value::Object(b)) => |
| 29 | a.len() == b.len() && b.iter().all(|(k, y)| match a.get(&Dat::Str(k.clone())) { |
| 30 | Some(x) => same(x, y), |
| 31 | None => false, |
| 32 | }), |
| 33 | (_, Value::Number(n)) => same_number(d, n), |
| 34 | _ => false, |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | fn integer(d: &Dat) -> Option<i128> { |
| 39 | match d { |
| 40 | Dat::U8(n) => Some(*n as i128), |
| 41 | Dat::U16(n) => Some(*n as i128), |
| 42 | Dat::U32(n) => Some(*n as i128), |
| 43 | Dat::U64(n) => Some(*n as i128), |
| 44 | Dat::I8(n) => Some(*n as i128), |
| 45 | Dat::I16(n) => Some(*n as i128), |
| 46 | Dat::I32(n) => Some(*n as i128), |
| 47 | Dat::I64(n) => Some(*n as i128), |
| 48 | Dat::I128(n) => Some(*n), |
| 49 | _ => None, |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | fn same_number(d: &Dat, n: &serde_json::Number) -> bool { |
| 54 | if let (Some(a), Some(b)) = (integer(d), n.as_i64()) { |
| 55 | return a == b as i128; |
| 56 | } |
| 57 | if let (Some(a), Some(b)) = (integer(d), n.as_u64()) { |
| 58 | return a == b as i128; |
| 59 | } |
| 60 | let ours = match d { |
| 61 | Dat::Adec(x) => x.to_string().parse::<f64>().ok(), |
| 62 | Dat::U128(x) => Some(*x as f64), |
| 63 | Dat::Aint(x) => x.to_string().parse::<f64>().ok(), |
| 64 | other => integer(other).map(|i| i as f64), |
| 65 | }; |
| 66 | // Within a few units in the last place, since serde_json's default float reading is fast |
| 67 | // rather than exactly rounded. |
| 68 | match (ours, n.as_f64()) { |
| 69 | (Some(a), Some(b)) => a == b || (a - b).abs() <= 4.0 * f64::EPSILON * a.abs().max(b.abs()), |
| 70 | _ => false, |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | /// JSON documents, each read the same way by `serde_json`, by this reader and by the JDAT text |
| 75 | /// decoder, since valid JSON is a subset of what that decoder reads. |
| 76 | #[test] |
| 77 | fn test_rfc_8259_documents_are_read_as_json_readers_read_them() -> Outcome<()> { |
| 78 | let docs = [ |
| 79 | "{}", "[]", "0", "-0", "1", "-1", "255", "256", "-128", "-129", "65535", "65536", |
| 80 | "4294967296", "9007199254740991", "18446744073709551615", "18446744073709551616", |
| 81 | "-9223372036854775808", "-9223372036854775809", "0.5", "-0.5", "1.25", "1e2", "1E2", |
| 82 | "1e+2", "1e-2", "-1.5E-3", "123.456e7", "0e0", "0.0", "true", "false", "null", |
| 83 | "\"\"", "\"plain\"", "\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"", "\"\\u0041\\u00e9\\u20AC\"", |
| 84 | "\"\\ud83d\\ude00\"", "\"\\u0000\"", "\"caf\u{e9} \u{1f600}\"", "\"\u{7f}\"", |
| 85 | "[1,2,3]", " [ 1 , 2 ] ", "\t\n\r[\n1\n]\n", "{\"a\":1}", "{ \"a\" : [ true , null ] }", |
| 86 | "{\"a\":{\"b\":{\"c\":[[],{}]}}}", "[\"\",0,-0,1e0,{\"\":null}]", |
| 87 | "{\"v\":\"present/1\",\"ts\":1800000000,\"predicates\":[\"adult\"],\"proof\":{\"alg\":\"linkring/1\"}}", |
| 88 | ]; |
| 89 | let jdat = DecoderConfig::<(), ()>::json(None); |
| 90 | for doc in docs { |
| 91 | let ours = match strict(doc) { |
| 92 | Ok(d) => d, |
| 93 | Err(e) => return Err(err!(e, "The JSON document {:?} was refused.", doc; Test)), |
| 94 | }; |
| 95 | let theirs: Value = match serde_json::from_str(doc) { |
| 96 | Ok(v) => v, |
| 97 | Err(e) => return Err(err!("serde_json refused {:?}: {}", doc, e; Test)), |
| 98 | }; |
| 99 | req!(same(&ours, &theirs), true, "{:?} read as {:?}, serde_json as {:?}", doc, ours, theirs); |
| 100 | let text = res!(Dat::decode_string_with_config(doc, &jdat)); |
| 101 | req!(ours, text, "{:?} against the JDAT text decoder", doc); |
| 102 | } |
| 103 | Ok(()) |
| 104 | } |
| 105 | |
| 106 | /// What is not JSON is refused, and `serde_json` refuses each too, so every refusal here is of |
| 107 | /// something that is not JSON rather than of something this reader cannot read. |
| 108 | #[test] |
| 109 | fn test_what_is_not_json_is_refused() { |
| 110 | let bad = [ |
| 111 | // The forms JDAT reads and JSON does not. |
| 112 | "(u64|5)", "{\"ts\":(u64|5)}", "(str|\"x\")", "0x10", "0o7", "0b1", "abc", "'abc'", |
| 113 | "{a:1}", "[1 2]", "1 2", "[1,]", "{\"a\":1,}", "[,]", "[,1]", "1_000", "empty", "none", |
| 114 | "{\"a\":1} x", "{\"a\":1}{\"b\":2}", "!comment! 1", "# 1", |
| 115 | // Numbers. |
| 116 | "-", "+1", "01", "-01", "00", "1.", ".5", "-.5", "1.e2", "1e", "1e+", "1e-", "--1", "0x", |
| 117 | "NaN", "Infinity", "-Infinity", "1.5.5", "1e2e3", |
| 118 | // Strings. |
| 119 | "\"abc", "\"\\x41\"", "\"\\u12\"", "\"\\u12G4\"", "\"\\'\"", "\"a\u{1}b\"", "\"a\nb\"", |
| 120 | "\"a\tb\"", "\"\\ud800\"", "\"\\udc00\"", "\"\\ud800\\u0041\"", "\"\\ud800x\"", |
| 121 | // Structure. |
| 122 | "", " ", "[", "]", "{", "}", "[1", "{\"a\"", "{\"a\":", "{\"a\" 1}", "{\"a\",1}", |
| 123 | "{1:1}", "[1:2]", "{\"a\":1 \"b\":2}", "tru", "nul", "fals", "True", "NULL", |
| 124 | "\u{feff}{}", "\u{a0}1", "[1]\u{0}", |
| 125 | ]; |
| 126 | for doc in bad { |
| 127 | assert!(strict(doc).is_err(), "{:?} is not JSON and was read", doc); |
| 128 | assert!(serde_json::from_str::<Value>(doc).is_err(), |
| 129 | "serde_json reads {:?}, so it does not belong on this list", doc); |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | /// Where RFC 8259 leaves the reader a choice, this one refuses: a member named twice, which |
| 134 | /// `serde_json` resolves by keeping the last. |
| 135 | #[test] |
| 136 | fn test_a_member_named_twice_is_refused() { |
| 137 | assert!(strict("{\"a\":1,\"a\":2}").is_err(), "a member named twice"); |
| 138 | assert!(strict("{\"a\":1,\"b\":{\"a\":1,\"a\":1}}").is_err(), "a member named twice, nested"); |
| 139 | assert!(strict("{\"a\":{\"a\":1}}").is_ok(), "the same name at two levels is two members"); |
| 140 | } |
| 141 | |
| 142 | /// Fuzzed documents: small edits to JSON seeds, each read by both readers, which must agree on |
| 143 | /// whether it is JSON and, where it is, on what it says. The two known differences are skipped: |
| 144 | /// a member named twice, which this reader refuses by choice, and a number beyond the range of |
| 145 | /// an `f64`, which `serde_json` refuses and this reader holds exactly. |
| 146 | #[test] |
| 147 | fn test_a_fuzzed_corpus_agrees_with_an_independent_reader() -> Outcome<()> { |
| 148 | let seeds = [ |
| 149 | "{\"v\":\"present/1\",\"mode\":\"named\",\"ts\":1800000000,\"predicates\":[\"adult\"],\ |
| 150 | \"proof\":{\"alg\":\"linkring/1\",\"body\":\"AAEC\"},\"prev\":null,\"live\":true}", |
| 151 | "[0,-1,2.5,-3e4,5E-6,\"\\u00e9\\n\\\"\",[],{},[{\"a\":[1,[2,[3]]]}],false,null]", |
| 152 | "{\"s\":\"caf\u{e9} \\ud83d\\ude00 \\/ \\\\ \\t\",\"n\":-0.0,\"big\":18446744073709551616}", |
| 153 | ]; |
| 154 | let alphabet: Vec<char> = "{}[],:\"\\/bfnrtu019-+.eEx a\t\n'(|)_\u{1}\u{e9}".chars().collect(); |
| 155 | let mut state: u64 = 0x2545_f491_4f6c_dd1d; |
| 156 | let mut next = |n: usize| -> usize { |
| 157 | state = state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1_442_695_040_888_963_407); |
| 158 | ((state >> 33) as usize) % n |
| 159 | }; |
| 160 | let (mut read, mut refused) = (0usize, 0usize); |
| 161 | for seed in seeds { |
| 162 | for _ in 0..4_000 { |
| 163 | let mut doc: Vec<char> = seed.chars().collect(); |
| 164 | for _ in 0..(1 + next(3)) { |
| 165 | let at = next(doc.len() + 1); |
| 166 | match next(3) { |
| 167 | 0 => doc.insert(at, alphabet[next(alphabet.len())]), |
| 168 | 1 if at < doc.len() => { doc.remove(at); }, |
| 169 | _ if at < doc.len() => doc[at] = alphabet[next(alphabet.len())], |
| 170 | _ => doc.push(alphabet[next(alphabet.len())]), |
| 171 | } |
| 172 | } |
| 173 | let doc: String = doc.into_iter().collect(); |
| 174 | let ours = strict(&doc); |
| 175 | let theirs = serde_json::from_str::<Value>(&doc); |
| 176 | match (&ours, &theirs) { |
| 177 | (Ok(d), Ok(v)) => { |
| 178 | read += 1; |
| 179 | req!(same(d, v), true, "{:?} read as {:?}, serde_json as {:?}", doc, d, v); |
| 180 | }, |
| 181 | (Err(_), Err(_)) => refused += 1, |
| 182 | (Err(e), Ok(_)) if fmt!("{}", e).contains("a second time") => refused += 1, |
| 183 | (Ok(_), Err(e)) if fmt!("{}", e).contains("out of range") => read += 1, |
| 184 | (Ok(d), Err(e)) => return Err(err!( |
| 185 | "{:?} was read as {:?}, and serde_json refuses it: {}", doc, d, e; Test)), |
| 186 | (Err(e), Ok(v)) => return Err(err!( |
| 187 | "{:?} was refused, and serde_json reads it as {:?}: {}", doc, v, e; Test)), |
| 188 | } |
| 189 | } |
| 190 | } |
| 191 | // Both verdicts must be common, or the corpus proves little. |
| 192 | req!(read > 1_000 && refused > 1_000, true, "{} read and {} refused", read, refused); |
| 193 | Ok(()) |
| 194 | } |