golang快速便携的非图灵完备表达式求值插件库cel-go的使用
Golang快速便携的非图灵完备表达式求值插件库cel-go的使用
概述
Common Expression Language (CEL) 是一种非图灵完备的语言,设计用于简单性、速度、安全性和可移植性。CEL的类C语法看起来与C++、Go、Java和TypeScript中的等效表达式几乎相同。
// 检查资源名称是否以组名开头
resource.name.startsWith("/groups/" + auth.claims.group)
// 确定请求是否在允许的时间窗口内
request.time - resource.age < duration("24h")
// 检查列表中所有资源名称是否匹配给定过滤器
auth.claims.email_verified && resources.all(r, r.startsWith(auth.claims.email))
环境设置
首先需要设置环境变量,将变量暴露给CEL:
import "github.com/google/cel-go/cel"
env, err := cel.NewEnv(
cel.Variable("name", cel.StringType),
cel.Variable("group", cel.StringType),
)
解析和检查
解析阶段指示表达式在语法上是否有效,并扩展环境中存在的任何宏。解析和检查比评估更耗费计算资源,建议提前进行。
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功能,这些功能可能不需要在所有用例中使用。
// 确保所有推文少于140个字符
tweets.all(t, t.size() <= 140)
// 测试字段是否为非默认值(如果是基于proto的)或在JSON情况下是否定义
has(message.field)
评估
评估是线程安全且无副作用的。许多不同的输入可以发送到同一个cel.Program
,如果字段存在于输入中但未在表达式中引用,则会被忽略。
// `out`变量包含成功评估的输出
// `details`变量如果启用为cel.ProgramOption,将包含中间评估状态
out, details, err := prg.Eval(map[string]interface{}{
"name": "/groups/acme.co/documents/secret-stuff",
"group": "acme.co"})
fmt.Println(out) // 'true'
完整示例
下面是一个完整的示例,展示如何使用cel-go进行表达式求值:
package main
import (
"fmt"
"log"
"github.com/google/cel-go/cel"
)
func main() {
// 1. 创建环境
env, err := cel.NewEnv(
cel.Variable("name", cel.StringType),
cel.Variable("group", cel.StringType),
)
if err != nil {
log.Fatalf("环境创建失败: %v", err)
}
// 2. 解析和检查表达式
ast, issues := env.Compile(`name.startsWith("/groups/" + group)`)
if issues != nil && issues.Err() != nil {
log.Fatalf("类型检查错误: %s", issues.Err())
}
// 3. 创建程序
prg, err := env.Program(ast)
if err != nil {
log.Fatalf("程序构建错误: %s", err)
}
// 4. 评估表达式
out, _, err := prg.Eval(map[string]interface{}{
"name": "/groups/acme.co/documents/secret-stuff",
"group": "acme.co",
})
if err != nil {
log.Fatalf("评估错误: %s", err)
}
// 5. 输出结果
fmt.Printf("结果: %v\n", out) // 输出: 结果: true
}
常见问题
为什么不用JavaScript、Lua或WASM?
JavaScript和Lua是丰富的语言,需要沙箱才能安全执行。当答案不仅仅是O(n)复杂度时,沙箱成本很高。CEL在禁用宏时,评估与表达式大小和输入大小成线性关系。
我需要同时解析和检查吗?
检查是一个可选但强烈建议的CEL表达式验证步骤。在某些情况下,只需解析并依赖运行时绑定和错误处理即可。
在哪里可以了解更多关于语言的信息?
安装
CEL-Go支持modules
并使用语义版本控制。当然,也可以直接从源代码构建。
许可证
根据Apache许可证发布。
更多关于golang快速便携的非图灵完备表达式求值插件库cel-go的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang快速便携的非图灵完备表达式求值插件库cel-go的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang CEL-Go 使用指南
CEL (Common Expression Language) 是 Google 开发的一种非图灵完备的表达式语言,专门设计用于安全评估。cel-go 是 Go 语言的 CEL 实现,非常适合需要表达式求值的场景,如配置验证、策略引擎等。
基本使用
安装
go get github.com/google/cel-go/cel
简单示例
package main
import (
"fmt"
"log"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
"google.golang.org/protobuf/reflect/protoreflect"
)
func main() {
// 创建 CEL 环境
env, err := cel.NewEnv(
cel.Declarations(
decls.NewVar("name", decls.String),
),
)
if err != nil {
log.Fatalf("环境创建失败: %v", err)
}
// 解析表达式
ast, issues := env.Compile(`"Hello, " + name + "!"`)
if issues != nil && issues.Err() != nil {
log.Fatalf("编译错误: %v", issues.Err())
}
// 创建程序
prg, err := env.Program(ast)
if err != nil {
log.Fatalf("程序创建失败: %v", err)
}
// 评估表达式
out, _, err := prg.Eval(map[string]interface{}{
"name": "World",
})
if err != nil {
log.Fatalf("评估失败: %v", err)
}
fmt.Println(out) // 输出: "Hello, World!"
}
高级特性
自定义函数
func main() {
// 定义自定义函数
env, err := cel.NewEnv(
cel.Declarations(
decls.NewVar("input", decls.Int),
decls.NewFunction("double",
decls.NewOverload(
"double_int",
[]*exprpb.Type{decls.Int},
decls.Int,
),
),
),
cel.Functions(
&functions.Overload{
Operator: "double_int",
Unary: func(value ref.Val) ref.Val {
if intVal, ok := value.Value().(int64); ok {
return types.Int(2 * intVal)
}
return types.NewErr("无效的输入类型")
},
},
),
)
// 使用自定义函数
ast, _ := env.Compile(`double(input)`)
prg, _ := env.Program(ast)
out, _, _ := prg.Eval(map[string]interface{}{
"input": 5,
})
fmt.Println(out) // 输出: 10
}
类型检查
func main() {
env, _ := cel.NewEnv(
cel.Declarations(
decls.NewVar("user", decls.NewMapType(decls.String, decls.Dyn)),
)
// 类型安全的表达式
ast, issues := env.Compile(`user.age > 18 && user.name.startsWith("A")`)
if issues != nil {
fmt.Printf("类型检查错误: %v\n", issues.Err())
return
}
prg, _ := env.Program(ast)
out, _, _ := prg.Eval(map[string]interface{}{
"user": map[string]interface{}{
"name": "Alice",
"age": 25,
},
})
fmt.Println(out) // 输出: true
}
性能优化
对于需要重复评估的表达式,可以预先编译:
func main() {
env, _ := cel.NewEnv()
ast, _ := env.Compile(`1 + 2 + 3`)
// 预编译程序
prg, _ := env.Program(ast,
cel.EvalOptions(cel.OptOptimize),
)
// 多次评估
for i := 0; i < 1000; i++ {
out, _, _ := prg.Eval(nil)
_ = out
}
}
实际应用场景
1. 配置验证
func validateConfig(config map[string]interface{}, rule string) bool {
env, _ := cel.NewEnv(
cel.Declarations(
decls.NewVar("config", decls.NewMapType(decls.String, decls.Dyn)),
),
)
ast, issues := env.Compile(rule)
if issues != nil {
return false
}
prg, _ := env.Program(ast)
out, _, _ := prg.Eval(map[string]interface{}{
"config": config,
})
result, ok := out.Value().(bool)
return ok && result
}
// 使用示例
config := map[string]interface{}{
"timeout": 30,
"retries": 3,
}
valid := validateConfig(config, `config.timeout > 0 && config.retries < 5`)
2. 策略引擎
type User struct {
Role string
Region string
Disabled bool
}
func checkAccess(user User, resource string) bool {
env, _ := cel.NewEnv(
cel.Declarations(
decls.NewVar("user", decls.NewObjectType("User")),
decls.NewVar("resource", decls.String),
),
cel.Types(&User{}),
)
policy := `user.Role == 'admin' ||
(user.Role == 'editor' && !user.Disabled && resource.startsWith(user.Region))`
ast, _ := env.Compile(policy)
prg, _ := env.Program(ast)
out, _, _ := prg.Eval(map[string]interface{}{
"user": user,
"resource": resource,
})
result, _ := out.Value().(bool)
return result
}
最佳实践
- 重用环境:创建 CEL 环境开销较大,应尽量重用
- 预编译:对频繁使用的表达式进行预编译
- 限制复杂度:CEL 不是图灵完备的,但复杂表达式仍可能影响性能
- 类型安全:尽可能声明变量类型,利用类型检查功能
- 错误处理:总是检查编译和评估阶段的错误
CEL-Go 提供了安全、高效的表达式求值能力,非常适合策略引擎、配置验证、条件过滤等场景。它的非图灵完备性保证了评估过程的安全性和可预测性。