xcel

package module
v0.0.0-...-f22ce92 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 31, 2024 License: MPL-2.0 Imports: 7 Imported by: 0

README

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

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultTypeProvider = &TypeProvider{
	Idents:           map[string]ref.Val{},
	Types:            map[string]*types.Type{},
	Stucts:           map[string]map[string]*types.FieldType{},
	StructFieldTypes: map[string]map[string]*types.FieldType{},
}

Functions

func NewFields

func NewFields[T any](objt *Object[T]) map[string]*types.FieldType

NewFields returns a map[string]*types.FieldType for the given object type wrapping a Go struct pointer value.

func RegisterIdent

func RegisterIdent(tp *TypeProvider, name string, value ref.Val)

func RegisterObject

func RegisterObject[T any](ta TypeAdapter, tp *TypeProvider, objt *Object[T], t *types.Type, fields map[string]*types.FieldType)

RegisterObject registers a CEL value wrapper for a Go value with the type adapter and type provider, which are provided by the caller when constructing a CEL environment.

func RegisterStructType

func RegisterStructType(tp *TypeProvider, name string, fields map[string]*types.FieldType)

func RegisterType

func RegisterType(tp *TypeProvider, t *types.Type)

Types

type Object

type Object[T any] struct {
	Raw T
}

Object is a CEL value wrapper for a Go value that can be used in expressions.

func NewObject

func NewObject[T any](val T) (*Object[T], *types.Type)

NewObject creates a new CEL value wrapper for a Go value that can be used in expressions.

Example
package main

import (
	"fmt"

	"github.com/google/cel-go/cel"
	"github.com/google/cel-go/common/types"
	"github.com/google/cel-go/common/types/ref"
	"github.com/picatz/xcel"
)

func main() {
	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])

				return x.Raw != nil && x.Raw.Name != ""
			}),
			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])

				return x.Raw != nil && x.Raw.Age >= 0
			}),
			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

func (*Object[T]) ConvertToNative

func (o *Object[T]) ConvertToNative(typeDesc reflect.Type) (any, error)

ConvertToNative converts the CEL value wrapper to a native Go value.

func (*Object[T]) ConvertToType

func (o *Object[T]) ConvertToType(typeValue ref.Type) ref.Val

ConvertToType converts the CEL value wrapper to a CEL value of the specified type.

func (*Object[T]) Equal

func (o *Object[T]) Equal(other ref.Val) ref.Val

Equal returns true if the CEL value wrapper is equal to the specified CEL value.

func (*Object[T]) Type

func (o *Object[T]) Type() ref.Type

Type returns the CEL type of the CEL value wrapper.

func (*Object[T]) Value

func (o *Object[T]) Value() any

Value returns the CEL value wrapper.

type TypeAdapter

type TypeAdapter map[reflect.Type]func(value any) ref.Val

func NewTypeAdapter

func NewTypeAdapter() TypeAdapter

func (TypeAdapter) NativeToValue

func (ta TypeAdapter) NativeToValue(value any) ref.Val

type TypeProvider

type TypeProvider struct {
	Idents           map[string]ref.Val
	Types            map[string]*types.Type
	Stucts           map[string]map[string]*types.FieldType
	StructFieldTypes map[string]map[string]*types.FieldType
}

func NewTypeProvider

func NewTypeProvider() *TypeProvider

func (TypeProvider) EnumValue

func (TypeProvider) EnumValue(enumName string) ref.Val

func (*TypeProvider) FindIdent

func (tp *TypeProvider) FindIdent(identName string) (ref.Val, bool)

func (*TypeProvider) FindStructFieldNames

func (tp *TypeProvider) FindStructFieldNames(structType string) ([]string, bool)

func (*TypeProvider) FindStructFieldType

func (tp *TypeProvider) FindStructFieldType(messageType, fieldName string) (*types.FieldType, bool)

func (*TypeProvider) FindStructType

func (tp *TypeProvider) FindStructType(structType string) (*types.Type, bool)

func (TypeProvider) NewValue

func (TypeProvider) NewValue(typeName string, fields map[string]ref.Val) ref.Val

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL