From 7e1e9b2c9b983efe45c50049d1fb29bfc3dfeb43 Mon Sep 17 00:00:00 2001 From: Danis Kirasirov <dbgbbu@gmail.com> Date: Wed, 21 Feb 2024 20:05:21 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8F=20try?= =?UTF-8?q?=20=D0=B2=20expr/mongo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/expr/expr.go | 23 --------------------- pkg/expr/expr_test.go | 45 ------------------------------------------ pkg/expr/mongo.go | 31 +++++++++++++++++++++++++++++ pkg/expr/mongo_test.go | 4 ++++ 4 files changed, 35 insertions(+), 68 deletions(-) diff --git a/pkg/expr/expr.go b/pkg/expr/expr.go index 1969b58c..42588c2b 100644 --- a/pkg/expr/expr.go +++ b/pkg/expr/expr.go @@ -1,21 +1,12 @@ package expr import ( - "regexp" - "strings" - - "git.perx.ru/perxis/perxis-go/pkg/data" exprcompiler "github.com/expr-lang/expr/compiler" "github.com/expr-lang/expr/parser" "github.com/expr-lang/expr/vm" "golang.org/x/net/context" ) -var ( - additionalFunctions = []string{"contains", "startsWith", "endsWith", "and", "or", "in", "not"} - isExpression = regexp.MustCompile(`[()}{<>=|&%*+\-\/\]\[\\]`).MatchString -) - const EnvContextKey = "$context" 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{}, 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 diff --git a/pkg/expr/expr_test.go b/pkg/expr/expr_test.go index 35153da2..b3db1b23 100644 --- a/pkg/expr/expr_test.go +++ b/pkg/expr/expr_test.go @@ -2,56 +2,11 @@ package expr import ( "context" - "fmt" "testing" - "time" - "github.com/stretchr/testify/assert" "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 { ID string `expr:"id"` Size int `expr:"size"` diff --git a/pkg/expr/mongo.go b/pkg/expr/mongo.go index f0e33578..71f3bdda 100644 --- a/pkg/expr/mongo.go +++ b/pkg/expr/mongo.go @@ -358,6 +358,8 @@ func (c *compiler) SliceNode(node *ast.SliceNode) interface{} { func (c *compiler) CallNode(node *ast.CallNode) interface{} { switch node.Callee.String() { + case "try": + return c.handleTryNode(node) case "search", "q": val := c.compile(node.Arguments[0]) return bson.M{"$text": bson.M{"$search": val}} @@ -710,3 +712,32 @@ func (c *compiler) handleLenNode(node *ast.BinaryNode) bson.M { 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") + } + + subcompiler := &compiler{env: c.env, config: c.config, identifierRenameFn: c.identifierRenameFn} + subexprCompile := func(subexpr string) interface{} { + tree, err := parser.Parse(subexpr) + if err != nil { + panic(err) + } + + subcompiler.tree = tree + return subcompiler.compile(tree.Node) + } + + defer func() { + if r := recover(); r != nil { + result = subexprCompile(node.Arguments[1].(*ast.StringNode).Value) + } + }() + + result = subexprCompile(node.Arguments[0].(*ast.StringNode).Value).(bson.M) + return +} diff --git a/pkg/expr/mongo_test.go b/pkg/expr/mongo_test.go index bbe42465..65070bbc 100644 --- a/pkg/expr/mongo_test.go +++ b/pkg/expr/mongo_test.go @@ -79,6 +79,10 @@ func TestConvertToMongo(t *testing.T) { {"in", "In(s, 1)", nil, bson.M{"s": bson.M{"$in": []interface{}{1}}}, false}, {"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}, + {"try#1", "try('s == 1', 's == 2')", nil, bson.M{"s": 1}, false}, + {"try#2", "try('some-slug', 'search(\\'some\\') || _id contains \\'some-slug\\'')", nil, bson.M{"$or": bson.A{bson.M{"$text": bson.M{"$search": "some"}}, 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 { t.Run(tt.name, func(t *testing.T) { -- GitLab