golang-github-google-cel-go:轻量级表达式语言,高效安全的规则引擎与动态计算工具

Common Expression Language (CEL) 是简洁、快速、安全的非图灵完备语言,支持C类语法,适用于轻量级规则评估,可嵌入Go应用实现高效动态逻辑计算。【此简介由AI生成】

分支1Tags0
文件最后提交记录最后更新时间
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前

通用表达式语言

Go 项目报告卡 Go 文档

通用表达式语言(CEL)是一种非图灵完备的语言,其设计理念强调简洁性、执行效率、安全性和跨平台兼容性。CEL 采用类 C 的语法结构,与 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 中标记为 javagotypescript,以展示语法的通用性。

当完全沙盒化的脚本语言资源消耗过大时,CEL 是进行轻量级表达式求值的理想选择。要快速入门,请尝试 Codelab

可通过 此仪表盘 查看 cel-go 一致性测试结果。



概述

确定要提供给 CEL 的变量和函数。解析并校验表达式以确保其有效性,然后根据输入对生成的抽象语法树(AST)进行求值。虽然校验是可选的,但强烈建议执行此步骤。

环境配置

让我们通过 cel.Declarations 环境选项向 CEL 暴露 namegroup 变量:

import "github.com/google/cel-go/cel"

env, err := cel.NewEnv(
    cel.Variable("name", cel.StringType),
    cel.Variable("group", cel.StringType),
)

至此,环境已准备就绪,可用于解析和类型检查。
CEL 不仅支持所有常见的基本类型,还支持列表、映射,以及对 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 语法, 会带来不必要的语法负担和复杂性。宏在解析阶段展开, 其展开内容会在检查阶段进行类型验证。

例如,启用宏后即可支持有界迭代/折叠操作符。其中 allexistsexists_onefiltermap 等宏对于针对列表和映射值 执行单一谓词判断尤为实用。

// 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 等动态数据类型的求值需求。

以下真值表中,符号 <x><y> 表示错误或未知值,? 表示因短路求值未执行的分支。当结果为 <x, y> 时,表示两个参数都可能影响结果:

表达式 结果
false && ? false
true && false false
<x> && false false
true && true true
true && <x> <x>
<x> && true <x>
<x> && <y> <x, y>
true || ? true
false || true true
<x> || true true
false || false false
false || <x> <x>
<x> || false <x>
<x> || <y> <x, y>

若预期存在未知值,应启用 cel.EvalOptions(cel.OptTrackState)Eval() 返回的 details 将包含中间求值结果,可传递给 interpreter.Prune 函数生成剩余表达式。例如:

// Residual when `name` omitted:
name.startsWith("/groups/acme.co")

当某些变量在非必要情况下计算成本较高时,这项技术会非常有用。该功能将成为未来多项改进的重点,敬请期待更多优化!

错误提示

解析与检查错误会提供友好的提示信息,并直接指向源码中问题出现的位置:

ERROR: <input>:1:40: undefined field 'undefined'
    | TestAllTypes{single_int32: 1, undefined: 2}
    | .......................................^`,

解析和检查后的表达式均包含输出AST中每个节点的源代码位置信息。这些信息同样可用于评估时确定错误位置。

安装

CEL-Go支持modules并采用语义化版本控制。更多信息请参阅Go Modules文档。当然,您也可以选择直接从源码构建。

常见问题

为何不选用JavaScript、Lua或WASM?

JavaScript和Lua是功能丰富的语言,需要沙箱环境才能安全执行。沙箱机制成本高昂,当评估内容复杂度超过O(n)时,会极大影响"允许用户评估什么内容"的决策。

CEL在禁用宏的情况下,其评估耗时与表达式大小及输入数据呈线性关系。除内置函数外,唯一可调用的函数由宿主环境提供。虽然扩展函数可能更复杂,但这取决于嵌入CEL的应用自身的选择。

那么为何不选用WASM?WASM在某些应用中是绝佳选择,远优于嵌入式JavaScript和Lua,但它不支持垃圾回收,且非基本对象类型需要跨模块调用,代价较高。在多数场景中,CEL对其目标用例而言速度更快且同样便携,不过对于node.js和基于Web的执行环境,CEL未来也可能提供直接编译为WASM的评估器。

必须同时进行解析和检查吗?

检查是CEL表达式验证中的可选步骤,但强烈建议执行。某些情况下仅进行解析,依赖运行时绑定和错误处理机制也是可行的。

如何深入了解这门语言?

如何了解内部实现原理?

  • 查看GoDoc学习如何将CEL集成到Go服务中
  • 参考正在开发的CEL C++工具链,了解如何将CEL评估集成到其他环境

如何参与贡献?

部分测试无法通过go test运行?

少量测试依赖Bazel环境。特别是检查阶段的动态proto支持以及一致性测试驱动程序,需要Bazel协调测试输入:

bazel test ...

许可证

基于 Apache 许可证 发布。

免责声明:此非 Google 官方产品。

项目介绍

Common Expression Language (CEL) 是简洁、快速、安全的非图灵完备语言,支持C类语法,适用于轻量级规则评估,可嵌入Go应用实现高效动态逻辑计算。【此简介由AI生成】

定制我的领域