picatz / xcel

⚗️ Extend CEL expressions with custom objects.

Home Page:https://pkg.go.dev/github.com/picatz/xcel

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

xcel

Extend CEL expressions with custom (native Go) objects and functions.

Usage

$ go get github.com/picatz/xcel@latest

Example

type Person struct {
	Name string
	Age  int
}

person := &Person{
	Name: "test",
	Age:  -1,
}

ta, tp := xcel.NewTypeAdapter(), xcel.NewTypeProvider()

obj, typ := xcel.NewObject(person)

xcel.RegisterObject(ta, tp, obj, typ, map[string]*types.FieldType{
	"name": {
		Type: types.StringType,
		IsSet: ref.FieldTester(func(target any) bool {
			x := target.(*xcel.Object[*Person])

			if x.Raw == nil || x.Raw.Name == "" {
				return false
			}

			return true
		}),
		GetFrom: ref.FieldGetter(func(target any) (any, error) {
			x := target.(*xcel.Object[*Person])

			if x.Raw == nil {
				return nil, fmt.Errorf("celval: object is nil")
			}

			return x.Raw.Name, nil
		}),
	},
	"age": {
		Type: types.IntType,
		IsSet: ref.FieldTester(func(target any) bool {
			x := target.(*xcel.Object[*Person])

			if x.Raw == nil || x.Raw.Age < 0 {
				return false
			}

			return true
		}),
		GetFrom: ref.FieldGetter(func(target any) (any, error) {
			x := target.(*xcel.Object[*Person])

			if x.Raw == nil {
				return nil, fmt.Errorf("celval: object is nil")
			}

			return x.Raw.Age, nil
		}),
	},
})

env, _ := cel.NewEnv(
    cel.Types(typ),
    cel.Variable("obj", typ),
    cel.CustomTypeAdapter(ta),
    cel.CustomTypeProvider(tp),
)

ast, _ := env.Compile("obj.name == 'test' && obj.age > 0")

prg, _ := env.Program(ast)

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

fmt.Println(out.Value())
// Output: false

Object Fields via Reflection

Fields can be registered via reflection, which is a bit more concise, but less flexible and less performant:

type Person struct {
	Name string
	Age  int
}

person := &Person{
	Name: "test",
	Age:  -1,
}

ta, tp := xcel.NewTypeAdapter(), xcel.NewTypeProvider()

obj, typ := xcel.NewObject(person)

xcel.RegisterObject(ta, tp, obj, typ, xcel.NewFields(obj))

Benchmarks

Showing some minimal performance differences between manual fields and reflection based fields for the same object:

ex := &Example{
	Name: "test",
	Age:  1,
	Tags: []string{"test"},
	Parent: &Example{
		Name: "root",
		Age:  -1,
		Tags: []string{"a", "b", "c"},
	},
	Pressure: 1.5,
	Fn: func(i int) string {
		return fmt.Sprintf("~%d~", i)
	},
	Blob: []byte("test"),
}
obj.name == 'test' && obj.age > 0 && ('test' in obj.tags) && obj.parent.name == 'root' && obj.pressure > 1.0 && obj.fn(1) == '~1~' && has(obj.blob)
$ go test -benchmem -run=^$ -bench ^BenchmarkNewObject github.com/picatz/xcel -v
goos: darwin
goarch: arm64
pkg: github.com/picatz/xcel
BenchmarkNewObjectManualFields
BenchmarkNewObjectManualFields-8          677050              1620 ns/op             784 B/op         22 allocs/op
BenchmarkNewObjectReflectionFields
BenchmarkNewObjectReflectionFields-8      546022              2138 ns/op             880 B/op         31 allocs/op

About

⚗️ Extend CEL expressions with custom objects.

https://pkg.go.dev/github.com/picatz/xcel

License:Mozilla Public License 2.0


Languages

Language:Go 100.0%