///|
/// Deterministic top-level serializer: RFC members first, extensions sorted.
pub fn serialize_problem(problem : ProblemDetails) -> String {
let fields : Array[String] = []
match problem.type_raw() {
Some(value) => fields.push("\"type\":\{json_quote(value)}")
None => ()
}
match problem.title() {
Some(value) => fields.push("\"title\":\{json_quote(value)}")
None => ()
}
match problem.status() {
Some(value) => fields.push("\"status\":\{value}")
None => ()
}
match problem.detail() {
Some(value) => fields.push("\"detail\":\{json_quote(value)}")
None => ()
}
match problem.instance() {
Some(value) => fields.push("\"instance\":\{json_quote(value)}")
None => ()
}
let extensions = problem.extensions()
extensions.sort_by(fn(a, b) { lexical_compare(a.name(), b.name()) })
for extension in extensions {
fields.push(
"\{json_quote(extension.name())}:\{json_value_string(extension.value())}",
)
}
let body = join_strings(fields, ",")
"{\{body}}"
}
///|
pub fn canonicalize_problem_json(
input : String,
) -> Result[String, ProblemError] {
match parse_problem(input) {
Ok(problem) => Ok(serialize_problem(problem))
Err(error) => Err(error)
}
}
///|
fn join_strings(values : Array[String], separator : String) -> String {
let builder = StringBuilder()
for i = 0; i < values.length(); i = i + 1 {
if i > 0 {
builder.write_string(separator)
}
builder.write_string(values[i])
}
builder.to_string()
}