///|
/// Result of checking whether a JSON document is already in JCS form.
pub(all) struct CanonicalCheck {
valid_json : Bool
canonical : Bool
normalized : String?
reason : String?
}
///|
/// Check syntax and compare the source byte-for-byte with its canonical form.
/// This deliberately reports non-canonical but valid JSON separately from
/// malformed JSON so formatters and CI can choose different policies.
pub fn check_canonical(input : String) -> CanonicalCheck {
let value = match parse(input) {
Ok(value) => value
Err(error) =>
return {
valid_json: false,
canonical: false,
normalized: None,
reason: Some(error.message()),
}
}
let normalized = match canonicalize(value) {
Ok(serialized) => serialized
Err(error) =>
return {
valid_json: true,
canonical: false,
normalized: None,
reason: Some(error.message()),
}
}
if normalized == input {
{
valid_json: true,
canonical: true,
normalized: Some(normalized),
reason: None,
}
} else {
{
valid_json: true,
canonical: false,
normalized: Some(normalized),
reason: Some("source differs from its RFC 8785 canonical form"),
}
}
}
///|
pub fn is_canonical_json(input : String) -> Bool {
check_canonical(input).canonical
}
///|
/// Canonicalize a complete JSON document in one call.
pub fn canonicalize_json(input : String) -> Result[String, String] {
let check = check_canonical(input)
if !check.valid_json {
return Err(
match check.reason {
Some(reason) => reason
None => "invalid JSON"
},
)
}
match check.normalized {
Some(normalized) => Ok(normalized)
None =>
Err(
match check.reason {
Some(reason) => reason
None => "canonicalization failed"
},
)
}
}
///|
/// Canonicalize untrusted input only after enforcing the caller's resource
/// budget. The parsed tree is reused, so the input is not parsed twice.
pub fn canonicalize_json_with_limits(
input : String,
limits : DocumentLimits,
) -> Result[String, String] {
let value = match parse_with_limits(input, limits) {
Ok(value) => value
Err(error) => return Err(error)
}
match canonicalize(value) {
Ok(canonical) => Ok(canonical)
Err(error) => Err(error.message())
}
}