2024-06-25 14:03:49 +02:00
|
|
|
package rule
|
|
|
|
|
|
|
|
import (
|
2024-09-24 15:46:42 +02:00
|
|
|
"context"
|
|
|
|
|
2024-06-25 14:03:49 +02:00
|
|
|
"github.com/expr-lang/expr"
|
|
|
|
"github.com/expr-lang/expr/vm"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
2024-09-24 15:46:42 +02:00
|
|
|
type Engine[V any] struct {
|
2024-06-25 14:03:49 +02:00
|
|
|
rules []*vm.Program
|
|
|
|
}
|
|
|
|
|
2024-09-24 15:46:42 +02:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2024-06-25 14:03:49 +02:00
|
|
|
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
|
|
|
|
}
|
2024-09-24 15:46:42 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|