bouncer/internal/rule/engine.go
William Petit fea0610346
All checks were successful
Cadoles/bouncer/pipeline/pr-develop This commit looks good
feat: reusable rule engine to prevent memory reallocation
2024-09-24 18:45:34 +02:00

80 lines
1.4 KiB
Go

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
}