///|
/// The importance assigned to a rule.
pub(all) enum Severity {
Info
Warning
Error
} derive(Eq, Debug, ToJson, FromJson)
///|
/// Serializable source configuration for one rule.
pub(all) struct RuleDefinition {
name : String
expression : String
message : String
severity : Severity
} derive(Eq, Debug, ToJson, FromJson)
///|
pub fn RuleDefinition::new(
name : String,
expression : String,
message : String,
severity? : Severity = Error,
) -> RuleDefinition {
{ name, expression, message, severity }
}
///|
pub struct CompiledRule {
definition : RuleDefinition
program : Program
}
///|
/// A collection of rules compiled once and reusable across many JSON values.
pub struct RuleSet {
rules : Array[CompiledRule]
}
///|
pub(all) enum OutcomeStatus {
Passed
Failed
EvaluationError
} derive(Eq, Debug, ToJson, FromJson)
///|
pub(all) struct RuleOutcome {
name : String
status : OutcomeStatus
message : String
severity : Severity
diagnostic : Diagnostic?
} derive(Eq, Debug, ToJson, FromJson)
///|
pub(all) struct RuleReport {
passed : Bool
passed_count : Int
failed_count : Int
error_count : Int
outcomes : Array[RuleOutcome]
} derive(Eq, Debug, ToJson, FromJson)
///|
pub fn RuleReport::to_json_string(
self : RuleReport,
indent? : Int = 2,
) -> String {
self.to_json().stringify(indent~)
}
///|
pub fn RuleSet::compile(
definitions : Array[RuleDefinition],
) -> Result[RuleSet, Array[Diagnostic]] {
RuleSet::compile_with_limits(definitions, RuleSetLimits::default())
}
///|
/// Decode and compile a JSON array of rule definitions.
pub fn RuleSet::from_json(config : Json) -> Result[RuleSet, Array[Diagnostic]] {
RuleSet::from_json_with_limits(config, RuleSetLimits::default())
}
///|
pub fn RuleSet::length(self : RuleSet) -> Int {
self.rules.length()
}
///|
/// Evaluate every rule. Runtime errors are recorded per rule so one malformed
/// input path does not hide the rest of the report.
pub fn RuleSet::evaluate(self : RuleSet, context : Json) -> RuleReport {
let outcomes = []
let mut passed_count = 0
let mut failed_count = 0
let mut error_count = 0
for rule in self.rules {
match evaluate(rule.program, context) {
Ok(True) => {
passed_count = passed_count + 1
outcomes.push({
name: rule.definition.name,
status: Passed,
message: rule.definition.message,
severity: rule.definition.severity,
diagnostic: None,
})
}
Ok(False) => {
failed_count = failed_count + 1
outcomes.push({
name: rule.definition.name,
status: Failed,
message: rule.definition.message,
severity: rule.definition.severity,
diagnostic: None,
})
}
Ok(other) => {
error_count = error_count + 1
let diagnostic = Diagnostic::new(
Evaluate,
"E022",
"rule returned \{value_kind(other)} instead of boolean",
rule.program.span(),
)
outcomes.push({
name: rule.definition.name,
status: EvaluationError,
message: rule.definition.message,
severity: rule.definition.severity,
diagnostic: Some(diagnostic),
})
}
Err(diagnostic) => {
error_count = error_count + 1
outcomes.push({
name: rule.definition.name,
status: EvaluationError,
message: rule.definition.message,
severity: rule.definition.severity,
diagnostic: Some(diagnostic),
})
}
}
}
{
passed: failed_count == 0 && error_count == 0,
passed_count,
failed_count,
error_count,
outcomes,
}
}