gdt050579/whereexpr
GitHub: gdt050579/whereexpr
一个用于在类型化记录上评估布尔过滤表达式的 Rust 库,解决在运行时对任意结构体进行灵活条件筛选的问题。
Stars: 1 | Forks: 0
# whereexpr
一个用于在类型化记录上评估布尔过滤表达式的 Rust 库——就像 `WHERE` 子句,但适用于你自己的类型。
## 它的作用
`whereexpr` 允许你传入一个类似如下的字符串:
```
cond_1 && (cond_2 || cond_3)
```
其中每个命名条件都是一个字段级测试,例如:
```
age > 30
surname is-one-of [Doe, Smith, Williams] {ignore-case}
```
……并将其针对任意结构体实例在运行时求值——无需宏。
它清晰地分离了两个关注点:
- **条件(Conditions)** — 每个字段的谓词(如 `age > 30`、`name starts-with Jo`、`status is active`)
- **表达式(Expressions)** — 在命名条件上的布尔组合(如 `cond_a && !cond_b || (cond_c && cond_d)`)
## 快速示例
```
use whereexpr::*;
struct Person {
name: String,
surname: String,
age: u32,
}
impl Person {
const NAME: AttributeIndex = AttributeIndex::new(0);
const SURNAME: AttributeIndex = AttributeIndex::new(1);
const AGE: AttributeIndex = AttributeIndex::new(2);
}
impl Attributes for Person {
fn get(&self, idx: AttributeIndex) -> Option> {
match idx {
Self::NAME => Some(Value::String(&self.name)),
Self::SURNAME => Some(Value::String(&self.surname)),
Self::AGE => Some(Value::U32(self.age)),
_ => None,
}
}
fn kind(idx: AttributeIndex) -> Option {
match idx {
Self::NAME => Some(ValueKind::String),
Self::SURNAME => Some(ValueKind::String),
Self::AGE => Some(ValueKind::U32),
_ => None,
}
}
fn index(name: &str) -> Option {
match name {
"name" => Some(Self::NAME),
"surname" => Some(Self::SURNAME),
"age" => Some(Self::AGE),
_ => None,
}
}
}
fn main() {
let expr = ExpressionBuilder::::new()
.add("is_john", Condition::from_str("name is John"))
.add("known_fam", Condition::from_str("surname is-one-of [Doe, Smith, Williams] {ignore-case}"))
.add("adult_plus",Condition::from_str("age > 30"))
.build("is_john && known_fam && adult_plus")
.unwrap();
let person = Person {
name: "John".to_string(),
surname: "doe".to_string(),
age: 33,
};
println!("matches: {}", expr.matches(&person)); // matches: true
}
```
## 工作原理
### 1. 实现 `Attributes`
`Attributes` 特性是库与你类型之间的桥梁。你需要实现三个方法:
| 方法 | 用途 |
|---|---|
| `get(idx)` | 返回给定索引的字段值 |
| `kind(idx)` | 返回字段索引的静态 `ValueKind` |
| `index(name)` | 将字段名称字符串映射到其 `AttributeIndex` |
### 2. 定义条件
条件是命名规则,每个规则测试结构体的一个字段。
**从字符串(条件 DSL):**
```
Condition::from_str("age >= 18")
Condition::from_str("status is-not banned {ignore-case}")
Condition::from_str("score in-range [0, 100]")
```
**程序化方式:**
```
let pred = Predicate::with_value(Operation::GreaterThan, 18u32)?;
Condition::new("age", pred)
```
### 3. 构建表达式
使用 `ExpressionBuilder` 注册你的条件,并传入一个布尔表达式字符串:
```
let expr = ExpressionBuilder::::new()
.add("rule_a", Condition::from_str("field_x is foo"))
.add("rule_b", Condition::from_str("field_y > 10"))
.build("rule_a && !rule_b")
.unwrap();
```
### 4. 求值
```
// Panics on type mismatch or missing field
let result: bool = expr.matches(&my_value);
// Returns None on type mismatch or missing field
let result: Option = expr.try_matches(&my_value);
```
编译后的 `Expression` 可复用——只需构建一次,然后在多个值上调用 `matches`。
## 虚拟属性
`Attributes::get` 不必返回存储的字段——它可以返回**任意计算值**。这使你可以在不存储的情况下,基于类型的派生属性进行过滤。
计算值是**惰性求值**的——仅当表达式实际到达引用它们的条件时才会计算。
## 条件 DSL 语法
条件字符串的形式如下:
```
[]
```
**单一值:**
```
name is Alice
status is-not active
age > 30
created-at < 1700000000
```
**列表值** — 一个或多个逗号分隔的项,用 `[ ]` 包裹:
```
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]
```
**带通配符的列表** — 通配符操作也接受列表;如果属性匹配**任意**一个模式即匹配:
```
full_path glob-match [**/*.rs, **/*.md, **/Cargo.*]
filename not-glob [*.tmp, *.bak, *.swp]
```
**日期时间字符串** — `DateTime` 属性接受 Unix 时间戳和
`YYYY-MM-DD` 格式的人可读日期字符串:
```
modified_at > 2024-01-01
created_at in-range [2020-01-01, 2024-12-31]
expires_at < 1700000000
```
**修饰符** 出现在末尾的 `{...}` 中:
| 修饰符 | 效果 |
|---|---|
| `{ignore-case}` | 忽略大小写匹配(字符串与路径) |
## 操作
| 操作 | DSL 关键字 |
|---|---|
| 相等 | `is`、`eq`、`==` |
| 不等 | `is-not`、`neq`、`!=` |
| 属于列表之一 | `is-one-of`、`in` |
| 不属于列表 | `is-not-one-of`、`not-in` |
| 以……开头 | `starts-with` |
| 不以……开头 | `not-starts-with` |
| 以列表中某一项开头 | `starts-with-one-of` |
| 不以任意项开头 | `not-starts-with-one-of` |
| 以……结尾 | `ends-with` |
| 不以……结尾 | `not-ends-with` |
| 以列表中某一项结尾 | `ends-with-one-of` |
| 不以任意项结尾 | `not-ends-with-one-of` |
| 包含 | `contains` |
| 不包含 | `not-contains` |
| 包含列表中某一项 | `contains-one-of` |
| 不包含任意项 | `not-contains-one-of` |
| 通配符匹配 | `glob`、`glob-match` |
| 通配符不匹配 | `not-glob`、`not-glob-match` |
| 大于 | `>`、`gt`、`greater-than` |
| 大于等于 | `>=`、`gte`、`greater-than-or-equal` |
| 小于 | `<`、`lt`、`less-than` |
| 小于等于 | `<=`、`lte`、`less-than-or-equal` |
| 在范围内(包含) | `in-range` |
| 不在范围内 | `not-in-range` |
## 表达式语法
布尔表达式组合命名条件规则:
| 语法 | 含义 |
|---|---|
| `rule_a && rule_b` | AND(也接受 `and`) |
| `rule_a \|\| rule_b` | OR(也接受 `or`) |
| `!rule_a` 或 `~rule_a` | NOT(也接受 `not`) |
| `(rule_a \|\| rule_b) && rule_c` | 使用括号分组 |
规则:
- 规则名称必须以字母开头,且只包含字母、数字、`_` 和 `-`。
- `AND` 和 `OR` 不能在相同括号层级混合使用,除非有显式分组。
- 括号可嵌套最多 8 层。
## 支持的值类型
| `ValueKind` | Rust 类型 | DSL 名称 |
|---|---|---|
| `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 时间戳) | `datetime` |
## 特性
| 特性 | 效果 |
|---|---|
| `error_description` | 在 `Error`、`Operation` 和 `ValueKind` 上启用 `Display` 和 `description()`,提供人类可读的错误信息 |
在 `Cargo.toml` 中启用:
```
[dependencies]
whereexpr = { version = "0.1", features = ["error_description"] }
```
## 许可证
MIT
标签:AND, crate, is active, is-one-of, NOT, OR, Rust, starts-with, WHERE子句, 云计算, 函数式编程, 可视化界面, 字段级条件, 属性索引, 布尔表达式, 开源, 忽略大小写, 数值比较, 无宏, 条件过滤, 查询DSL, 类型安全, 编译时, 网络流量审计, 表达式求值, 表达式组合, 规则引擎, 谓词, 过滤引擎, 运行时, 通知系统, 集合成员