#rule-engine #json #logic #performance-optimization #rules #allocation

datalogic-rs

A fast, type-safe Rust implementation of JSONLogic for evaluating logical rules as JSON. Perfect for business rules engines and dynamic filtering in Rust applications.

67 stable releases

3.0.24 Apr 12, 2025
3.0.9 Mar 30, 2025
2.1.15 Mar 5, 2025
2.0.5 Dec 20, 2024
1.0.2 Nov 20, 2024

#63 in Data structures

Download history 211/week @ 2025-01-04 199/week @ 2025-01-11 170/week @ 2025-01-18 203/week @ 2025-01-25 287/week @ 2025-02-01 225/week @ 2025-02-08 954/week @ 2025-02-15 962/week @ 2025-02-22 1552/week @ 2025-03-01 602/week @ 2025-03-08 782/week @ 2025-03-15 838/week @ 2025-03-22 1574/week @ 2025-03-29 2372/week @ 2025-04-05 790/week @ 2025-04-12

5,799 downloads per month
Used in 2 crates

Apache-2.0

745KB
9K SLoC

datalogic-rs

Release Crates Documentation crates.io Downloads

A lightweight, high-performance Rust implementation of JSONLogic, optimized for rule-based decision-making and dynamic expressions.

Why datalogic-rs?

  • 🏆 Fully JSONLogic-compliant (100% test coverage)
  • 🚀 Fast & lightweight: Zero-copy JSON parsing, minimal allocations
  • 🔒 Thread-safe: Designed for parallel execution
  • Optimized for production: Static dispatch and rule optimization
  • 🔌 Extensible: Support for custom operators

Overview

datalogic-rs provides a robust implementation of JSONLogic rules with arena-based memory management for optimal performance. The library features comprehensive operator support, optimizations for static rule components, and high test coverage.

Features

  • Arena-based memory management for optimal performance
  • Comprehensive JSONLogic operator support
  • Optimizations for static rule components
  • Zero copy rule creation and evaluation
  • High test coverage and compatibility with standard JSONLogic
  • Intuitive API for creating, parsing, and evaluating rules

Installation

Add datalogic-rs to your Cargo.toml:

[dependencies]
datalogic-rs = "3.0.12"

Core API Methods

datalogic-rs provides three primary API methods for evaluating rules, each suited for different use cases:

1. evaluate - For reusing parsed rules and data

Best for scenarios where the same rule will be evaluated against different data contexts, or vice versa.

use datalogic_rs::DataLogic;

let dl = DataLogic::new();

// Parse rule and data once
let rule = dl.parse_logic(r#"{ ">": [{"var": "temp"}, 100] }"#, None).unwrap();
let data = dl.parse_data(r#"{"temp": 110}"#).unwrap();

// Evaluate the rule against the data
let result = dl.evaluate(&rule, &data).unwrap();
assert!(result.to_json().as_bool().unwrap());

2. evaluate_str - One-step parsing and evaluation

Ideal for one-time evaluations or when rules are dynamically generated.

use datalogic_rs::DataLogic;

let dl = DataLogic::new();

// Parse and evaluate in one step
let result = dl.evaluate_str(
    r#"{ "abs": -42 }"#,
    r#"{}"#,
    None
).unwrap();

assert_eq!(result.as_i64().unwrap(), 42);

3. evaluate_json - Work directly with JSON values

Perfect when your application already has the rule and data as serde_json Values.

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

let dl = DataLogic::new();

// Use serde_json values directly
let logic = json!({
    "if": [
        {">": [{"var": "cart.total"}, 100]},
        "Eligible for discount",
        "No discount"
    ]
});
let data = json!({"cart": {"total": 120}});

let result = dl.evaluate_json(&logic, &data, None).unwrap();
assert_eq!(result.as_str().unwrap(), "Eligible for discount");

Real-World Examples

1. Complex Logical Rules (AND/OR)

use datalogic_rs::DataLogic;

let dl = DataLogic::new();
let result = dl.evaluate_str(
    r#"{
        "and": [
            {">=": [{"var": "age"}, 18]},
            {"<": [{"var": "age"}, 65]},
            {"or": [
                {"==": [{"var": "subscription"}, "premium"]},
                {">=": [{"var": "purchases"}, 5]}
            ]}
        ]
    }"#,
    r#"{"age": 25, "subscription": "basic", "purchases": 7}"#,
    None
).unwrap();

assert!(result.as_bool().unwrap());

2. Array Operations

use datalogic_rs::DataLogic;

let dl = DataLogic::new();
let result = dl.evaluate_str(
    r#"{
        "map": [
            {
                "filter": [
                    {"var": "users"},
                    {">=": [{"var": "age"}, 18]}
                ]
            },
            {"var": "name"}
        ]
    }"#,
    r#"{
        "users": [
            {"name": "Alice", "age": 20},
            {"name": "Bob", "age": 15},
            {"name": "Charlie", "age": 25}
        ]
    }"#,
    None
).unwrap();

// Returns ["Alice", "Charlie"]
assert_eq!(result.as_array().unwrap().len(), 2);

3. DateTime Operations

use datalogic_rs::DataLogic;

let dl = DataLogic::new();
let result = dl.evaluate_str(
    r#"{
        ">": [
            {"+": [
                {"datetime": "2023-07-15T08:30:00Z"},
                {"timestamp": "2d"}
            ]},
            {"datetime": "2023-07-16T08:30:00Z"}
        ]
    }"#,
    r#"{}"#,
    None
).unwrap();

assert!(result.as_bool().unwrap());

Custom Operators

Create domain-specific operators to extend the system:

use datalogic_rs::{DataLogic, SimpleOperatorFn, DataValue};
use datalogic_rs::value::NumberValue;

// Define a custom operator function - simple approach
fn double<'r>(args: Vec<DataValue<'r>>, data: DataValue<'r>) -> std::result::Result<DataValue<'r>, String> {
    if args.is_empty() {
        // If no arguments, try to use a value from data context
        if let Some(obj) = data.as_object() {
            for (key, val) in obj {
                if *key == "value" && val.is_number() {
                    if let Some(n) = val.as_f64() {
                        return Ok(DataValue::Number(NumberValue::from_f64(n * 2.0)));
                    }
                }
            }
        }
        return Err("double operator requires an argument or 'value' in data".to_string());
    }
    
    if let Some(n) = args[0].as_f64() {
        return Ok(DataValue::Number(NumberValue::from_f64(n * 2.0)));
    }
    
    Err("Argument must be a number".to_string())
}

let mut dl = DataLogic::new();
dl.register_simple_operator("double", double);

// Using with an explicit argument
let result = dl.evaluate_str(
    r#"{"double": 4}"#,
    r#"{}"#,
    None
).unwrap();

assert_eq!(result.as_f64().unwrap(), 8.0);

// Using with data context
let result = dl.evaluate_str(
    r#"{"double": []}"#,
    r#"{"value": 5}"#,
    None
).unwrap();

assert_eq!(result.as_f64().unwrap(), 10.0);

Custom operators can be combined with built-in operators for complex logic:

let complex_rule = r#"{
    "*": [
        2,
        {"double": {"var": "value"}},
        3
    ]
}"#;

// With data: {"value": 3}, evaluates to 2 * (3*2) * 3 = 2 * 6 * 3 = 36

For more advanced use cases and complex data types, DataLogic-rs also provides an advanced custom operator API.

Use Cases

datalogic-rs excels in scenarios requiring runtime rule evaluation:

Feature Flagging

Control feature access based on user attributes or context:

let rule = r#"{
    "and": [
        {"==": [{"var": "user.country"}, "US"]},
        {"or": [
            {"==": [{"var": "user.role"}, "beta_tester"]},
            {">=": [{"var": "user.account_age_days"}, 30]}
        ]}
    ]
}"#;

// Feature is available only to US users who are either beta testers or have accounts older than 30 days
let feature_enabled = dl.evaluate_str(rule, user_data_json, None).unwrap().as_bool().unwrap();

Dynamic Pricing

Apply complex discount rules:

let pricing_rule = r#"{
    "if": [
        {">=": [{"var": "cart.total"}, 100]},
        {"-": [{"var": "cart.total"}, {"*": [{"var": "cart.total"}, 0.1]}]},
        {"var": "cart.total"}
    ]
}"#;

// 10% discount for orders over $100
let final_price = dl.evaluate_str(pricing_rule, order_data, None).unwrap().as_f64().unwrap();

Fraud Detection

Evaluate transaction risk:

let fraud_check = r#"{
    "or": [
        {"and": [
            {"!=": [{"var": "transaction.billing_country"}, {"var": "user.country"}]},
            {">=": [{"var": "transaction.amount"}, 1000]}
        ]},
        {"and": [
            {">=": [{"var": "transaction.attempts_last_hour"}, 5]},
            {">": [{"var": "transaction.amount"}, 500]}
        ]}
    ]
}"#;

let is_suspicious = dl.evaluate_str(fraud_check, transaction_data, None).unwrap().as_bool().unwrap();

Authorization Rules

Implement complex access control:

let access_rule = r#"{
    "or": [
        {"==": [{"var": "user.role"}, "admin"]},
        {"and": [
            {"==": [{"var": "user.role"}, "editor"]},
            {"in": [{"var": "resource.project_id"}, {"var": "user.projects"}]}
        ]}
    ]
}"#;

let has_access = dl.evaluate_str(access_rule, access_context, None).unwrap().as_bool().unwrap();

Form Validation

Check field dependencies dynamically:

let validation_rule = r#"{
    "if": [
        {"==": [{"var": "shipping_method"}, "international"]},
        {"and": [
            {"!": {"missing": "postal_code"}},
            {"!": {"missing": "country"}}
        ]},
        true
    ]
}"#;

let is_valid = dl.evaluate_str(validation_rule, form_data, None).unwrap().as_bool().unwrap();

Supported Operations

Category Operators
Comparison == (equal), === (strict equal), != (not equal), !== (strict not equal), > (greater than), >= (greater than or equal), < (less than), <= (less than or equal)
Logic and, or, ! (not), !! (double negation)
Arithmetic + (addition), - (subtraction), * (multiplication), / (division), % (modulo), min, max, abs (absolute value), ceil (round up), floor (round down)
Control Flow if (conditional), ?: (ternary), ?? (nullish coalescing)
Arrays map, filter, reduce, all, some, none, merge, in (contains), length, slice, sort
Strings cat (concatenate), substr, starts_with, ends_with, upper, lower, trim
Data Access var (variable access), val (value access), exists, missing, missing_some
DateTime datetime, timestamp, now, parse_date, format_date, date_diff
Error Handling throw, try
Custom Support for user-defined operators

Performance

Benchmark results show datalogic-rs is 30% faster than the next fastest JSONLogic implementations, thanks to:

  • Arena-based memory management
  • Static operator dispatch
  • Zero-copy deserialization
  • Optimized rule compilation

Benchmark Metrics (Apple M2 Pro)

Implementation Execution Time Relative Performance
datalogic-rs 380ms 1.0x (baseline)
json-logic-engine (pre-compiled) 417ms 1.1x slower
json-logic-engine (interpreted) 986.064ms 2.6x slower
json-logic-js 5,755ms 15.1x slower

These benchmarks represent execution time for the same standard suite of JSONLogic tests, demonstrating datalogic-rs's superior performance profile across common expression patterns.

Contributing

We welcome contributions! See the CONTRIBUTING.md for details.

License

Licensed under Apache License, Version 2.0


Next Steps

✅ Try out datalogic-rs today!
📖 Check out the API documentation for detailed usage instructions
📚 See the docs.rs documentation for comprehensive reference
📝 Learn how to implement custom operators to extend the engine
⭐ Star the GitHub repository if you find it useful!

Dependencies

~4–6MB
~108K SLoC