Skip to content
Snippets Groups Projects
Commit 474ac12e authored by Semyon Krestyaninov's avatar Semyon Krestyaninov :dog2:
Browse files

wip

parent 4d84bb87
No related branches found
No related tags found
No related merge requests found
package evaluate
import (
"context"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
)
type Evaluator interface {
Evaluate(ctx context.Context, tmpl string) (string, error)
}
func EvaluateAll(ctx context.Context, evaluator Evaluator, tmpls []string) ([]string, error) {
result := make([]string, 0, len(tmpls))
for _, tmpl := range tmpls {
eval, err := evaluator.Evaluate(ctx, tmpl)
if err != nil {
return nil, err
}
result = append(result, eval)
}
return result, nil
}
type TemplateEvaluatorContext map[string]any
// TemplateEvaluator TODO implement Evaluator
type TemplateEvaluator struct {
ctx TemplateEvaluatorContext
}
func init() {
field.RegisterOption(collectionFilter{})
field.RegisterOption(itemFilter{})
}
package evaluate
import (
"context"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
)
type collectionFilter struct {
ID []string `json:"id"`
Name []string `json:"name"`
Tag []string `json:"tag"`
Hidden bool `json:"hidden"`
NoData bool `json:"no_data"`
}
func (f collectionFilter) GetName() string {
return "collection_filter"
}
func (f *collectionFilter) Evaluate(ctx context.Context, evaluator Evaluator) error {
var err error
f.ID, err = EvaluateAll(ctx, evaluator, f.ID)
if err != nil {
return err
}
f.Name, err = EvaluateAll(ctx, evaluator, f.Name)
if err != nil {
return err
}
f.Tag, err = EvaluateAll(ctx, evaluator, f.Tag)
if err != nil {
return err
}
return nil
}
func (f collectionFilter) Validate(_ context.Context, _ *field.Field, _ interface{}) error {
return nil
}
func (f collectionFilter) ValidateOption() error {
return nil
}
type itemFilter struct {
Query []string `json:"query"`
QueryError string `json:"query_error"`
Hidden bool `json:"hidden"`
Template bool `json:"template"`
}
func (f itemFilter) GetName() string {
return "item_filter"
}
func (f *itemFilter) Evaluate(ctx context.Context, evaluator Evaluator) error {
var err error
f.Query, err = EvaluateAll(ctx, evaluator, f.Query)
if err != nil {
return err
}
f.QueryError, err = evaluator.Evaluate(ctx, f.QueryError)
if err != nil {
return err
}
return nil
}
func (f itemFilter) Validate(_ context.Context, _ *field.Field, _ interface{}) error {
return nil
}
func (f itemFilter) ValidateOption() error {
return nil
}
package evaluate
import (
"context"
"strings"
"testing"
"text/template"
"git.perx.ru/perxis/perxis-go/pkg/items"
"github.com/stretchr/testify/assert"
)
type testEvaluator struct {
env map[string]any
}
func (e testEvaluator) Evaluate(ctx context.Context, tmpl string) (string, error) {
var b strings.Builder
t, err := template.New("").Funcs(template.FuncMap{
"error": func(msg string) string {
e.env["Error"] = msg
return ""
},
}).Parse(tmpl)
if err != nil {
return "", err
}
err = t.Execute(&b, e.env)
if err != nil {
return "", err
}
return strings.TrimSpace(b.String()), nil
}
func TestItemFilter_Evaluate(t *testing.T) {
tests := []struct {
name string
input itemFilter
env map[string]any
want itemFilter
assertErr assert.ErrorAssertionFunc
}{
{
name: "query",
input: itemFilter{
Query: []string{
"name == '{{ .Item.Data.name }}'",
},
},
env: map[string]any{"Item": &items.Item{Data: map[string]any{"name": "Ivan"}}},
want: itemFilter{
Query: []string{
"name == 'Ivan'",
},
},
assertErr: assert.NoError,
},
{
name: "query with expression",
input: itemFilter{
Query: []string{
"{{ if .Item.Data.name }} name == 'Ivan' {{ else }} name == 'Oleg' {{ end }}",
},
},
env: map[string]any{"Item": &items.Item{Data: map[string]any{"name": "Ivan"}}},
want: itemFilter{
Query: []string{
"name == 'Ivan'",
},
},
assertErr: assert.NoError,
},
{
name: "invalid template",
input: itemFilter{
Query: []string{
"{{ if }} name == 'Ivan' {{ end }}",
},
},
want: itemFilter{},
assertErr: assert.Error,
},
{
name: "error func",
input: itemFilter{
Query: []string{
"{{ if .Item.Data.name }} name == '{{ .Item.Data.name }}' {{ else }} {{ error \"Name must be specified\" }} {{ end }}",
},
QueryError: "Error received: {{ .Error }}",
},
env: map[string]any{"Item": &items.Item{Data: map[string]any{}}},
want: itemFilter{
Query: []string{
"",
},
QueryError: "Error received: Name must be specified",
},
assertErr: assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.input.Evaluate(context.Background(), testEvaluator{env: tt.env})
tt.assertErr(t, err)
assert.Equal(t, tt.want, tt.input)
})
}
}
package executor
import (
"context"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
)
type CollectionFilter struct {
ID []string `json:"id"`
Name []string `json:"name"`
Tag []string `json:"tag"`
Hidden bool `json:"hidden"`
NoData bool `json:"no_data"`
}
func WithCollectionFilter(id []string, name []string, tag []string, hidden bool, noData bool) CollectionFilter {
return CollectionFilter{
ID: id,
Name: name,
Tag: tag,
Hidden: hidden,
NoData: noData,
}
}
func (opt CollectionFilter) GetName() string {
return "collection_filter"
}
func (opt CollectionFilter) Execute(ctx context.Context, exec Executor, fld *field.Field) error {
var err error
opt.ID, err = ExecuteAll(ctx, exec, opt.ID)
if err != nil && !exec.IsCanceled() {
return err
}
opt.Name, err = ExecuteAll(ctx, exec, opt.Name)
if err != nil && !exec.IsCanceled() {
return err
}
opt.Tag, err = ExecuteAll(ctx, exec, opt.Tag)
if err != nil && !exec.IsCanceled() {
return err
}
// TODO Как лучше всего обновить опцию?
// Возможно, стоит добавить соответствующий метод
fld.AddOptions(opt)
return nil
}
package executor
import (
"context"
"strings"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
)
// Executor выполняет динамическую подстановку значений в параметры полей схемы.
type Executor interface {
Execute(ctx context.Context, input string) (string, error)
// IsCanceled проверяет, было ли выполнение отменено.
IsCanceled() bool
}
// Executable определяет опции полей, использующие Executor для подстановки значений.
type Executable interface {
Execute(ctx context.Context, executor Executor, field *field.Field) error
}
// ExecuteAll последовательно выполняет Execute для каждого элемента inputs.
// Возвращает собранные непустые результаты (игнорируя пустые строки) или первую возникшую ошибку.
func ExecuteAll(ctx context.Context, exec Executor, inputs []string) ([]string, error) {
var result []string
for _, input := range inputs {
output, err := exec.Execute(ctx, input)
if err != nil {
return nil, err
}
if strings.TrimSpace(output) == "" {
continue
}
result = append(result, output)
}
return result, nil
}
func Execute(ctx context.Context, w field.Walker, exec Executor) error {
_, _, err := w.Walk(ctx, nil, func(ctx context.Context, fld *field.Field, _ any) (res field.WalkFuncResult, err error) {
enabled, _ := fld.IsEnabled(ctx)
if !enabled {
res.Stop = true
return
}
for _, op := range fld.Options {
executable, ok := op.(Executable)
if !ok {
continue
}
err = executable.Execute(ctx, exec, fld)
if err != nil {
return
}
}
return
}, field.WalkSchema())
return err
}
func init() {
field.RegisterOption(CollectionFilter{})
field.RegisterOption(ItemFilter{})
}
package executor
import (
"context"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
)
type ItemFilter struct {
Query []string `json:"query,omitempty"`
QueryError string `json:"query_error,omitempty"`
Hidden bool `json:"hidden"`
Template bool `json:"template"`
}
func WithItemFilter(query []string, queryError string, hidden bool, template bool) ItemFilter {
return ItemFilter{
Query: query,
QueryError: queryError,
Hidden: hidden,
Template: template,
}
}
func (opt ItemFilter) GetName() string {
return "item_filter"
}
func (opt ItemFilter) Execute(ctx context.Context, exec Executor, fld *field.Field) error {
var err error
opt.Query, err = ExecuteAll(ctx, exec, opt.Query)
if err != nil && !exec.IsCanceled() {
return err
}
opt.QueryError, err = exec.Execute(ctx, opt.QueryError)
if err != nil {
return err
}
// TODO Как лучше всего обновить опцию?
// Возможно, стоит добавить соответствующий метод
fld.AddOptions(opt)
return nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment