///|
pub fn parse(source : String) -> Result[RuleDocument, RuleParseError] {
  parse_rule_document(@json.parse(source)) catch {
    error => Err(ExpectedObject("invalid JSON: \{error}"))
  }
}

///|
pub fn check(
  rule : RuleDocument,
  budget? : Budget = Budget::default(),
) -> Array[Diagnostic] {
  check_expr(rule.condition, budget)
}

///|
fn diagnostics_message(diagnostics : Array[Diagnostic]) -> String {
  let out = StringBuilder()
  for index, diagnostic in diagnostics {
    if index > 0 {
      out.write_string("; ")
    }
    out.write_string(
      "\{diagnostic.code} at \{diagnostic.rule_path}: \{diagnostic.message}",
    )
  }
  out.to_string()
}

///|
pub fn evaluate_json(
  rule_source : String,
  data_source : String,
  budget? : Budget = Budget::default(),
  trace_mode? : TraceMode = Full,
) -> Result[EvaluationReport, String] {
  let rule = match parse(rule_source) {
    Ok(value) => value
    Err(error) => return Err("rule parse failed: \{error.message()}")
  }
  let diagnostics = check(rule, budget~)
  if diagnostics.length() > 0 {
    return Err("rule check failed: \{diagnostics_message(diagnostics)}")
  }
  let data = @json.parse(data_source) catch {
    error => return Err("invalid data JSON: \{error}")
  }
  Ok(evaluate(rule, data, budget, trace_mode~))
}