cel-expr/cel-go

GitHub: cel-expr/cel-go

Google 开源的 Go 语言通用表达式求值引擎,提供快速、安全、可移植的非图灵完备表达式解析与执行能力。

Stars: 3037 | Forks: 306

# 通用表达式语言 [![Go Report Card](https://goreportcard.com/badge/github.com/google/cel-go)](https://goreportcard.com/report/github.com/google/cel-go) [![GoDoc](https://godoc.org/github.com/google/cel-go?status.svg)][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, 开发工具, 日志审计, 表达式求值, 表达式语言, 解释器