use crate::filter::expr::{Expr, Op};
use crate::record::LogRecord;
use regex::Regex;
pub fn eval(expr: &Expr, record: &LogRecord) -> bool {
match expr {
Expr::Comparison { field, op, value } => {
let field_value = get_field(record, field);
match field_value {
Some(fv) => compare(&fv, op, value),
None => false,
}
}
Expr::And(left, right) => eval(left, record) && eval(right, record),
Expr::Or(left, right) => eval(left, record) || eval(right, record),
Expr::Not(inner) => !eval(inner, record),
}
}
fn get_field(record: &LogRecord, field: &str) -> Option<String> {
match field {
"id" => Some(record.id.to_string()),
"timestamp" => Some(record.timestamp.to_rfc3339()),
"level" => record.level.map(|l| l.to_string()),
"source" => Some(record.source.to_string()),
"pid" => record.pid.map(|p| p.to_string()),
"tid" => record.tid.map(|t| t.to_string()),
"component_name" | "component" => record.component_name.clone(),
"process_name" | "process" => record.process_name.clone(),
"hostname" => record.hostname.clone(),
"container" => record.container.clone(),
"context" => record.context.clone(),
"function" => record.function.clone(),
"message" => Some(record.message.clone()),
"raw" => Some(record.raw.clone()),
"loader_id" => Some(record.loader_id.to_string()),
_ => {
if let Some(key) = field.strip_prefix("metadata.") {
record.metadata.as_ref().and_then(|m| m.get(key).cloned())
} else {
record.metadata.as_ref().and_then(|m| m.get(field).cloned())
}
}
}
}
fn compare(field_value: &str, op: &Op, target: &str) -> bool {
match op {
Op::Eq => field_value == target,
Op::Ne => field_value != target,
Op::Gt => numeric_cmp(field_value, target)
.map_or(field_value > target, |o| o == std::cmp::Ordering::Greater),
Op::Ge => numeric_cmp(field_value, target)
.map_or(field_value >= target, |o| o != std::cmp::Ordering::Less),
Op::Lt => numeric_cmp(field_value, target)
.map_or(field_value < target, |o| o == std::cmp::Ordering::Less),
Op::Le => numeric_cmp(field_value, target)
.map_or(field_value <= target, |o| o != std::cmp::Ordering::Greater),
Op::Contains => field_value.contains(target),
Op::StartsWith => field_value.starts_with(target),
Op::EndsWith => field_value.ends_with(target),
Op::Regex => Regex::new(target).is_ok_and(|re| re.is_match(field_value)),
}
}
fn numeric_cmp(a: &str, b: &str) -> Option<std::cmp::Ordering> {
let na: f64 = a.parse().ok()?;
let nb: f64 = b.parse().ok()?;
na.partial_cmp(&nb)
}
#[cfg(test)]
#[path = "eval_tests.rs"]
mod eval_tests;