///|
/// Minimum severity that makes a failed rule reject an input.
pub(all) enum FailureThreshold {
AnyFailure
WarningOrHigher
ErrorOnly
} derive(Eq, Debug, ToJson, FromJson)
///|
/// Controls severity handling and early termination for a rule-set evaluation.
pub(all) struct RuleEvaluationOptions {
failure_threshold : FailureThreshold
stop_on_blocking_failure : Bool
stop_on_evaluation_error : Bool
} derive(Eq, Debug, ToJson, FromJson)
///|
pub fn RuleEvaluationOptions::default() -> RuleEvaluationOptions {
{
failure_threshold: AnyFailure,
stop_on_blocking_failure: false,
stop_on_evaluation_error: false,
}
}
///|
/// Result of evaluating a rule set with an explicit acceptance policy.
pub(all) struct PolicyReport {
passed : Bool
evaluated_count : Int
passed_count : Int
failed_count : Int
blocking_count : Int
advisory_count : Int
error_count : Int
stopped_early : Bool
outcomes : Array[RuleOutcome]
} derive(Eq, Debug, ToJson, FromJson)
///|
pub fn PolicyReport::to_json_string(
self : PolicyReport,
indent? : Int = 2,
) -> String {
self.to_json().stringify(indent~)
}
///|
fn severity_is_blocking(
threshold : FailureThreshold,
severity : Severity,
) -> Bool {
match threshold {
AnyFailure => true
WarningOrHigher => severity == Warning || severity == Error
ErrorOnly => severity == Error
}
}
///|
fn boolean_result_diagnostic(rule : CompiledRule, value : Json) -> Diagnostic {
Diagnostic::new(
Evaluate,
"E022",
"rule returned \{value_kind(value)} instead of boolean",
rule.program.span(),
hint="Finish the expression with a comparison or boolean operator.",
)
}
///|
/// Evaluate rules using a configurable failure threshold.
///
/// Informational or warning outcomes remain visible even when the selected
/// policy treats them as advisory. Runtime errors always reject the input.
pub fn RuleSet::evaluate_with_options(
self : RuleSet,
context : Json,
options : RuleEvaluationOptions,
) -> PolicyReport {
let outcomes = []
let mut passed_count = 0
let mut failed_count = 0
let mut blocking_count = 0
let mut advisory_count = 0
let mut error_count = 0
let mut stopped_early = false
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
let blocking = severity_is_blocking(
options.failure_threshold,
rule.definition.severity,
)
if blocking {
blocking_count = blocking_count + 1
} else {
advisory_count = advisory_count + 1
}
outcomes.push({
name: rule.definition.name,
status: Failed,
message: rule.definition.message,
severity: rule.definition.severity,
diagnostic: None,
})
if blocking && options.stop_on_blocking_failure {
stopped_early = true
break
}
}
Ok(other) => {
error_count = error_count + 1
outcomes.push({
name: rule.definition.name,
status: EvaluationError,
message: rule.definition.message,
severity: rule.definition.severity,
diagnostic: Some(boolean_result_diagnostic(rule, other)),
})
if options.stop_on_evaluation_error {
stopped_early = true
break
}
}
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),
})
if options.stop_on_evaluation_error {
stopped_early = true
break
}
}
}
}
{
passed: blocking_count == 0 && error_count == 0,
evaluated_count: outcomes.length(),
passed_count,
failed_count,
blocking_count,
advisory_count,
error_count,
stopped_early,
outcomes,
}
}
///|
/// Options for applying the same compiled rules to several JSON records.
pub(all) struct BatchOptions {
rule_options : RuleEvaluationOptions
stop_on_first_rejected_record : Bool
} derive(Eq, Debug, ToJson, FromJson)
///|
pub fn BatchOptions::default() -> BatchOptions {
{
rule_options: RuleEvaluationOptions::default(),
stop_on_first_rejected_record: false,
}
}
///|
/// Result for one zero-based input record.
pub(all) struct BatchItemReport {
index : Int
passed : Bool
report : PolicyReport
} derive(Eq, Debug, ToJson, FromJson)
///|
/// Aggregate result for a batch validation operation.
pub(all) struct BatchReport {
passed : Bool
requested_count : Int
evaluated_count : Int
accepted_count : Int
rejected_count : Int
stopped_early : Bool
items : Array[BatchItemReport]
} derive(Eq, Debug, ToJson, FromJson)
///|
pub fn BatchReport::to_json_string(
self : BatchReport,
indent? : Int = 2,
) -> String {
self.to_json().stringify(indent~)
}
///|
/// Apply one compiled rule set to a batch of independent JSON values.
pub fn RuleSet::evaluate_batch(
self : RuleSet,
contexts : Array[Json],
options? : BatchOptions = BatchOptions::default(),
) -> BatchReport {
let items = []
let mut accepted_count = 0
let mut rejected_count = 0
let mut stopped_early = false
for index, context in contexts {
let report = self.evaluate_with_options(context, options.rule_options)
if report.passed {
accepted_count = accepted_count + 1
} else {
rejected_count = rejected_count + 1
}
items.push({ index, passed: report.passed, report })
if !report.passed && options.stop_on_first_rejected_record {
stopped_early = true
break
}
}
{
passed: rejected_count == 0,
requested_count: contexts.length(),
evaluated_count: items.length(),
accepted_count,
rejected_count,
stopped_early,
items,
}
}