///|
fn show_value(value : Json?) -> String {
match value {
None => ""
Some(v) => canonical_unchecked(v)
}
}
///|
fn kind_label(kind : DifferenceKind) -> String {
match kind {
MissingCase => "missing case"
ExtraCase => "extra case"
ValueMismatch => "value mismatch"
TypeMismatch => "type mismatch"
MissingField => "missing field"
ExtraField => "extra field"
}
}
///|
/// Human-readable summary. Values and IDs are JSON escaped, keeping each diagnostic on one line.
pub fn Report::render(
self : Report,
max_differences? : Int = 20,
) -> String raise CaseError {
if max_differences < 0 {
raise Invalid("max_differences must be nonnegative")
}
let status = if self.is_match() { "MATCH" } else { "DIFFERENT" }
let lines = [
"\{status} \{self.suite.to_json().stringify()}: \{self.matched_cases}/\{self.total_cases} cases match; \{self.differences.length()} differences",
]
if self.abs_tol > 0.0 || self.rel_tol > 0.0 {
lines.push(
"numeric tolerance: absolute=\{self.abs_tol}, relative=\{self.rel_tol}",
)
}
for i, diff in self.differences {
if i >= max_differences {
break
}
lines.push(
" \{diff.case_id.to_json().stringify()} at \{diff.path.to_json().stringify()}: \{kind_label(diff.kind)}",
)
lines.push(" expected: \{show_value(diff.expected)}")
lines.push(" actual: \{show_value(diff.actual)}")
}
if self.differences.length() > max_differences {
lines.push(
" ... \{self.differences.length() - max_differences} more differences",
)
}
lines.join("\n")
}
///|
/// Machine-readable report with stable field ordering.
pub fn Report::encode(self : Report) -> String raise CaseError {
canonical(self.to_json())
}