A business rules engine that evaluates a custom DSL against JSON data — a clean-room Rust regeneration of gorules (2016), and the "after" half of a regenerate-don't-patch case study. The Go original was audited by four AI specialists (37 findings), its test suite rehabilitated 29→124 to pin down actual behavior, and then this crate was generated fresh from that specification: 12+ audited bugs fixed, 15 operators added, 121 tests (cargo test --workspace).
# Compare a JSON path to a constant
country IsEqualTo 'USA'
# Compare two paths
country IsEqualTo state
# Array selectors — apply a rule to every element
ALL orderItems.weight IsEqualTo '11'
ANY orderItems.availableInventory IsEqualTo 'NC'
# Conjunctions
country IsEqualTo 'USA' AND state IsEqualTo 'FL'
country IsEqualTo 'CANADA' OR state IsEqualTo 'TX'
# Multi-line rules (precedence parsing)
country IsEqualTo 'CANADA'
AND state IsEqualTo 'FL'
OR state IsEqualTo 'TX'
# Math expressions
'10' IsEqualTo |ADD '5' '5'|
'256' IsEqualTo |SQUARE '16'|
# String expressions
'SOUTH' IsEqualTo |TAKETILL '-' 'SOUTH-EAST'|
'TEST' IsEqualTo |TOUPPER 'test'|
| Operator | Description |
|---|---|
IsEqualTo |
Equality check |
IsNotEqualTo |
Inequality check |
IsGreaterThan |
Numeric greater than |
IsGreaterThanOrEqualTo |
Numeric greater than or equal |
IsLessThan |
Numeric less than |
IsLessThanOrEqualTo |
Numeric less than or equal |
Contains |
String contains substring |
StartsWith |
String starts with prefix |
EndsWith |
String ends with suffix |
IsNull |
Value is empty |
IsNotNull |
Value is not empty |
In |
Value in comma-separated list |
NotIn |
Value not in comma-separated list |
IsLike |
Regex match |
IsNotLike |
Regex non-match |
IsTrue |
Boolean true check |
IsFalse |
Boolean false check |
ADD, SUB, MUL, DIV, SQUARE — used inside pipe delimiters: |ADD '5' '10'|
TAKETILL, TOUPPER, TOLOWER — used inside pipe delimiters: |TOUPPER 'hello'|
| Selector | Description |
|---|---|
THIS |
Default — compare a single value |
ANY |
True if any array element matches (OR) |
ALL |
True if all array elements match (AND) |
Add to your Cargo.toml:
[dependencies]
rustrules-core = { path = "crates/rustrules-core" }use rustrules_core::{dsl_evaluate, evaluate_rules};
use serde_json::json;
let data = json!({
"country": "USA",
"state": "FL",
"orderItems": [
{"weight": "11"},
{"weight": "11"}
]
});
// Simple evaluation
let result = dsl_evaluate("country IsEqualTo 'USA'", &data).unwrap();
assert!(result);
// Array selector
let result = dsl_evaluate("ALL orderItems.weight IsEqualTo '11'", &data).unwrap();
assert!(result);
// Multi-line with precedence
let rule = "country IsEqualTo 'USA'\nAND\nstate IsEqualTo 'FL'";
let result = evaluate_rules(rule, &data).unwrap();
assert!(result);Process rules from multiple sources:
use rustrules_core::{process_rules, RuleSource, RuleResult};
use std::collections::HashMap;
let data = json!({"country": "USA"});
// Single rule
let source = RuleSource::Text("country IsEqualTo 'USA'".into());
let result = process_rules(&source, &data).unwrap();
// RuleResult::Single(true)
// Multiple rules
let source = RuleSource::Collection(vec![
"country IsEqualTo 'USA'".into(),
"country IsEqualTo 'CANADA'".into(),
]);
let result = process_rules(&source, &data).unwrap();
// RuleResult::List([true, false])
// Named rules
let mut rules = HashMap::new();
rules.insert("is_usa".into(), "country IsEqualTo 'USA'".into());
rules.insert("is_canada".into(), "country IsEqualTo 'CANADA'".into());
let source = RuleSource::ObjectCollection(rules);
let result = process_rules(&source, &data).unwrap();
// RuleResult::Named({"is_usa": true, "is_canada": false})
// From a JSON rules file
let source = RuleSource::File {
name: "is-country-canada".into(),
path: "rulez.json".into(),
};
let result = process_rules(&source, &data).unwrap();The rustrules-server crate provides an Axum HTTP server.
cargo run -p rustrules-server
# or with custom host/port:
RUSTRULES_HOST=0.0.0.0 RUSTRULES_PORT=3000 cargo run -p rustrules-servercurl http://localhost:8080/status
# {"status":"Service running"}curl http://localhost:8080/operators/comparison
# {"operators":["IsEqualTo","IsNotEqualTo",...]}curl http://localhost:8080/operators/math
# {"operators":["ADD","SUB","MUL","DIV","SQUARE"]}curl http://localhost:8080/operators/string
# {"operators":["TAKETILL","TOUPPER","TOLOWER"]}Evaluate a rule against JSON data:
curl -X POST http://localhost:8080/rule \
-H "Content-Type: application/json" \
-d '{
"rule": "country IsEqualTo '\''USA'\''",
"data": {"country": "USA", "state": "FL"}
}'
# {"result":true}Supports single rules, collections, and named rules:
# Collection — returns array of bools
curl -X POST http://localhost:8080/rule \
-H "Content-Type: application/json" \
-d '{
"rule": ["country IsEqualTo '\''USA'\''", "state IsEqualTo '\''TX'\''"],
"data": {"country": "USA", "state": "FL"}
}'
# {"result":[true,false]}
# Named — returns map of bools
curl -X POST http://localhost:8080/rule \
-H "Content-Type: application/json" \
-d '{
"rule": {"check_usa": "country IsEqualTo '\''USA'\''", "check_tx": "state IsEqualTo '\''TX'\''"},
"data": {"country": "USA", "state": "FL"}
}'
# {"result":{"check_usa":true,"check_tx":false}}Evaluate a value expression:
curl -X POST http://localhost:8080/expression \
-H "Content-Type: application/json" \
-d '{
"expression": "|ADD '\''5'\'' '\''10'\''|",
"data": {}
}'
# {"result":"15"}rustrules/
├── Cargo.toml # Workspace root
├── crates/
│ ├── rustrules-core/ # Library crate
│ │ ├── src/
│ │ │ ├── lib.rs # Public API re-exports
│ │ │ ├── error.rs # RustrulesError enum
│ │ │ ├── operator.rs # All operator enums + evaluate
│ │ │ ├── value.rs # Value types + JSON path traversal
│ │ │ ├── expr/ # Expression trait + types
│ │ │ │ ├── mod.rs
│ │ │ │ ├── rule_expr.rs # RuleExpression (with selectors)
│ │ │ │ ├── conjunction_expr.rs # AND/OR combinations
│ │ │ │ └── boolean_expr.rs # Constant true/false
│ │ │ ├── parser/ # DSL parser
│ │ │ │ ├── mod.rs # parse_dsl, parse_dsl_with_precedence
│ │ │ │ └── tokens.rs # Tokenization utilities
│ │ │ ├── eval.rs # Evaluation entry points
│ │ │ └── rule.rs # Rule processor (file/collection/named)
│ │ └── tests/ # Integration tests
│ │ ├── evaluator_tests.rs
│ │ └── rule_processor_tests.rs
│ └── rustrules-server/ # Binary crate (Axum HTTP API)
│ └── src/
│ ├── main.rs # Server setup + graceful shutdown
│ ├── routes.rs # Route handlers
│ └── models.rs # Request/response types
└── tests/fixtures/ # Test data
├── rulez.json
└── test_data.json
cargo build # Debug build
cargo build --release # Release build
cargo test # Run all 121 tests
cargo clippy # Lint checkMIT