fsm-guards 0.1.1

Fuel-metered JSONLogic guard evaluation with per-call custom operators — forked from jsonlogic-rs
Documentation
//! Fuel metering — in-loop counting and short-circuit behaviour.
//!
//! Run with: cargo run --example fuel_limits

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

fn main() {
    // ── short-circuit: or stops on first true ──────────────────────────────
    // Build a 200-node branch that would blow a pre-walk budget.
    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);
    let result = evaluate(&rule, &json!({}), &ctx).unwrap();
    println!("short-circuit or: {result}"); // true — only ~2 nodes visited

    // ── budget exhausted ───────────────────────────────────────────────────
    let rule = json!({"and": [
        {"==": [1, 1]},
        {"==": [2, 2]},
        {"==": [3, 3]}
    ]});

    match evaluate(&rule, &json!({}), &EvalCtx::new().with_fuel(5)) {
        Ok(v) => println!("passed with fuel:5 → {v}"),
        Err(GuardError::FuelExceeded { limit }) => println!("exceeded limit:{limit}"),
        Err(e) => println!("error: {e}"),
    }

    // ── default fuel (200) handles realistic guards comfortably ───────────
    let ctx = EvalCtx::new(); // fuel: 200
    let rule = json!({"and": [
        {"==": [{"var": "status"}, "active"]},
        {">=": [{"var": "score"}, 80]},
        {"in": [{"var": "role"}, ["admin", "operator"]]}
    ]});
    let data = json!({"status": "active", "score": 95, "role": "admin"});
    println!("realistic guard: {}", evaluate(&rule, &data, &ctx).unwrap()); // true
}