Oregami
Repositories/oxedyne/fe2o3

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
5use oxedyne_fe2o3_core::prelude::*;
6use oxedyne_fe2o3_jdat::{
7 prelude::*,
8 bdat::DecodeLimits,
9 string::dec::DecoderConfig,
10};
11
12use serde_json::Value;
13
14
15fn 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.
21fn 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
38fn 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
53fn 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]
77fn 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]
109fn 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]
136fn 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]
147fn 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}