Skip to main content

DataLogic

Struct DataLogic 

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

The main DataLogic engine for compiling and evaluating JSONLogic expressions.

The engine provides a two-phase approach to logic evaluation:

  1. Compilation: Parse JSON logic into optimized CompiledLogic
  2. Evaluation: Execute compiled logic against data

§Features

  • Thread-safe: Compiled logic can be shared across threads with Arc
  • Extensible: Add custom operators via add_operator
  • Structure preservation: Optionally preserve object structure for templating
  • OpCode dispatch: Built-in operators use fast enum-based dispatch

§Example

use datalogic_rs::DataLogic;
use serde_json::json;

let engine = DataLogic::new();
let logic = json!({">": [{"var": "age"}, 18]});
let compiled = engine.compile(&logic).unwrap();

let data = json!({"age": 21});
let result = engine.evaluate_owned(&compiled, data).unwrap();
assert_eq!(result, json!(true));

Implementations§

Source§

impl DataLogic

Source

pub fn new() -> Self

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

The engine includes 50+ built-in operators optimized with OpCode dispatch. Structure preservation is disabled by default.

§Example
use datalogic_rs::DataLogic;

let engine = DataLogic::new();
Source

pub fn with_preserve_structure() -> Self

Creates a new DataLogic engine with structure preservation enabled.

When enabled, objects with unknown operators are preserved as structured templates, allowing for dynamic object generation. Custom operators registered via add_operator are recognized and evaluated properly, even within structured objects.

§Example
use datalogic_rs::DataLogic;
use serde_json::json;

let engine = DataLogic::with_preserve_structure();
let logic = json!({
    "name": {"var": "user.name"},
    "score": {"+": [{"var": "base"}, {"var": "bonus"}]}
});
// Returns: {"name": "Alice", "score": 95}
§Custom Operators with Preserve Structure

Custom operators work seamlessly in preserve_structure mode:

use datalogic_rs::{DataLogic, Operator, ContextStack, Evaluator, Result, Error};
use serde_json::{json, Value};
use std::sync::Arc;

struct UpperOperator;
impl Operator for UpperOperator {
    fn evaluate(&self, args: &[Value], context: &mut ContextStack,
                evaluator: &dyn Evaluator) -> Result<Value> {
        let val = evaluator.evaluate(&args[0], context)?;
        Ok(json!(val.as_str().unwrap_or("").to_uppercase()))
    }
}

let mut engine = DataLogic::with_preserve_structure();
engine.add_operator("upper".to_string(), Box::new(UpperOperator));

let logic = json!({
    "message": {"upper": {"var": "text"}},
    "count": {"var": "num"}
});
let compiled = engine.compile(&logic).unwrap();
let result = engine.evaluate(&compiled, Arc::new(json!({"text": "hello", "num": 5}))).unwrap();
// Returns: {"message": "HELLO", "count": 5}
Source

pub fn with_config(config: EvaluationConfig) -> Self

Creates a new DataLogic engine with a custom configuration.

§Arguments
  • config - The evaluation configuration
§Example
use datalogic_rs::{DataLogic, EvaluationConfig, NanHandling};

let config = EvaluationConfig::default()
    .with_nan_handling(NanHandling::IgnoreValue);
let engine = DataLogic::with_config(config);
Source

pub fn with_config_and_structure( config: EvaluationConfig, preserve_structure: bool, ) -> Self

Creates a new DataLogic engine with both configuration and structure preservation.

§Arguments
  • config - The evaluation configuration
  • preserve_structure - Whether to preserve object structure
§Example
use datalogic_rs::{DataLogic, EvaluationConfig, NanHandling};

let config = EvaluationConfig::default()
    .with_nan_handling(NanHandling::IgnoreValue);
let engine = DataLogic::with_config_and_structure(config, true);
Source

pub fn config(&self) -> &EvaluationConfig

Gets a reference to the current evaluation configuration.

Source

pub fn preserve_structure(&self) -> bool

Returns whether structure preservation is enabled.

Source

pub fn add_operator(&mut self, name: String, operator: Box<dyn Operator>)

Registers a custom operator with the engine.

Custom operators extend the engine’s functionality with domain-specific logic. They override built-in operators if the same name is used.

§Arguments
  • name - The operator name (e.g., “custom_calc”)
  • operator - The operator implementation
§Example
use datalogic_rs::{DataLogic, Operator, ContextStack, Evaluator, Result, Error};
use serde_json::{json, Value};

struct DoubleOperator;

impl Operator for DoubleOperator {
    fn evaluate(
        &self,
        args: &[Value],
        _context: &mut ContextStack,
        _evaluator: &dyn Evaluator,
    ) -> Result<Value> {
        if let Some(n) = args.first().and_then(|v| v.as_f64()) {
            Ok(json!(n * 2.0))
        } else {
            Err(Error::InvalidArguments("Argument must be a number".to_string()))
        }
    }
}

let mut engine = DataLogic::new();
engine.add_operator("double".to_string(), Box::new(DoubleOperator));
Source

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

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

§Arguments
  • name - The operator name to check
§Returns

true if the operator exists, false otherwise.

Source

pub fn compile(&self, logic: &Value) -> Result<Arc<CompiledLogic>>

Compiles a JSON logic expression into an optimized form.

Compilation performs:

  • Static evaluation of constant expressions
  • OpCode assignment for built-in operators
  • Structure analysis for templating

The returned Arc<CompiledLogic> can be safely shared across threads.

§Arguments
  • logic - The JSON logic expression to compile
§Returns

An Arc-wrapped compiled logic structure, or an error if compilation fails.

§Example
use datalogic_rs::DataLogic;
use serde_json::json;
use std::sync::Arc;

let engine = DataLogic::new();
let logic = json!({"==": [1, 1]});
let compiled: Arc<_> = engine.compile(&logic).unwrap();
Source

pub fn evaluate( &self, compiled: &CompiledLogic, data: Arc<Value>, ) -> Result<Value>

Evaluates compiled logic with Arc-wrapped data.

Use this method when you already have data in an Arc to avoid re-wrapping. For owned data, use evaluate_owned instead.

§Arguments
  • compiled - The compiled logic to evaluate
  • data - The data context wrapped in an Arc
§Returns

The evaluation result, or an error if evaluation fails.

Source

pub fn evaluate_owned( &self, compiled: &CompiledLogic, data: Value, ) -> Result<Value>

Evaluates compiled logic with owned data.

This is a convenience method that wraps the data in an Arc before evaluation. If you already have Arc-wrapped data, use evaluate instead.

§Arguments
  • compiled - The compiled logic to evaluate
  • data - The owned data context
§Returns

The evaluation result, or an error if evaluation fails.

§Example
use datalogic_rs::DataLogic;
use serde_json::json;

let engine = DataLogic::new();
let logic = json!({"var": "name"});
let compiled = engine.compile(&logic).unwrap();

let data = json!({"name": "Alice"});
let result = engine.evaluate_owned(&compiled, data).unwrap();
assert_eq!(result, json!("Alice"));
Source

pub fn evaluate_json(&self, logic: &str, data: &str) -> Result<Value>

Convenience method for evaluating JSON strings directly.

This method combines compilation and evaluation in a single call. For repeated evaluations, compile once and reuse the compiled logic.

§Arguments
  • logic - JSON logic as a string
  • data - Data context as a JSON string
§Returns

The evaluation result, or an error if parsing or evaluation fails.

§Example
use datalogic_rs::DataLogic;

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

pub fn evaluate_node( &self, node: &CompiledNode, context: &mut ContextStack, ) -> Result<Value>

Evaluates a compiled node using OpCode dispatch.

This is the core evaluation method that handles:

  • Static values
  • Arrays
  • Built-in operators (via OpCode)
  • Custom operators
  • Structured objects (in preserve mode)
§Arguments
  • node - The compiled node to evaluate
  • context - The context stack containing data and metadata
§Returns

The evaluation result, or an error if evaluation fails.

Source

pub fn evaluate_node_cow<'a>( &self, node: &'a CompiledNode, context: &mut ContextStack, ) -> Result<Cow<'a, Value>>

Evaluate a compiled node, returning a Cow to avoid cloning literal values.

For CompiledNode::Value nodes (constants/literals), returns a borrowed reference to the pre-compiled value without cloning. For all other node types, performs full evaluation and returns the owned result.

Source

pub fn evaluate_json_with_trace( &self, logic: &str, data: &str, ) -> Result<TracedResult>

Evaluate JSON logic with execution trace for debugging.

This method compiles and evaluates JSONLogic while recording each execution step for replay in debugging UIs.

§Arguments
  • logic - JSON logic as a string
  • data - Data context as a JSON string
§Returns

A TracedResult containing the result, expression tree, and execution steps.

§Example
use datalogic_rs::DataLogic;

let engine = DataLogic::new();
let result = engine.evaluate_json_with_trace(
    r#"{">=": [{"var": "age"}, 18]}"#,
    r#"{"age": 25}"#
).unwrap();

println!("Result: {}", result.result);
println!("Steps: {}", result.steps.len());
Source

pub fn evaluate_node_traced( &self, node: &CompiledNode, context: &mut ContextStack, collector: &mut TraceCollector, node_id_map: &HashMap<usize, u32>, ) -> Result<Value>

Evaluate a compiled node with tracing.

This method records each step of the evaluation for debugging.

Trait Implementations§

Source§

impl Default for DataLogic

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.