Skip to main content

Crate fsm_guards

Crate fsm_guards 

Source
Expand description

Fuel-metered JSONLogic guard evaluation for FSM transition engines.

fsm-guards evaluates JSONLogic rules as boolean predicates with three production features missing from the upstream jsonlogic-rs crate:

  • In-loop fuel metering — a node-visit budget threaded through the recursive evaluator. Short-circuit operators (or, and, if) charge only for branches they actually visit. A rule like {"or": [true, <200-node-branch>]} with fuel: 10 succeeds in 2 node visits, not 200.
  • Typed errorsGuardError carries five distinct variants so callers can distinguish missing variables from type errors from budget exhaustion.
  • Per-evaluation custom operators — register extension functions on EvalCtx for each call. No global registry; no cross-tenant state.

§Quick start

use fsm_guards::{evaluate, EvalCtx};
use serde_json::json;

let ctx = EvalCtx::new();
let rule = json!({"and": [
    {"==": [{"var": "status"}, "active"]},
    {">=": [{"var": "score"}, 80]}
]});
let data = json!({"status": "active", "score": 95});

assert!(evaluate(&rule, &data, &ctx).unwrap());

§Two-tier API

§High-level: evaluate

use fsm_guards::{evaluate, EvalCtx, GuardError};
use serde_json::json;

let ctx = EvalCtx::new().with_fuel(50);

// MissingVar: pre-walk detects the absent key before evaluation starts.
let rule = json!({"var": "ghost"});
let data = json!({"status": "active"});
assert!(matches!(evaluate(&rule, &data, &ctx), Err(GuardError::MissingVar(k)) if k == "ghost"));

§Low-level: apply

Returns the raw JSONLogic result value — useful when the result is not a boolean (e.g. extracting a field value or building a derived value).

use fsm_guards::apply;
use serde_json::json;

let result = apply(&json!({"cat": ["hello", " ", "world"]}), &json!({})).unwrap();
assert_eq!(result, json!("hello world"));

§Custom operators

Register domain-specific operators on EvalCtx. They receive already-evaluated argument values (not raw AST nodes) and compose naturally inside compound expressions.

use fsm_guards::{evaluate, EvalCtx};
use serde_json::{json, Value};

let ctx = EvalCtx::new()
    .with_op("state_in", |args| {
        // args[0] = current state value, args[1..] = allowed states
        Ok(Value::Bool(args[1..].contains(&args[0])))
    });

let rule = json!({"state_in": [{"var": "status"}, "active", "pending"]});
let data = json!({"status": "active"});
assert!(evaluate(&rule, &data, &ctx).unwrap());

let data_blocked = json!({"status": "blocked"});
assert!(!evaluate(&rule, &data_blocked, &ctx).unwrap());

§Fuel accounting

Fuel is decremented for every node visit — operators and literal values. Short-circuit operators charge only for visited branches.

use fsm_guards::{evaluate, EvalCtx, GuardError};
use serde_json::json;

// {"or": [true, <deep-branch>]} with fuel:5 — short-circuits on `true`,
// deep branch never visited, only ~2 nodes charged.
let mut deep = json!({"==": [1, 2]});
for _ in 0..50 {
    deep = json!({"and": [deep, {"==": [1, 2]}]});
}
let rule = json!({"or": [true, deep]});
let ctx = EvalCtx::new().with_fuel(5);
assert_eq!(evaluate(&rule, &json!({}), &ctx).unwrap(), true);

// A rule that exceeds the budget:
let deep_rule = json!({"and": [{"==": [1,1]}, {"and": [{"==": [1,1]}, {"==": [1,1]}]}]});
let tight_ctx = EvalCtx::new().with_fuel(4);
assert!(matches!(
    evaluate(&deep_rule, &json!({}), &tight_ctx),
    Err(GuardError::FuelExceeded { limit: 4 })
));

§Thread safety

EvalCtx is Send + Sync. Wrap in Arc to share across threads; each evaluate() call uses its own stack-local fuel counter.

use fsm_guards::{evaluate, EvalCtx};
use serde_json::json;
use std::sync::Arc;

let ctx = Arc::new(EvalCtx::new());
let ctx2 = Arc::clone(&ctx);
let rule = json!({"==": [1, 1]});
let data = json!({});

let handle = std::thread::spawn(move || evaluate(&rule, &data, &ctx2).unwrap());
assert!(handle.join().unwrap());

Re-exports§

pub use ctx::CustomOp;
pub use ctx::EvalCtx;
pub use engine::apply;
pub use error::ApplyError;
pub use error::GuardError;
pub use guard::evaluate;

Modules§

ctx
engine
error
guard