266 lines
9.5 KiB
Rust
266 lines
9.5 KiB
Rust
mod support;
|
|
|
|
fn source_text(value: &str) -> String {
|
|
format!("{value:?}").replace("\\0", "\\u{0}")
|
|
}
|
|
|
|
const ROUNDTRIP: &str = r#"
|
|
roundtrip(source: Text) (result: Text) {
|
|
result = parsed(result(dever.json.parse(source)))
|
|
}
|
|
parsed(result: dever.json.value.ParseResult.Parsed(document)) (text: Text) recover("test observes parsed failures in its assertions") {
|
|
text = written(result(dever.json.stringify(document)))
|
|
}
|
|
parsed(result: dever.json.value.ParseResult.Failed(position, message)) (text: Text) recover("test observes parsed failures in its assertions") {
|
|
text = "ERROR " + int.to_text(position) + ": " + message
|
|
}
|
|
written(result: dever.json.value.WriteResult.Written(value)) (text: Text) recover("test observes written failures in its assertions") { text = value }
|
|
written(result: dever.json.value.WriteResult.Failed(node, message)) (text: Text) recover("test observes written failures in its assertions") {
|
|
text = "INVALID " + int.to_text(node) + ": " + message
|
|
}
|
|
show(source: Text) () { dever.io.println(roundtrip(source)) }
|
|
"#;
|
|
|
|
fn roundtrips(values: &[&str]) -> Vec<String> {
|
|
let literals = values
|
|
.iter()
|
|
.map(|value| source_text(value))
|
|
.collect::<Vec<_>>()
|
|
.join(", ");
|
|
let source = format!("{ROUNDTRIP}\npublic main() () {{each(show, [{literals}])}}\n");
|
|
support::stdout(&source)
|
|
.lines()
|
|
.map(str::to_owned)
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn json_sources_pass_the_same_checker_as_application_packages() {
|
|
support::checked(&support::sources("public main() () {}\n"));
|
|
}
|
|
|
|
#[test]
|
|
fn json_values_unicode_and_exact_numbers_roundtrip_natively() {
|
|
let cases = [
|
|
("null", "null"),
|
|
(" \t\r\ntrue\n", "true"),
|
|
("false", "false"),
|
|
("-0", "-0"),
|
|
("1.2300e+40000", "1.2300e+40000"),
|
|
(
|
|
"12345678901234567890123456789012345678901234567890",
|
|
"12345678901234567890123456789012345678901234567890",
|
|
),
|
|
(
|
|
r#""\"\\\/\b\f\n\r\t\u0000\u001f""#,
|
|
r#""\"\\/\b\f\n\r\t\u0000\u001f""#,
|
|
),
|
|
(r#""\uD834\uDD1E\ud83d\ude00中文""#, "\"𝄞😀中文\""),
|
|
(
|
|
r#" { "z": [ 1, null, { "empty": [] } ], "a": {}, "": "value" } "#,
|
|
r#"{"z":[1,null,{"empty":[]}],"a":{},"":"value"}"#,
|
|
),
|
|
(
|
|
"[[],{},true,false,null,0,-12.5E-7]",
|
|
"[[],{},true,false,null,0,-12.5E-7]",
|
|
),
|
|
];
|
|
let output = roundtrips(&cases.iter().map(|(source, _)| *source).collect::<Vec<_>>());
|
|
assert_eq!(
|
|
output,
|
|
cases
|
|
.iter()
|
|
.map(|(_, expected)| *expected)
|
|
.collect::<Vec<_>>()
|
|
);
|
|
let second = roundtrips(&output.iter().map(String::as_str).collect::<Vec<_>>());
|
|
assert_eq!(second, output);
|
|
}
|
|
|
|
#[test]
|
|
fn malformed_json_is_rejected_without_default_values() {
|
|
let cases = [
|
|
"",
|
|
" ",
|
|
"+1",
|
|
"01",
|
|
"-01",
|
|
"-",
|
|
".1",
|
|
"1.",
|
|
"1e",
|
|
"1e+",
|
|
"NaN",
|
|
"Infinity",
|
|
"tru",
|
|
"True",
|
|
"nul",
|
|
"true false",
|
|
"nullx",
|
|
"[]x",
|
|
"[",
|
|
"{",
|
|
"[1,]",
|
|
"[,1]",
|
|
"[1 2]",
|
|
"{,}",
|
|
"{a:1}",
|
|
"{\"a\"}",
|
|
"{\"a\":}",
|
|
"{\"a\":1,}",
|
|
"{\"a\":1 \"b\":2}",
|
|
"[}",
|
|
"\"",
|
|
"\"\\",
|
|
"\"line\nbreak\"",
|
|
"\"\0\"",
|
|
r#""\x20""#,
|
|
r#""\u12""#,
|
|
r#""\uXY00""#,
|
|
r#""\ud800""#,
|
|
r#""\udc00""#,
|
|
r#""\ud800x""#,
|
|
r#""\ud800\n""#,
|
|
r#""\ud800\u0041""#,
|
|
r#"{"a":1,"a":2}"#,
|
|
r#"{"a":1,"\u0061":2}"#,
|
|
"\u{feff}null",
|
|
"[1\u{a0},2]",
|
|
];
|
|
let output = roundtrips(&cases);
|
|
for (source, result) in cases.iter().zip(&output) {
|
|
assert!(
|
|
result.starts_with("ERROR "),
|
|
"{source:?} unexpectedly produced {result:?}"
|
|
);
|
|
}
|
|
assert!(output[output.len() - 4].contains("duplicate JSON object name"));
|
|
assert!(output[output.len() - 3].contains("duplicate JSON object name"));
|
|
}
|
|
|
|
#[test]
|
|
fn nesting_limit_and_unicode_error_offsets_are_explicit() {
|
|
let allowed = format!("{}0{}", "[".repeat(256), "]".repeat(256));
|
|
let exceeded = format!("{}0{}", "[".repeat(257), "]".repeat(257));
|
|
let output = roundtrips(&[&allowed, &exceeded, "[\"😀\",]", "{\"a\":"]);
|
|
assert_eq!(output[0], allowed);
|
|
assert_eq!(output[1], "ERROR 256: JSON nesting exceeds 256 containers");
|
|
assert_eq!(output[2], "ERROR 5: expected a JSON value");
|
|
assert_eq!(output[3], "ERROR 5: unterminated JSON container");
|
|
}
|
|
|
|
#[test]
|
|
fn constructed_documents_are_validated_and_can_use_any_map_insertion_order() {
|
|
let source = format!(
|
|
r#"{ROUNDTRIP}
|
|
show_document(document: dever.json.value.Document) () {{
|
|
dever.io.println(written(result(dever.json.stringify(document))))
|
|
}}
|
|
public main() () {{
|
|
show_document(dever.json.value.Document {{root = 1
|
|
nodes = {{1 = dever.json.value.Value.Array([0])
|
|
0 = dever.json.value.Value.String("\"\\\n\u{{1}}😀")}}}})
|
|
show_document(dever.json.value.Document {{root = 0
|
|
nodes = {{0 = dever.json.value.Value.Array([0])}}}})
|
|
show_document(dever.json.value.Document {{root = 0
|
|
nodes = {{0 = dever.json.value.Value.Array([-1])}}}})
|
|
show_document(dever.json.value.Document {{root = 0
|
|
nodes = {{0 = dever.json.value.Value.Array([1])
|
|
1 = dever.json.value.Value.Null}}}})
|
|
show_document(dever.json.value.Document {{root = 3
|
|
nodes = {{3 = dever.json.value.Value.Null}}}})
|
|
show_document(dever.json.value.Document {{root = -1
|
|
nodes = {{0 = dever.json.value.Value.Null}}}})
|
|
show_document(dever.json.value.Document {{root = 0
|
|
nodes = {{0 = dever.json.value.Value.Number("01")}}}})
|
|
show_document(dever.json.value.Document {{root = 0
|
|
nodes = {{}}}})
|
|
}}
|
|
"#
|
|
);
|
|
let output = support::stdout(&source);
|
|
let rows = output.lines().collect::<Vec<_>>();
|
|
assert_eq!(rows[0], r#"["\"\\\n\u0001😀"]"#);
|
|
assert_eq!(
|
|
rows[1],
|
|
"INVALID 0: JSON child references must precede their parent"
|
|
);
|
|
assert_eq!(rows[2], rows[1]);
|
|
assert_eq!(rows[3], rows[1]);
|
|
assert_eq!(
|
|
rows[4],
|
|
"INVALID 0: JSON node IDs must be consecutive from zero"
|
|
);
|
|
assert_eq!(
|
|
rows[5],
|
|
"INVALID -1: JSON root does not refer to a document node"
|
|
);
|
|
assert_eq!(rows[6], "INVALID 0: invalid JSON number spelling");
|
|
assert_eq!(
|
|
rows[7],
|
|
"INVALID 0: JSON root does not refer to a document node"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parsed_document_accessors_preserve_order_and_distinguish_json_null_from_missing() {
|
|
let output = support::stdout(
|
|
r#"inspect(parsed: dever.json.value.ParseResult.Parsed(document)) (result: Bool) recover("test observes inspect failures in its assertions") {
|
|
array = dever.json.property(document, document.root, "values")
|
|
result = inspect_array(array, document)
|
|
}
|
|
inspect(parsed: other) (result: Bool) recover("test observes inspect failures in its assertions") { result = false }
|
|
inspect_array(id: Int, document: dever.json.value.Document) (result: Bool) {
|
|
first = dever.json.element(document, id, 0)
|
|
second = dever.json.element(document, id, 1)
|
|
missing = dever.json.element(document, id, 2)
|
|
negative = dever.json.element(document, id, -1)
|
|
result = equals(first, 0) and equals(second, 1) and missing == null and negative == null and is_null(dever.json.node(document, 0)) and dever.json.node(document, 99) == null and dever.json.property(document, document.root, "missing") == null and dever.json.element(document, document.root, 0) == null
|
|
}
|
|
inspect_array(id: null, document: dever.json.value.Document) (result: Bool) { result = false }
|
|
equals(id: Int, expected: Int) (result: Bool) { result = id == expected }
|
|
equals(id: null, expected: Int) (result: Bool) { result = false }
|
|
is_null(value: dever.json.value.Value.Null) (result: Bool) { result = true }
|
|
is_null(value: null) (result: Bool) { result = false }
|
|
is_null(value: other) (result: Bool) { result = false }
|
|
public main() (valid: Bool) { valid = inspect(result(dever.json.parse("{\"values\":[null,123]}"))) }
|
|
"#,
|
|
);
|
|
assert_eq!(output, "valid = true\n");
|
|
}
|
|
|
|
#[test]
|
|
fn constructed_graph_limits_reject_depth_and_exponential_expansion() {
|
|
let nested = std::iter::repeat_n("0", 257).collect::<Vec<_>>().join(",");
|
|
let doubled = std::iter::repeat_n("0", 23).collect::<Vec<_>>().join(",");
|
|
let source = format!(
|
|
r#"{ROUNDTRIP}
|
|
type Growth {{document: dever.json.value.Document
|
|
duplicate: Bool}}
|
|
seed(duplicate: Bool) (result: Growth) {{
|
|
result = Growth {{document = dever.json.value.Document {{root = 0
|
|
nodes = {{0 = dever.json.value.Value.String("a")}}}}
|
|
duplicate = duplicate}}
|
|
}}
|
|
grow(unused: Int, state: Growth) (next: Growth) {{
|
|
id = length(state.document.nodes)
|
|
document = state.document
|
|
document.nodes = put(document.nodes, id, dever.json.value.Value.Array(children(state.duplicate, id - 1)))
|
|
document.root = id
|
|
next = state
|
|
next.document = document
|
|
}}
|
|
children(duplicate: true, previous: Int) (result: List<Int>) {{result = [previous, previous]}}
|
|
children(duplicate: false, previous: Int) (result: List<Int>) {{result = [previous]}}
|
|
public main() (deep: Text, expanded: Text) {{
|
|
deep = written(result(dever.json.stringify(reduce(grow, [{nested}], seed(false)).document)))
|
|
expanded = written(result(dever.json.stringify(reduce(grow, [{doubled}], seed(true)).document)))
|
|
}}
|
|
"#
|
|
);
|
|
assert_eq!(
|
|
support::stdout(&source),
|
|
"deep = INVALID 257: JSON nesting exceeds 256 containers\nexpanded = INVALID 22: JSON output exceeds 16777216 Unicode scalars\n"
|
|
);
|
|
}
|