Skip to main content

Engine

Struct Engine 

Source
pub struct Engine { /* private fields */ }
Expand description

JSONLogic compile/evaluate engine.

Holds the immutable engine state — registered crate::CustomOperator implementations, the EvaluationConfig, the optional preserve-structure flag — and exposes the public surface for parsing rules (Self::compile), evaluating them (Self::eval / Self::eval_str, Self::eval_into), and opening hot-loop sessions (Self::session). Enabling the trace feature also exposes Self::trace for traced sessions.

Engine is Send + Sync (every field is); the typical pattern is to build one at startup, wrap it in Arc<Engine>, and clone the Arc across threads or async tasks.

§Example

use datalogic_rs::Engine;

// 1. Build the engine.
let engine = Engine::new();

// 2. Compile a rule once.
let logic = engine
    .compile(r#"{"if": [{">=": [{"var": "age"}, 18]}, "adult", "minor"]}"#)
    .unwrap();

// 3. Evaluate against many inputs (here via `Session::eval_str`;
//    drop to `Engine::evaluate` if you want zero-copy borrowed results).
let mut session = engine.session();
for age in [12, 18, 42] {
    let payload = format!(r#"{{"age": {age}}}"#);
    let result = session.eval_str(&logic, &payload).unwrap();
    assert!(result == "\"adult\"" || result == "\"minor\"");
    session.reset();
}

§Choosing an evaluate method

Start here. Use Self::eval_str for one-shot calls. Switch to crate::Session once you’re evaluating the same compiled rule many times — it reuses one arena instead of allocating per call. Drop down to Self::evaluate only when you’re managing your own bumpalo::Bump (custom pools, integration with arena-aware downstream code).

Result-shape suffixes work the same on every tier: (none) returns datavalue::OwnedDataValue, _str returns String (JSON), _into::<T> returns T: DeserializeOwned (requires serde_json). The raw Self::evaluate is the only method that exposes &'a DataValue<'a> and a caller-owned &Bump.

Three tiers, in order of caller control:

MethodArena ownershipResult typeWhen to use
Self::eval / Self::eval_str / Self::eval_intoengine creates a fresh Bump::with_capacity(4096) per callOwnedDataValue / String / TOne-shot. Any caller that doesn’t want to think about arenas. Allocates each call — for hot loops, drop to Session.
crate::Session::eval / crate::Session::eval_str / crate::Session::eval_into / crate::Session::eval_borrowedsession-owned Bump, caller calls crate::Session::reset between batchesowned / String / T / borrowed &'a DataValue<'a>Hot loop with a long-lived engine. The Session hides bumpalo from the call site and pre-sizes the arena via crate::Session::reset_with_capacity when needed.
Self::evaluatecaller-passed &Bump; library never resets&'a DataValue<'a> (borrowed)Zero-copy result paths, custom pool/allocator strategies, integration with arena-aware downstream code.

All routes share the same dispatcher; the differences are who owns the arena, what the result type looks like, and whether the boundary parses / serialises JSON for you. There is no perf difference between the arena-aware paths once the bump is warm.

See the crate-level docs for the two-phase architecture, threading model, and walk-through examples; see the Session and EvaluationConfig rustdoc for arena-management and behaviour-tuning options respectively.

Implementations§

Source§

impl Engine

Source

pub fn builder() -> EngineBuilder

Start a crate::EngineBuilder for fluent construction.

Use the builder when you need a non-default EvaluationConfig, templating mode, or pre-registered custom operators. For a stock engine, Self::new is shorter.

Source

pub fn session(&self) -> Session<'_>

Open a crate::Session handle that owns a reusable arena and returns owned results, so callers don’t need to manage a bumpalo::Bump themselves.

Use this when you want the throughput of arena reuse without the lifetime juggling of Self::evaluate. The arena resets at the start of every eval* call; results are deep-cloned out of the arena before returning, so they outlive the next reset.

§Example
use datalogic_rs::Engine;

let engine = Engine::new();
let compiled = engine.compile(r#"{"+": [{"var": "x"}, 1]}"#).unwrap();
let mut session = engine.session();
let result = session.eval_str(&compiled, r#"{"x": 41}"#).unwrap();
assert_eq!(result, "42");
Source

pub fn new() -> Self

Creates a new Engine engine with all built-in operators.

The engine includes 50+ built-in operators optimized with OpCode dispatch. Templating mode is disabled by default. For non-default configuration (custom EvaluationConfig, templating mode, pre-registered custom operators) prefer Self::builder.

§Example
use datalogic_rs::Engine;

let engine = Engine::new();
Source

pub fn config(&self) -> &EvaluationConfig

Gets a reference to the current evaluation configuration.

Source

pub fn has_custom_operator(&self, name: &str) -> bool

Checks if a custom operator with the given name is registered.

Operator registration is builder-only; this is a read-only check against the frozen set produced by crate::EngineBuilder.

Source

pub fn custom_operator_names(&self) -> impl Iterator<Item = &str>

Iterator over the names of every custom operator registered on this engine (built-ins are not included). Order is unspecified (HashMap iteration order). Useful for tooling, UIs, and tests that need to introspect what’s available.

Source

pub fn compile<R: IntoLogic>(&self, rule: R) -> Result<Logic>

Compile a rule source into reusable Logic.

rule accepts any crate::IntoLogic shape: &str (JSON-parsed), &OwnedDataValue / OwnedDataValue (cloned/moved), or &serde_json::Value (gated on serde_json). For cross-thread sharing prefer Self::compile_arc.

§Example
use datalogic_rs::Engine;

let engine = Engine::new();
let compiled = engine.compile(r#"{"==": [{"var": "x"}, 1]}"#).unwrap();
Source

pub fn compile_arc<R: IntoLogic>(&self, rule: R) -> Result<Arc<Logic>>

Compile and wrap in an Arc in one call. Convenience for the dominant cross-thread-sharing pattern; equivalent to Arc::new(engine.compile(rule)?).

Source

pub fn trace(&self) -> TracedSession<'_>

Available on crate feature trace only.

Open a crate::TracedSession over this engine. Calls made through the session collect a per-call trace; the bare eval* methods on Engine itself pay no trace overhead.

Available only when the crate is built with feature = "trace".

§Trace coverage

The session’s one-shot crate::TracedSession::eval_str compiles the rule internally with optimization disabled, so every operator in the rule surfaces a trace step.

The pre-compiled paths (crate::TracedSession::eval taking a &Logic) inherit whatever shape that Logic was compiled into — constant sub-expressions folded by Self::compile won’t appear, since there is no operator left to execute. Use eval_str for full coverage on a one-shot run.

§Example
use datalogic_rs::Engine;

let engine = Engine::new();
let run = engine
    .trace()
    .eval_str(r#"{"+": [1, 2]}"#, "null");
assert_eq!(run.result.unwrap(), "3");
// run.steps is the per-node execution log;
// run.expression_tree is the rule's compile-time tree shape.
Source

pub fn evaluate<'a, D: EvalInput<'a>>( &self, compiled: &'a Logic, data: D, arena: &'a Bump, ) -> Result<&'a DataValue<'a>>

Evaluate compiled logic against arena-resident data — raw tier.

The caller owns the bumpalo::Bump lifecycle and may reset() it between calls; the returned &DataValue<'a> borrows from the arena, so it must be dropped before the next reset (enforced by the borrow checker). For ergonomic owned/typed/JSON-string output, prefer Self::eval / Self::eval_str / Self::eval_into.

§Example
use bumpalo::Bump;
use datalogic_rs::{Engine, DataValue};

let engine = Engine::new();
let compiled = engine.compile(r#"{"+": [{"var": "x"}, 2]}"#).unwrap();

let arena = Bump::new();
let data = DataValue::from_str(r#"{"x": 40}"#, &arena).unwrap();
let result = engine.evaluate(&compiled, data, &arena).unwrap();
assert_eq!(result.as_i64(), Some(42));

data accepts any input shape understood by crate::EvalInput: &'a DataValue<'a> (zero-cost passthrough), DataValue<'a> (single arena alloc), &str (JSON-parsed), &OwnedDataValue (deep-borrowed), or &serde_json::Value (gated on serde_json).

Source

pub fn eval<R, D>(&self, rule: R, data: D) -> Result<OwnedDataValue>
where R: IntoLogic, D: OwnedInput,

One-shot evaluation returning datavalue::OwnedDataValue.

Compiles rule, parses data, evaluates against a fresh per-call arena, and deep-clones the result out. For the same rule run repeatedly, escalate to Self::compile + a Session.

§Example
use datalogic_rs::Engine;

let engine = Engine::new();
let result = engine.eval(
    r#"{"+": [{"var": "x"}, 1]}"#,
    r#"{"x": 41}"#,
).unwrap();
assert_eq!(result.as_i64(), Some(42));
Source

pub fn eval_str<R, D>(&self, rule: R, data: D) -> Result<String>
where R: IntoLogic, D: OwnedInput,

One-shot evaluation returning a JSON String.

§Example
use datalogic_rs::Engine;

let engine = Engine::new();
let result = engine.eval_str(
    r#"{"==": [{"var": "x"}, 5]}"#,
    r#"{"x": 5}"#,
).unwrap();
assert_eq!(result, "true");
Source

pub fn eval_into<T, R, D>(&self, rule: R, data: D) -> Result<T>

Available on crate feature serde_json only.

One-shot evaluation deserialised into a typed T: DeserializeOwned.

Use T = serde_json::Value for a JSON Value result; use a typed struct for direct mapping. Internally routes through serde_json (round-trips the result through a JSON value).

§Example
use datalogic_rs::Engine;
use serde_json::Value;

let engine = Engine::new();
let result: Value = engine.eval_into(
    r#"{"+": [{"var": "x"}, 1]}"#,
    r#"{"x": 41}"#,
).unwrap();
assert_eq!(result, Value::from(42));

Trait Implementations§

Source§

impl Debug for Engine

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Engine

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.