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 pub fn new() -> Self {
30 Self {
31 fuel: 200,
32 ops: HashMap::new(),
33 }
34 }
35
36 /// Override the fuel budget.
37 pub fn with_fuel(mut self, fuel: usize) -> Self {
38 self.fuel = fuel;
39 self
40 }
41
42 /// Register a custom operator. Operators are matched by exact name string.
43 pub fn with_op<F>(mut self, name: &str, f: F) -> Self
44 where
45 F: Fn(&[Value]) -> Result<Value, Box<dyn std::error::Error + Send + Sync>>
46 + Send
47 + Sync
48 + 'static,
49 {
50 self.ops.insert(name.to_string(), Arc::new(f));
51 self
52 }
53}
54
55impl Default for EvalCtx {
56 fn default() -> Self {
57 Self::new()
58 }
59}