fsm-guards 0.1.0

Fuel-metered JSONLogic guard evaluation with per-call custom operators — forked from jsonlogic-rs
Documentation
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;

/// Signature for a custom operator handler.
///
/// Receives a slice of already-evaluated argument values (not raw AST nodes).
/// Returns a `Value` that the evaluator inserts back into the expression tree,
/// or an error that surfaces as `GuardError::CustomOpFailed`.
pub type CustomOp =
    dyn Fn(&[Value]) -> Result<Value, Box<dyn std::error::Error + Send + Sync>>
        + Send
        + Sync;

/// Per-evaluation context: fuel budget + custom operator map.
///
/// `EvalCtx` is `Send + Sync`. Build once, share via `Arc<EvalCtx>` across
/// threads. Each `evaluate()` call receives `&EvalCtx` and maintains its own
/// stack-local fuel counter — no shared mutable state.
pub struct EvalCtx {
    /// Maximum AST nodes the evaluator may visit. Default: 200.
    pub fuel: usize,
    /// Custom operator map: operator name → handler.
    pub ops: HashMap<String, Arc<CustomOp>>,
}

impl EvalCtx {
    /// Create a context with 200 fuel and no custom operators.
    pub fn new() -> Self {
        Self {
            fuel: 200,
            ops: HashMap::new(),
        }
    }

    /// Override the fuel budget.
    pub fn with_fuel(mut self, fuel: usize) -> Self {
        self.fuel = fuel;
        self
    }

    /// Register a custom operator. Operators are matched by exact name string.
    pub fn with_op<F>(mut self, name: &str, f: F) -> Self
    where
        F: Fn(&[Value]) -> Result<Value, Box<dyn std::error::Error + Send + Sync>>
            + Send
            + Sync
            + 'static,
    {
        self.ops.insert(name.to_string(), Arc::new(f));
        self
    }
}

impl Default for EvalCtx {
    fn default() -> Self {
        Self::new()
    }
}