Skip to main content

fsm_guards/
ctx.rs

1use serde_json::Value;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5/// Signature for a custom operator handler.
6///
7/// Receives a slice of already-evaluated argument values (not raw AST nodes).
8/// Returns a `Value` that the evaluator inserts back into the expression tree,
9/// or an error that surfaces as `GuardError::CustomOpFailed`.
10pub type CustomOp =
11    dyn Fn(&[Value]) -> Result<Value, Box<dyn std::error::Error + Send + Sync>>
12        + Send
13        + Sync;
14
15/// Per-evaluation context: fuel budget + custom operator map.
16///
17/// `EvalCtx` is `Send + Sync`. Build once, share via `Arc<EvalCtx>` across
18/// threads. Each `evaluate()` call receives `&EvalCtx` and maintains its own
19/// stack-local fuel counter — no shared mutable state.
20pub struct EvalCtx {
21    /// Maximum AST nodes the evaluator may visit. Default: 200.
22    pub fuel: usize,
23    /// Custom operator map: operator name → handler.
24    pub ops: HashMap<String, Arc<CustomOp>>,
25}
26
27impl EvalCtx {
28    /// Create a context with 200 fuel and no custom operators.
29    ///
30    /// # Example
31    /// ```rust
32    /// use fsm_guards::EvalCtx;
33    /// let ctx = EvalCtx::new();
34    /// assert_eq!(ctx.fuel, 200);
35    /// ```
36    pub fn new() -> Self {
37        Self {
38            fuel: 200,
39            ops: HashMap::new(),
40        }
41    }
42
43    /// Override the fuel budget.
44    ///
45    /// # Example
46    /// ```rust
47    /// use fsm_guards::EvalCtx;
48    /// let ctx = EvalCtx::new().with_fuel(50);
49    /// assert_eq!(ctx.fuel, 50);
50    /// ```
51    pub fn with_fuel(mut self, fuel: usize) -> Self {
52        self.fuel = fuel;
53        self
54    }
55
56    /// Register a custom operator. Operators are matched by exact name string.
57    ///
58    /// The handler receives already-evaluated argument values. It may be called
59    /// from multiple threads if the `EvalCtx` is shared via `Arc`.
60    ///
61    /// # Example
62    /// ```rust
63    /// use fsm_guards::{evaluate, EvalCtx};
64    /// use serde_json::json;
65    ///
66    /// let ctx = EvalCtx::new().with_op("double", |args| {
67    ///     let n = args[0].as_f64().unwrap_or(0.0);
68    ///     Ok(serde_json::json!(n * 2.0))
69    /// });
70    ///
71    /// // {"double": [5]} returns 10.0, which is truthy.
72    /// assert!(evaluate(&json!({"double": [5]}), &json!({}), &ctx).unwrap());
73    /// ```
74    pub fn with_op<F>(mut self, name: &str, f: F) -> Self
75    where
76        F: Fn(&[Value]) -> Result<Value, Box<dyn std::error::Error + Send + Sync>>
77            + Send
78            + Sync
79            + 'static,
80    {
81        self.ops.insert(name.to_string(), Arc::new(f));
82        self
83    }
84}
85
86impl Default for EvalCtx {
87    fn default() -> Self {
88        Self::new()
89    }
90}