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}