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
5,799 downloads per month
Used in 2 crates
745KB
9K
SLoC
datalogic-rs
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