///|
/// Render a machine-readable policy decision for CI systems.
pub fn PolicyResult::to_json(self : PolicyResult) -> String {
  let violations = self.violations.map(violation => {
    "{" +
    "\"code\":" +
    json_string(violation.code) +
    "," +
    "\"path\":" +
    json_string(violation.path) +
    "," +
    "\"message\":" +
    json_string(violation.message) +
    "," +
    "\"relatedChange\":" +
    optional_json_string(violation.related_change) +
    "}"
  })
  let allowed = self.allowed_breaking.map(change_reference_json)
  let unallowed = self.unallowed_breaking.map(change_reference_json)
  "{" +
  "\"schemaVersion\":\"1.0\"," +
  "\"kind\":\"evowitness-policy-result\"," +
  "\"policy\":" +
  json_string(self.policy_name) +
  "," +
  "\"passed\":" +
  bool_json(self.passed()) +
  "," +
  "\"summary\":{" +
  "\"violations\":" +
  self.violations.length().to_string() +
  "," +
  "\"allowedBreaking\":" +
  self.allowed_breaking.length().to_string() +
  "," +
  "\"unallowedBreaking\":" +
  self.unallowed_breaking.length().to_string() +
  "}," +
  "\"violations\":[" +
  violations.join(",") +
  "]," +
  "\"allowedBreaking\":[" +
  allowed.join(",") +
  "]," +
  "\"unallowedBreaking\":[" +
  unallowed.join(",") +
  "]}"
}

///|
/// Render a migration plan for automation, ticket generation or dashboards.
pub fn MigrationPlan::to_json(self : MigrationPlan) -> String {
  let steps = self.steps.map(step => {
    let codes = step.related_codes.map(json_string)
    "{" +
    "\"id\":" +
    json_string(step.id) +
    "," +
    "\"phase\":" +
    json_string(step.phase.render()) +
    "," +
    "\"path\":" +
    json_string(step.path) +
    "," +
    "\"action\":" +
    json_string(step.action) +
    "," +
    "\"verification\":" +
    json_string(step.verification) +
    "," +
    "\"relatedCodes\":[" +
    codes.join(",") +
    "]}"
  })
  "{" +
  "\"schemaVersion\":\"1.0\"," +
  "\"kind\":\"evowitness-migration-plan\"," +
  "\"contract\":" +
  json_string(self.contract_name) +
  "," +
  "\"fromVersion\":" +
  json_string(self.from_version) +
  "," +
  "\"toVersion\":" +
  json_string(self.to_version) +
  "," +
  "\"risk\":" +
  json_string(self.risk) +
  "," +
  "\"steps\":[" +
  steps.join(",") +
  "]}"
}

///|
fn change_reference_json(change : Change) -> String {
  "{" +
  "\"code\":" +
  json_string(change.code) +
  "," +
  "\"direction\":" +
  json_string(change.direction) +
  "," +
  "\"path\":" +
  json_string(change.path) +
  "}"
}

///|
fn optional_json_string(value : String?) -> String {
  match value {
    Some(text) => json_string(text)
    None => "null"
  }
}

///|
fn bool_json(value : Bool) -> String {
  if value {
    "true"
  } else {
    "false"
  }
}