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, 类型安全, 编译时, 网络流量审计, 表达式求值, 表达式组合, 规则引擎, 谓词, 过滤引擎, 运行时, 通知系统, 集合成员