src/filter/dsl/eval/context.rs

//! Per-line evaluation context.
//!
//! An [`EvalContext`] is a small string->string map populated by the
//! renderer pipeline before invoking the filter. Values are stored as
//! strings; the evaluator lazily coerces them to integers when the right-hand
//! side of the comparison is an integer literal.

use std::collections::HashMap;

#[derive(Debug, Default, Clone)]
pub struct EvalContext {
    values: HashMap<String, String>,
}

impl EvalContext {
    pub fn new() -> Self {
        Self::default()
    }

    /// Set a field. Overwrites previous value.
    pub fn set(&mut self, key: &str, value: impl Into<String>) {
        self.values.insert(key.to_string(), value.into());
    }

    /// Bulk-load from an iterator of key/value pairs.
    pub fn extend<I, K, V>(&mut self, iter: I)
    where
        I: IntoIterator<Item = (K, V)>,
        K: Into<String>,
        V: Into<String>,
    {
        for (k, v) in iter {
            self.values.insert(k.into(), v.into());
        }
    }

    /// Look up a field as a string. Returns the empty string if missing, so
    /// comparisons against missing fields naturally fail rather than panic.
    pub fn get(&self, key: &str) -> &str {
        self.values
            .get(key)
            .map(|s| s.as_str())
            .unwrap_or("")
    }

    /// Look up a field as an integer. Returns `None` if the field is missing
    /// or not parseable.
    pub fn get_int(&self, key: &str) -> Option<i64> {
        self.values.get(key)?.parse::<i64>().ok()
    }

    pub fn is_empty(&self) -> bool {
        self.values.is_empty()
    }

    pub fn len(&self) -> usize {
        self.values.len()
    }

    pub fn clear(&mut self) {
        self.values.clear();
    }

    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
        self.values.iter().map(|(k, v)| (k.as_str(), v.as_str()))
    }
}