use crate::ctx::EvalCtx;
use crate::engine::eval::{apply_value, truthy};
use crate::error::{ApplyError, GuardError};
use serde_json::Value;
pub fn evaluate(rule: &Value, data: &Value, ctx: &EvalCtx) -> Result<bool, GuardError> {
if let Some(missing) = first_missing_var(rule, data) {
return Err(GuardError::MissingVar(missing));
}
let mut fuel = ctx.fuel;
match apply_value(rule, data, ctx, &mut fuel) {
Ok(v) => Ok(truthy(&v)),
Err(ApplyError::FuelExceeded) => Err(GuardError::FuelExceeded { limit: ctx.fuel }),
Err(ApplyError::CustomOpFailed { op, source }) => {
Err(GuardError::CustomOpFailed { op, source })
}
Err(ApplyError::UnknownOperator(op)) => {
Err(GuardError::Malformed(format!("unknown operator: {op}")))
}
Err(ApplyError::InvalidArguments(msg)) => {
let lower = msg.to_lowercase();
let is_type_error = lower.contains("type")
|| lower.contains("expected")
|| lower.contains("could not")
|| lower.contains("must be")
|| lower.contains("got ");
if is_type_error {
Err(GuardError::TypeError(msg))
} else {
Err(GuardError::Malformed(msg))
}
}
}
}
fn first_missing_var(rule: &Value, data: &Value) -> Option<String> {
let data_obj = data.as_object()?;
collect_var_refs(rule).into_iter().find(|path| {
let root = path.split('.').next().unwrap_or("");
!root.is_empty() && !data_obj.contains_key(root)
})
}
fn collect_var_refs(rule: &Value) -> Vec<String> {
let mut refs = Vec::new();
walk_for_vars(rule, &mut refs);
refs
}
fn walk_for_vars(v: &Value, refs: &mut Vec<String>) {
match v {
Value::Object(map) => {
if let Some(var_arg) = map.get("var") {
match var_arg {
Value::String(s) if !s.is_empty() => refs.push(s.clone()),
Value::Array(arr) => {
if arr.len() == 1 {
if let Some(Value::String(s)) = arr.first() {
if !s.is_empty() {
refs.push(s.clone());
}
}
}
}
_ => {}
}
return;
}
for child in map.values() {
walk_for_vars(child, refs);
}
}
Value::Array(arr) => {
for child in arr {
walk_for_vars(child, refs);
}
}
_ => {}
}
}