0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CEL(Common Expression Language)でGo言語の構造体を処理

Posted at

式言語 CEL(Common Expression Language) の Go 言語用ライブラリとして cel-go があります。

cel-go では、一般的なプリミティブ型に加えて list, map, JSON, Protocol Buffers の型に対応していますが、ソースコードを見る限り Go の構造体も処理できそうだったので試してみました。

はじめに

基本的に、cel-go は以下の流れで実装します。

  1. Environment のセットアップ
    • NewEnv の実行
  2. CEL式のパースとチェック
    • Compile の実行
    • Program の実行
  3. 評価
    • Eval の実行

NewEnv で使用する変数や追加の型を定義し、CEL式を CompileProgram で処理して、Eval へ変数の値を渡すとCEL式を評価した結果が得られます。

例えば、name という変数を使って size(name) > 3 (name 変数値の文字数が 3より大きいかどうか)というCEL式を評価する処理は以下のようになります。(エラー処理は適当です)

sample.go
package main

import (
	"fmt"
	"log"
	"os"

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

func main() {
	name := os.Args[1]
    // 1. Environment のセットアップ
	env, err := cel.NewEnv(cel.Variable("name", cel.StringType))

	if err != nil {
		log.Fatal(err)
	}
    // 2. パースとチェック
	ast, iss := env.Compile("size(name) > 3")

	if iss.Err() != nil {
		log.Fatal(iss.Err())
	}

	prg, err := env.Program(ast)

	if err != nil {
		log.Fatal(err)
	}
    // 3. 評価
	out, _, err := prg.Eval(map[string]any{
		"name": name,
	})

	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(out)
	fmt.Printf("type=%v, value=%v \n", out.Type(), out.Value())
}

実行結果は以下の通りです。

実行例1
$ go run sample.go abc
false
type=bool, value=false
実行例2
$ go run sample.go abcd
true
type=bool, value=true

構造体の処理

それでは、本題の構造体です。

正式な実装方法に関しては記載が無くて分からなかったのですが、とりあえず NewEnv で以下のようにすれば実現できました。

  • ext.NativeTypes で Go の構造体を定義
  • 変数の型指定で ObjectType を使って、構造体の型名(package名.構造体名)を文字列で指定

とりあえず、ext.NativeTypes の引数としては reflect.Type で良さそうだったので、reflect.TypeFor の戻り値を使っています。

また、以下では main パッケージ内で構造体 Data を定義しているので、ObjectType の引数は main.Data になります。

処理するCEL式は、Data 構造体の ID や Value の値を文字列として連結するだけのものですが、int 型の値を文字列と + で直接連結できなかったので string(intの値) を使って文字列化しています。

main.go
package main

import (
	"fmt"
	"log"
	"reflect"

	"github.com/google/cel-go/cel"
	"github.com/google/cel-go/ext"
)

type Data struct {
	ID    string
	Value int
}

func main() {
	env, err := cel.NewEnv(
		cel.Variable("data", cel.ObjectType("main.Data")), // 変数の型に Data 構造体を使用
		ext.NativeTypes(reflect.TypeFor[Data]()), // Data 構造体を使用するための型設定
	)

	if err != nil {
		log.Fatal(err)
	}

	ast, iss := env.Compile(`"id=" + data.ID + ", value=" + string(data.Value)`)

	if iss.Err() != nil {
		log.Fatal(iss.Err())
	}

	prg, err := env.Program(ast)

	if err != nil {
		log.Fatal(err)
	}

	d := Data{"d-1", 1000}

	out, _, err := prg.Eval(map[string]any{
		"data": d,
	})

	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(out)
}

実行結果はこのようになり、構造体を処理できました。

実行結果
$ go run main.go
id=d-1, value=1000
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?