package rule import ( "context" "github.com/expr-lang/expr" "github.com/expr-lang/expr/vm" "github.com/pkg/errors" ) type Engine[V any] struct { rules []*vm.Program } func (e *Engine[V]) Apply(ctx context.Context, vars V) ([]any, error) { type Env[V any] struct { Context context.Context `expr:"ctx"` Vars V `expr:"vars"` } env := Env[V]{ Context: ctx, Vars: vars, } results := make([]any, 0, len(e.rules)) for i, r := range e.rules { result, err := expr.Run(r, env) if err != nil { return nil, errors.Wrapf(err, "could not run rule #%d", i) } results = append(results, result) } return results, nil } func NewEngine[E any](funcs ...OptionFunc) (*Engine[E], error) { opts := NewOptions(funcs...) engine := &Engine[E]{ rules: make([]*vm.Program, 0, len(opts.Rules)), } for i, r := range opts.Rules { program, err := expr.Compile(r, opts.Expr...) if err != nil { return nil, errors.Wrapf(err, "could not compile rule #%d", i) } engine.rules = append(engine.rules, program) } return engine, nil } func Context[T any](ctx context.Context, key any) (T, bool) { raw := ctx.Value(key) if raw == nil { return *new(T), false } value, err := Assert[T](raw) if err != nil { return *new(T), false } return value, true } func Assert[T any](raw any) (T, error) { value, ok := raw.(T) if !ok { return *new(T), errors.Errorf("unexpected value '%T'", value) } return value, nil }