Condition Matcher
Why? Rust's match statement is arguably one of the most powerful and flexible features of the language. It allows you to match against a value and execute different code blocks based on the value. However, if you want to match against user-defined rules, you'd need to write a lot of boilerplate code.
What? This library provides a flexible and type-safe condition matching library for Rust with automatic struct field access. It allows you to create matchers that can be used to match against values and execute different code blocks based on the value.
Features
- Automatic struct matching with derive macro
- Multiple matching modes with support for nested conditions
- Support for various condition types (value, length, type, field)
- Numeric comparisons on fields (>, <, >=, <=)
- String operations (contains, starts_with, ends_with)
- Regex matching (optional feature)
- Optional field handling (Option support)
- Detailed match results with error information
- Builder pattern for ergonomic API
- Serde support (optional feature)
- Parallel processing (optional feature)
- Zero-cost abstractions with compile-time type safety
Installation
Add to your Cargo.toml:
[]
= "0.2.0"
# Optional features
= { = "0.2.0", = ["serde", "regex", "json_condition", "parallel"] }
# Or all features
= { = "0.2.0", = ["full"] }
Quick Start
There are two main concepts in this library:
Matcher: A matcher is a collection of conditions that are evaluated against some data.Matchable: A matchable is a type (data) that conditions can be evaluated against.
The Matcher trait is implemented by RuleMatcher and JsonMatcher. The primary difference is that a JsonMatcher is constructed from a JSON input value.
A Matcher can be created in a few different ways:
- By using the
MatcherBuilderto construct the ruleset in Rust code. This creates aRuleMatcher. - By using a JSON string or
serde_json::Valueto construct aJsonMatcher.
A Matchable can be a basic type, like a string or number, or a complex type, like a struct. To make a complex type matchable, you can derive the Matchable trait.
It is also possible to implement the Matchable trait for your own types and more complex use-cases, like a database cache layer. More details can be found in the examples.
Basic Usage
use ;
// Simply derive Matchable to get automatic field access!
let user = User ;
// Create a matcher with AND mode
let mut matcher = new;
matcher
.add_condition
.add_condition;
assert!;
Builder API
For a more ergonomic experience, use the builder pattern:
use ;
let matcher = new
.mode
.length_gte
.value_not_equals
.build;
assert!;
Or use the field condition builder:
use ;
let condition = .gte;
let mut matcher = new;
matcher.add_condition;
JSON Condition Usage
Your rules can be stored in a database or a config file. To enable using stored rules, you can create a JsonMatcher from a JSON string or serde_json::Value.
Here's a quick example of how a JSON value can be used to create a JsonMatcher:
let conditions = r#"{
"mode": "OR",
"nested": [
{
"mode": "AND",
"rules": [
{"field": "price", "operator": "greater_than", "value": 500}
]
},
{
"mode": "AND",
"rules": [
{"field": "price", "operator": "less_than", "value": 100},
{"field": "in_stock", "operator": "equals", "value": false}
]
}
]
}"#;
let matcher = from_json.unwrap;
Matching Modes
AND Mode
All conditions must match:
let mut matcher = new;
OR Mode
At least one condition must match:
let mut matcher = new;
Note: More matching modes will be included in future versions. For example
XORto match exactly one condition.
Condition Types
Value Matching
Condition
Length Matching
Condition
Field Value Matching
Field value matching has a built-in implementation for all basic types within a struct. Complex types need to implement the
get_fieldandget_field_pathmethods from theMatchabletrait to handle arbitrary field access.
Condition
NOT Operator
let inner = Condition ;
Condition
Nested Conditions
Nested conditions are supported by the NestedCondition struct.
let nested = NestedCondition ;
Supported Operators
| Operator | Description | Works With |
|---|---|---|
Equals |
Exact equality | All types |
NotEquals |
Inequality | All types |
GreaterThan |
Greater than | Numeric types, strings |
LessThan |
Less than | Numeric types, strings |
GreaterThanOrEqual |
Greater or equal | Numeric types, strings |
LessThanOrEqual |
Less or equal | Numeric types, strings |
Contains |
Contains substring | Strings |
NotContains |
Does not contain | Strings |
StartsWith |
Starts with prefix | Strings |
EndsWith |
Ends with suffix | Strings |
Regex |
Matches regex pattern | Strings (requires regex feature) |
IsEmpty |
Check if empty | Strings, collections |
IsNotEmpty |
Check if not empty | Strings, collections |
IsNone |
Check if Option is None | Option types |
IsSome |
Check if Option is Some | Option types |
Supported Types
The matcher automatically supports comparison for:
- Integers:
i8,i16,i32,i64,i128,isize - Unsigned:
u8,u16,u32,u64,u128,usize - Floats:
f32,f64 - Other:
bool,char,String,&str
Detailed Results
Get detailed information about why a match succeeded or failed:
let result = matcher.run_detailed.unwrap;
println!;
println!;
println!;
for condition in result.condition_results
Error Handling
The library provides detailed error information:
use MatchError;
match matcher.run
Optional Features
Serde Support
Enable serialization/deserialization of operators and modes:
= { = "0.1.0", = ["serde"] }
Regex Support
Enable regex pattern matching:
= { = "0.1.0", = ["regex"] }
Condition
All Features
= { = "0.1.0", = ["full"] }
Custom Types
To make your custom type matchable, simply derive Matchable:
The derive macro automatically:
- Implements field access for all named fields
- Handles
Option<T>fields by unwrapping when present - Returns
Nonefor missing optional fields
Examples
Run the examples to see the library in action:
License
MIT
Contributing
Contributions are welcome! Please feel free to submit an issue for a feature request or bug report, or a Pull Request if you'd like to add something.