whereexpr
A Rust library for evaluating boolean filter expressions over typed records — like a WHERE clause, but for your own types.
What it does
whereexpr lets you take a string like:
cond_1 && (cond_2 || cond_3)
where each named condition is a field-level test like:
age > 30
surname is-one-of [Doe, Smith, Williams] {ignore-case}
...and evaluate the whole thing against any instance of your struct — at runtime, with no macros.
It separates two concerns cleanly:
- Conditions — per-field predicates (
age > 30,name starts-with Jo,status is active) - Expressions — boolean combinators over named conditions (
cond_a && !cond_b || (cond_c && cond_d))
Quick example
use *;
How it works
1. Implement Attributes
The Attributes trait is the bridge between the library and your type. You implement three methods:
| Method | Purpose |
|---|---|
get(idx) |
Return the field value for the given index |
kind(idx) |
Return the static ValueKind for a field index |
index(name) |
Map a field name string to its AttributeIndex |
2. Define conditions
Conditions are named rules, each testing one field of your struct.
From a string (condition DSL):
from_str
from_str
from_str
Programmatically:
let pred = with_value?;
new
3. Build an expression
Register your conditions with ExpressionBuilder and pass a boolean expression string:
let expr = new
.add
.add
.build
.unwrap;
4. Evaluate
// Panics on type mismatch or missing field
let result: bool = expr.matches;
// Returns None on type mismatch or missing field
let result: = expr.try_matches;
The compiled Expression is reusable — build it once and call matches on many values.
Virtual attributes
Attributes::get does not have to return stored fields — it can return any computed value. This lets you filter on derived properties of your type without storing them.
use *;
let expr = new
.add
.add
.add
.build
.unwrap;
Computed values are evaluated lazily — only when the expression actually reaches the condition that references them.
Condition DSL syntax
A condition string has the form:
<attribute> <operation> <value> [<modifiers>]
Single value:
name is Alice
status is-not active
age > 30
created-at < 1700000000
List value — one or more comma-separated entries inside [ ]:
score in-range [1, 100]
role is-one-of [admin, moderator] {ignore-case}
path ends-with-one-of [.log, .tmp]
tag contains-one-of [warn, error, fatal]
Glob with a list — the glob operation also accepts a list; the attribute matches if it matches any of the patterns:
full_path glob-match [**/*.rs, **/*.md, **/Cargo.*]
filename not-glob [*.tmp, *.bak, *.swp]
DateTime strings — DateTime attributes accept both raw Unix timestamps and
human-readable date strings in YYYY-MM-DD format:
modified_at > 2024-01-01
created_at in-range [2020-01-01, 2024-12-31]
expires_at < 1700000000
Modifiers appear at the end inside {...}:
| Modifier | Effect |
|---|---|
{ignore-case} |
Case-insensitive match (strings and paths) |
Operations
| Operation | DSL keywords |
|---|---|
| Equality | is, eq, == |
| Inequality | is-not, neq, != |
| One of a list | is-one-of, in |
| Not one of | is-not-one-of, not-in |
| Starts with | starts-with |
| Does not start with | not-starts-with |
| Starts with one of | starts-with-one-of |
| Does not start with any | not-starts-with-one-of |
| Ends with | ends-with |
| Does not end with | not-ends-with |
| Ends with one of | ends-with-one-of |
| Does not end with any | not-ends-with-one-of |
| Contains | contains |
| Does not contain | not-contains |
| Contains one of | contains-one-of |
| Contains none of | not-contains-one-of |
| Glob pattern match | glob, glob-match |
| Glob pattern no match | not-glob, not-glob-match |
| Greater than | >, gt, greater-than |
| Greater than or equal | >=, gte, greater-than-or-equal |
| Less than | <, lt, less-than |
| Less than or equal | <=, lte, less-than-or-equal |
| In range (inclusive) | in-range |
| Not in range | not-in-range |
Expression syntax
Boolean expressions combine named condition rules:
| Syntax | Meaning |
|---|---|
rule_a && rule_b |
AND (and also accepted) |
rule_a || rule_b |
OR (or also accepted) |
!rule_a or ~rule_a |
NOT (not also accepted) |
(rule_a || rule_b) && rule_c |
Grouping with parentheses |
Rules:
- Rule names must start with a letter and contain only letters, digits,
_, and-. ANDandORcannot be mixed at the same parenthesis level without explicit grouping.- Parentheses can be nested up to 8 levels deep.
Supported value types
ValueKind |
Rust type | DSL name |
|---|---|---|
String |
&str |
string |
Path |
&[u8] |
path |
Bool |
bool |
bool |
U8–U64 |
u8–u64 |
u8–u64 |
I8–I64 |
i8–i64 |
i8–i64 |
F32, F64 |
f32, f64 |
f32, f64 |
Hash128/160/256 |
[u8; N] |
hash128, hash160, hash256 |
IpAddr |
std::net::IpAddr |
ip |
DateTime |
u64 (Unix timestamp) |
datetime |
Features
| Feature | Effect |
|---|---|
error_description |
Enables Display and description() on Error, Operation, and ValueKind for human-readable error messages |
Enable in Cargo.toml:
[]
= { = "0.1", = ["error_description"] }
License
MIT