///|
/// How a caller should respond when valid JSON is not already canonical.
pub enum CanonicalPolicy {
RequireCanonical
Normalize
ReportOnly
}
///|
pub fn policy_require() -> CanonicalPolicy {
CanonicalPolicy::RequireCanonical
}
///|
pub fn policy_normalize() -> CanonicalPolicy {
CanonicalPolicy::Normalize
}
///|
pub fn policy_report() -> CanonicalPolicy {
CanonicalPolicy::ReportOnly
}
///|
pub fn CanonicalPolicy::to_string(self : CanonicalPolicy) -> String {
match self {
RequireCanonical => "require-canonical"
Normalize => "normalize"
ReportOnly => "report-only"
}
}
///|
pub fn CanonicalPolicy::to_json(self : CanonicalPolicy) -> String {
canonical_string(self.to_string())
}
///|
pub(all) struct PolicyDecision {
accepted : Bool
canonical : String?
digest : String?
issues : Array[CanonicalIssue]
error : String?
}
///|
/// Evaluate syntax, resource limits, canonical form, and semantic issues as a
/// single policy decision suitable for a CI gate.
pub fn evaluate_policy(
input : String,
limits : DocumentLimits,
policy : CanonicalPolicy,
) -> PolicyDecision {
let value = match parse_with_limits(input, limits) {
Ok(value) => value
Err(error) =>
return {
accepted: false,
canonical: None,
digest: None,
issues: [],
error: Some(error),
}
}
let canonical = match canonicalize(value) {
Ok(canonical) => canonical
Err(error) =>
return {
accepted: false,
canonical: None,
digest: None,
issues: [],
error: Some(error.message()),
}
}
let issues = canonical_issues(value)
let digest = match canonical_digest(value) {
Ok(digest) => digest
Err(error) =>
return {
accepted: false,
canonical: Some(canonical),
digest: None,
issues,
error: Some(error.message()),
}
}
let exact = check_canonical(input).canonical
let accepted = match policy {
RequireCanonical => exact
Normalize | ReportOnly => true
}
{
accepted,
canonical: Some(canonical),
digest: Some(digest),
issues,
error: if accepted || policy is ReportOnly {
None
} else {
Some("document is valid JSON but not in canonical form")
},
}
}
///|
pub fn enforce_canonical(
input : String,
limits : DocumentLimits,
) -> Result[String, String] {
let decision = evaluate_policy(input, limits, RequireCanonical)
if !decision.accepted {
return Err(
match decision.error {
Some(error) => error
None => "canonical policy rejected document"
},
)
}
match decision.canonical {
Some(canonical) => Ok(canonical)
None => Err("canonical policy produced no output")
}
}
///|
pub fn PolicyDecision::to_text(self : PolicyDecision) -> String {
let mut output = "accepted: " +
(if self.accepted { "true" } else { "false" }) +
"\n"
match self.canonical {
Some(canonical) => output = output + "canonical: " + canonical + "\n"
None => ()
}
match self.digest {
Some(digest) => output = output + "sha256: " + digest + "\n"
None => ()
}
if self.issues.length() > 0 {
output = output + "issues: " + self.issues.length().to_string() + "\n"
}
match self.error {
Some(error) => output = output + "error: " + error + "\n"
None => ()
}
output
}