///|
/// Validate invariants of a manually constructed JsonValue tree. Parsed input
/// already satisfies these checks, but the API protects callers constructing
/// values from another decoder or an in-memory transformation.
pub enum TreeIssueKind {
DuplicateKey
InvalidNumber
DepthLimit
}
///|
pub(all) struct TreeIssue {
path : String
kind : TreeIssueKind
detail : String
}
///|
pub fn TreeIssueKind::to_string(self : TreeIssueKind) -> String {
match self {
DuplicateKey => "duplicate-key"
InvalidNumber => "invalid-number"
DepthLimit => "depth-limit"
}
}
///|
pub fn validate_tree(value : JsonValue, max_depth : Int) -> Array[TreeIssue] {
let issues : Array[TreeIssue] = []
validate_tree_at(value, "", 0, max_depth, issues)
issues
}
///|
pub fn TreeIssue::to_text(self : TreeIssue) -> String {
self.kind.to_string() + " " + self.path + ": " + self.detail
}
///|
fn validate_tree_at(
value : JsonValue,
path : String,
depth : Int,
max_depth : Int,
issues : Array[TreeIssue],
) -> Unit {
if depth > max_depth {
issues.push({ path, kind: DepthLimit, detail: max_depth.to_string() })
return
}
match value {
Number(number) =>
match canonical_number(number) {
Ok(_) => ()
Err(error) =>
issues.push({ path, kind: InvalidNumber, detail: error.message() })
}
Array(values) => {
let mut index = 0
while index < values.length() {
validate_tree_at(
values[index],
path + "/" + index.to_string(),
depth + 1,
max_depth,
issues,
)
index = index + 1
}
}
Object(entries) => {
let seen : Array[String] = []
for entry in entries {
if contains_string(seen, entry.0) {
issues.push({
path: path + "/" + entry.0,
kind: DuplicateKey,
detail: entry.0,
})
} else {
seen.push(entry.0)
}
validate_tree_at(
entry.1,
path + "/" + entry.0,
depth + 1,
max_depth,
issues,
)
}
}
_ => ()
}
}