cel-expr/cel-go
GitHub: cel-expr/cel-go
Google 开源的 Go 语言通用表达式求值引擎,提供快速、安全、可移植的非图灵完备表达式解析与执行能力。
Stars: 3037 | Forks: 306
# 通用表达式语言
[](https://goreportcard.com/report/github.com/google/cel-go)
[][6]
通用表达式语言 (CEL) 是一种非图灵完备的语言,专为
简洁性、速度、安全性和可移植性而设计。CEL 类似 C 语言的[语法][1]
看起来与 C++、Go、Java 和 TypeScript 中的等效表达式几乎完全相同。
```
// Check whether a resource name starts with a group name.
resource.name.startsWith("/groups/" + auth.claims.group)
```
```
// Determine whether the request is in the permitted time window.
request.time - resource.age < duration("24h")
```
```
// Check whether all resource names in a list match a given filter.
auth.claims.email_verified && resources.all(r, r.startsWith(auth.claims.email))
```
一个 CEL “程序”就是一个单一的表达式。示例在 markdown 中被标记为
`java`、`go` 和 `typescript`,以展示语法的通用性。
当完全沙箱化的脚本语言资源消耗过大时,CEL 非常适合用于
轻量级的表达式求值。要开始使用,请尝试
[Codelab](https://codelabs.developers.google.com/codelabs/cel-go/index.html)。
* [概述](#overview)
* [环境设置](#environment-setup)
* [解析与检查](#parse-and-check)
* [宏](#macros)
* [求值](#evaluate)
* [错误](#Errors)
* [示例](examples/README.md)
* [安装](#install)
* [常见问题](#common-questions)
* [许可证](#license)
## 概述
确定要提供给 CEL 的变量和函数。解析并
检查表达式以确保其有效。然后针对某些输入对输出的 AST
进行求值。检查是可选的,但强烈建议执行。
### 环境设置
让我们使用 `cel.Variable`
环境选项向 CEL 暴露 `name` 和 `group` 变量:
```
import "github.com/google/cel-go/cel"
env, err := cel.NewEnv(
cel.Variable("name", cel.StringType),
cel.Variable("group", cel.StringType),
)
```
就是这样。环境已准备好用于解析和类型检查。
CEL 支持所有常见的原始类型以及列表、map,同时
也对 JSON 和 Protocol Buffers 提供了一等公民支持。
### 解析与检查
解析阶段会指示表达式在语法上是否有效,并
展开环境中存在的任何宏。解析和检查的
计算成本比求值更高,因此建议
提前对表达式进行解析和检查。
为了方便起见,解析和检查阶段被合并到了 `Compile`
步骤中:
```
ast, issues := env.Compile(`name.startsWith("/groups/" + group)`)
if issues != nil && issues.Err() != nil {
log.Fatalf("type-check error: %s", issues.Err())
}
prg, err := env.Program(ast)
if err != nil {
log.Fatalf("program construction error: %s", err)
}
```
在解析和检查结束时生成的 `cel.Program` 是无状态的、
线程安全的,并且是可缓存的。
类型检查是一个可选但强烈建议的步骤,它可以通过
静态分析拒绝一些语义无效的表达式。此外,检查
产生的元数据可以提高函数调用的性能以及
在求值时对象字段的选择速度。
#### 宏
宏是可选的,但默认是启用的。引入宏是为了
支持可选的 CEL 功能,如果在所有用例中并不都需要这些功能,
那么如果将它们作为核心 CEL 语法的一部分,可能会带来
语法负担和复杂性。宏在解析时展开,
其展开内容会在检查时进行类型检查。
例如,当启用宏时,可以支持有界
迭代 / 折叠操作符。宏 `all`、`exists`、`exists_one`、`filter`
和 `map` 对于针对 list 和 map 值评估单个谓词特别有用。
```
// Ensure all tweets are less than 140 chars
tweets.all(t, t.size() <= 140)
```
`has` 宏对于统一 protobuf 类型和动态(类 JSON)类型的
字段存在性测试逻辑非常有用。
```
// Test whether the field is a non-default value if proto-based, or defined
// in the JSON case.
has(message.field)
```
这两种情况通常在语言层面需要特殊的语法,但在
CEL 中这些功能是通过宏来暴露的。
### 求值
现在,为了乐趣和收益进行求值。求值是线程安全且无副作用的。
许多不同的输入可以发送到同一个 `cel.Program`,如果
字段存在于输入中,但未在表达式中被引用,它们将被
忽略。
```
// The `out` var contains the output of a successful evaluation.
// The `details' var would contain intermediate evaluation state if enabled as
// a cel.ProgramOption. This can be useful for visualizing how the `out` value
// was arrive at.
out, details, err := prg.Eval(map[string]interface{}{
"name": "/groups/acme.co/documents/secret-stuff",
"group": "acme.co"})
fmt.Println(out) // 'true'
```
#### 部分状态
如果之前没有提供 `name` 怎么办?CEL 就是为这种情况设计的。在
分布式应用中,拥有边缘缓存和中心服务是很常见的。
如果可能,求值应该在边缘进行,但并不总是能够
知道 CEL 表达式中存在的所有值和函数所需的完整状态。
为了提高在部分状态下成功求值的几率,CEL 使用了
可交换的逻辑操作符 `&&`、`||`。如果在左侧遇到
错误或未知值(不是同一回事),右侧也会进行
求值以确定结果。虽然没有此功能也有可能实现
部分状态的求值,但选择这种方法是因为它符合 SQL 求值的
语义,并且对于针对动态数据类型(如 JSON 输入)的求值更具
鲁棒性。
在下面的真值表中,符号 `` 和 `` 代表错误或
未知值,其中 `?` 表示由于短路而未采用该分支。
当结果为 `` 时,这意味着两个参数
都可能与结果相关。
| 表达式 | 结果 |
|---------------------|----------|
| `false && ?` | `false` |
| `true && false` | `false` |
| ` && false` | `false` |
| `true && true` | `true` |
| `true && ` | `` |
| ` && true` | `` |
| ` && ` | `` |
| `true \|\| ?` | `true` |
| `false \|\| true` | `true` |
| ` \|\| true` | `true` |
| `false \|\| false` | `false` |
| `false \|\| ` | `` |
| ` \|\| false` | `` |
| ` \|\| ` | `` |
在预期会出现未知值的情况下,应该启用
`cel.EvalOptions(cel.OptTrackState)`。`Eval()` 返回的 `details` 值将包含
中间求值结果,并可以提供给 `interpreter.Prune`
函数以生成残差表达式。例如:
```
// Residual when `name` omitted:
name.startsWith("/groups/acme.co")
```
当有些变量计算成本很高,除非绝对需要,否则不应该计算时,这种技术会非常有用。此功能将是许多未来改进的重点,所以请留意这里的更多惊喜!
### 错误
解析和检查错误具有友好的错误信息,并带有指向源中
问题发生位置的指针:
```
ERROR: :1:40: undefined field 'undefined'
| TestAllTypes{single_int32: 1, undefined: 2}
| .......................................^`,
```
解析和检查后的表达式都包含有关
输出 AST 中出现的每个节点的源位置信息。此信息也可以
用于确定求值时的错误位置。
## 安装
CEL-Go 支持 `modules` 并使用语义化版本控制。有关更多信息,
请参阅 [Go Modules](https://github.com/golang/go/wiki/Modules) 文档。
当然,始终也可以选择直接从源码构建。
## 常见问题
### 为什么不使用 JavaScript、Lua 或 WASM?
JavaScript 和 Lua 是功能丰富的语言,需要沙箱化才能安全
执行。沙箱化的成本很高,并且在回答“我会让用户
求值什么?”这个问题时,如果答案的复杂度超过 O(n),
沙箱化的影响就会非常大。
当禁用宏时,CEL 的求值相对于表达式的大小和正在
求值的输入呈线性关系。除了内置函数外,可能调用的唯一
函数由宿主环境提供。虽然
扩展函数可能更复杂,但这是嵌入 CEL 的应用程序
做出的选择。
但是,为什么不使用 WASM 呢?对于某些应用来说,WASM 是一个极好的选择,而且
远优于嵌入式的 JavaScript 和 Lua,但它不支持
垃圾回收,并且非原始对象类型需要跨模块进行
成本较高的调用。在大多数情况下,对于其预期用例,CEL 会更快并且同样具有
可移植性,尽管对于 node.js 和基于 Web 的执行,CEL
也可能提供一个直接编译为 WASM 的 WASM 求值器。
### 我需要同时解析_和_检查吗?
检查是 CEL 表达式验证中一个可选但强烈建议的步骤。
在某些情况下,仅仅进行解析并依赖
运行时绑定和错误处理来正确处理就足够了。
### 在哪里可以了解有关该语言的更多信息?
* 有关规范和一致性测试套件,请参阅 [CEL Spec][1]。
* 在 [CEL Go Discuss][2] Google 群组寻求支持。
### 在哪里可以了解有关内部原理的更多信息?
* 请参阅 [GoDoc][6] 了解如何将 CEL 集成到使用 Go 编写的服务中。
* 请参阅 [CEL C++][3] 工具链(正在开发中),了解有关如何
将 CEL 求值集成到其他环境中的信息。
### 某些测试不适用于 `go test`?
少数测试依赖于 [Bazel][5]。特别是检查时的
动态 proto 支持以及一致性测试驱动程序需要 Bazel 来协调
测试输入:
```
bazel test ...
```
## 许可证
在 [Apache License](LICENSE) 下发布。
标签:EVTX分析, Go, Homebrew安装, Ruby工具, SOC Prime, 开发工具, 日志审计, 表达式求值, 表达式语言, 解释器