概要
CELでは評価式で扱う変数をEnvironment内で定義しますが、既存のデータモデルを使いたい場合は
- 同じ定義を都度作らないといけない
- 変更があった際の追従漏れが発生する
といった手間が発生してしまいます。
しかしそのデータモデルがprotobufで定義されていれば再利用することが可能です。
今回はその方法を紹介します。
環境
- go v1.22.1
- cel-go v0.20.1
実装
外部protoの例
cel.Types
で型を読み込み、cel.ObjectType
でその型を指定してcel.Variable
で自分の変数として定義します。
import ( ... "github.com/google/cel-go/cel" rpcpb "google.golang.org/genproto/googleapis/rpc/context/attribute_context" "google.golang.org/protobuf/types/known/structpb" tpb "google.golang.org/protobuf/types/known/timestamppb" ) func main() { env, _ := cel.NewEnv( cel.Types(&rpcpb.AttributeContext_Request{}), cel.Variable("request", cel.ObjectType("google.rpc.context.AttributeContext.Request"), ), ) ...
ポイント
ポイントは以下です。
cel.Types
にはdescriptor(protoファイルからgenerateされたGoの型)を書くcel.ObjectType
にはprotoファイルの定義に基づくmessageを書く
すると次のように評価式においてprotobufの定義に基づくオブジェクトとして扱うことが可能です。
ast, iss := env.Compile( `request.auth.claims.group == 'admin' || request.auth.principal == 'user:me@acme.co'`, )
独自protoの例
例えば次のようなprotoを自前で定義して、
syntax = "proto3"; package helloworld; option go_package = "github.com/jun06t/cel-sample/external-proto/proto;helloworld"; message HelloRequest { string name = 1; int32 age = 2; bool man = 3; } message HelloReply { string message = 1; }
これを先ほどと同じようにEnvにセットし、評価式もmessageのフィールドを使うように与えます。
import ( ... pb "github.com/jun06t/cel-sample/external-proto/proto" ... ) main() env, _ := cel.NewEnv( cel.Types(&pb.HelloRequest{}), cel.Variable("request", cel.ObjectType("helloworld.HelloRequest"), ), ) ast, iss := env.Compile( `request.name == 'Alice' && request.age > 20 && request.man == false`, ) ...
入力値を与えて評価すると、
input := map[string]any{ "request": &pb.HelloRequest{ Name: "Alice", Age: 21, Man: false, }, } out, _, err := prog.Eval(input) if err != nil { log.Fatalf("Evaluation error: %v", err) } fmt.Println("Is permitted user?", out)
期待通りの結果になります。
$ go run main.go
Is permitted user? true
その他
サンプルコード
今回のサンプルコードはこちらです。
まとめ
既存のデータモデルを扱う場合にproto定義を利用することで二重管理の負債を避けることができます。