Skip to content
Snippets Groups Projects
Commit e8c2ec27 authored by Pavel Antonov's avatar Pavel Antonov :asterisk:
Browse files

Merge branch 'feature/PRXS-1998-AddTryExprFunc' into 'master'

Добавлена функция try в expr/mongo

See merge request perxis/perxis-go!173
parents 8a44924d 38d449f2
No related branches found
No related tags found
No related merge requests found
package expr package expr
import ( import (
"regexp"
"strings"
"git.perx.ru/perxis/perxis-go/pkg/data"
exprcompiler "github.com/expr-lang/expr/compiler" exprcompiler "github.com/expr-lang/expr/compiler"
"github.com/expr-lang/expr/parser" "github.com/expr-lang/expr/parser"
"github.com/expr-lang/expr/vm" "github.com/expr-lang/expr/vm"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
var (
additionalFunctions = []string{"contains", "startsWith", "endsWith", "and", "or", "in", "not"}
isExpression = regexp.MustCompile(`[()}{<>=|&%*+\-\/\]\[\\]`).MatchString
)
const EnvContextKey = "$context" const EnvContextKey = "$context"
func Eval(ctx context.Context, input string, env map[string]interface{}) (interface{}, error) { func Eval(ctx context.Context, input string, env map[string]interface{}) (interface{}, error) {
...@@ -65,17 +56,3 @@ func EvalKV(ctx context.Context, input string, kv ...interface{}) (interface{}, ...@@ -65,17 +56,3 @@ func EvalKV(ctx context.Context, input string, kv ...interface{}) (interface{},
return Eval(ctx, input, m) return Eval(ctx, input, m)
} }
func IsExpression(input string) bool {
if isExpression(input) {
return true
}
for _, s := range strings.Fields(input) {
if data.Contains(s, additionalFunctions) {
return true
}
}
return false
}
\ No newline at end of file
...@@ -2,56 +2,11 @@ package expr ...@@ -2,56 +2,11 @@ package expr
import ( import (
"context" "context"
"fmt"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestIsExpression(t *testing.T) {
now := time.Now()
tests := []struct {
name string
eval string
want bool
}{
{"equal", "i == 3", true},
{"in array", "i in [1,2,3]", true},
{"contains", "value contains 'some'", true},
{"contains with . + () $ {} ^", "value contains 'something with . + () $ {} ^'", true},
{"startsWith", "value startsWith 'some'", true},
{"startsWith . + () $ {} ^", "value startsWith '. + () $ {} ^'", true},
{"endsWith", "value endsWith 'some'", true},
{"endsWith . + () $ {} ^", "value endsWith '. + () $ {} ^'", true},
{"icontains", "icontains(value, 'some')", true},
{"icontains with . + () $ {} ^", "icontains (value, 'something with . + () $ {} ^')", true},
{"istartsWith", "istartsWith(value, 'Some')", true},
{"istartsWith . + () $ {} ^ . + () $ {} ^", "istartsWith(value, '. + () $ {} ^')", true},
{"iendsWith", "iendsWith(value, 'some')", true},
{"iendsWith . + () $ {} ^", "iendsWith(value,'. + () $ {} ^')", true},
{"or", "i == 2 || i > 10", true},
{"search", "search('some') || i > 10", true},
{"vars:or", "i == a + 2 || i > a + 10", true},
{"near", "near(a, [55.5, 37.5], 1000)", true},
{"within", "within(a, 'box', [[54.54, 36.36], [55.55, 37.37]])", true},
{"time", "d > Time.Date('2021-08-31')", true},
{"time", fmt.Sprintf("d > Time.Time('%s')", now.Format(time.RFC3339)), true},
{"in", "In(s, [1,2,3])", true},
{"in", "In(s, 1)", true},
{"text search or id", "id", false},
{"numbers", "3", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsExpression(tt.eval)
assert.Equal(t, tt.want, got)
})
}
}
type testEnvStruct struct { type testEnvStruct struct {
ID string `expr:"id"` ID string `expr:"id"`
Size int `expr:"size"` Size int `expr:"size"`
......
...@@ -358,6 +358,8 @@ func (c *compiler) SliceNode(node *ast.SliceNode) interface{} { ...@@ -358,6 +358,8 @@ func (c *compiler) SliceNode(node *ast.SliceNode) interface{} {
func (c *compiler) CallNode(node *ast.CallNode) interface{} { func (c *compiler) CallNode(node *ast.CallNode) interface{} {
switch node.Callee.String() { switch node.Callee.String() {
case "try":
return c.handleTryNode(node)
case "search", "q": case "search", "q":
val := c.compile(node.Arguments[0]) val := c.compile(node.Arguments[0])
return bson.M{"$text": bson.M{"$search": val}} return bson.M{"$text": bson.M{"$search": val}}
...@@ -710,3 +712,26 @@ func (c *compiler) handleLenNode(node *ast.BinaryNode) bson.M { ...@@ -710,3 +712,26 @@ func (c *compiler) handleLenNode(node *ast.BinaryNode) bson.M {
panic("invalid comparison operator with len()") panic("invalid comparison operator with len()")
} }
} }
// handleTryNode получает узел AST с двумя выражениями и пытается выполнить первое
// в случае успеха - возвращает его
// в случае ошибки - пытается выполнить второе и вернуть его результат
func (c *compiler) handleTryNode(node *ast.CallNode) (result interface{}) {
if len(node.Arguments) != 2 {
panic("try() expects exactly 2 arguments")
}
defer func() {
if r := recover(); r != nil {
result = c.compile(node.Arguments[1])
}
}()
tree, err := parser.Parse(node.Arguments[0].(*ast.StringNode).Value)
if err != nil {
panic(err)
}
subcompiler := &compiler{tree: tree, env: c.env, config: c.config, identifierRenameFn: c.identifierRenameFn}
result = subcompiler.compile(tree.Node).(bson.M)
return
}
...@@ -79,6 +79,10 @@ func TestConvertToMongo(t *testing.T) { ...@@ -79,6 +79,10 @@ func TestConvertToMongo(t *testing.T) {
{"in", "In(s, 1)", nil, bson.M{"s": bson.M{"$in": []interface{}{1}}}, false}, {"in", "In(s, 1)", nil, bson.M{"s": bson.M{"$in": []interface{}{1}}}, false},
{"text search or id", "id", nil, nil, true}, {"text search or id", "id", nil, nil, true},
{"struct env", "db_item.id == env_item.id", map[string]interface{}{"env_item": &testEnvStruct{ID: "id1"}}, bson.M{"db_item.id": "id1"}, false}, {"struct env", "db_item.id == env_item.id", map[string]interface{}{"env_item": &testEnvStruct{ID: "id1"}}, bson.M{"db_item.id": "id1"}, false},
{"try#1", "try('s == 1', s == 2)", nil, bson.M{"s": 1}, false},
{"try#2", "try('some-slug', search('some-slug') || _id contains 'some-slug')", nil, bson.M{"$or": bson.A{bson.M{"$text": bson.M{"$search": "some-slug"}}, bson.M{"_id": bson.M{"$regex": "some-slug"}}}}, false},
{"try#3", "try('bad-expr', bad-expr)", nil, nil, true},
{"try#4", "try('3', s == 1)", nil, bson.M{"s": 1}, false},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment