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

1 回复

更多关于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
}

最佳实践

  1. 重用环境:创建 CEL 环境开销较大,应尽量重用
  2. 预编译:对频繁使用的表达式进行预编译
  3. 限制复杂度:CEL 不是图灵完备的,但复杂表达式仍可能影响性能
  4. 类型安全:尽可能声明变量类型,利用类型检查功能
  5. 错误处理:总是检查编译和评估阶段的错误

CEL-Go 提供了安全、高效的表达式求值能力,非常适合策略引擎、配置验证、条件过滤等场景。它的非图灵完备性保证了评估过程的安全性和可预测性。

回到顶部