diff --git a/README.md b/README.md index 88b1b19..d3ea449 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ An interesting go struct tag expression syntax for field validation, etc. - Support for accessing arrays, slices, members of the dictionary - Support access to any field in the current structure - Support access to nested fields, non-exported fields, etc. +- Support variable - Support registers function expression - Built-in len, sprintf, regexp functions - Support single mode and multiple mode to define expression @@ -43,6 +44,7 @@ func Example() { f struct { g int `tagexpr:"$"` } + h int `tagexpr:"$>minVal"` } vm := tagexpr.New("tagexpr") @@ -56,6 +58,7 @@ func Example() { f: struct { g int `tagexpr:"$"` }{1}, + h: 10, } tagExpr, err := vm.Run(t) @@ -73,6 +76,8 @@ func Example() { fmt.Println(tagExpr.Eval("e")) fmt.Println(tagExpr.Eval("e2")) fmt.Println(tagExpr.Eval("f.g")) + fmt.Println(tagExpr.EvalWithEnv("h", map[string]interface{}{"minVal": 9})) + fmt.Println(tagExpr.EvalWithEnv("h", map[string]interface{}{"minVal": 11})) // Output: // true @@ -83,6 +88,8 @@ func Example() { // true // false // 1 + // true + // false } ``` diff --git a/example_test.go b/example_test.go index 10f944b..1a16bc8 100644 --- a/example_test.go +++ b/example_test.go @@ -31,6 +31,7 @@ func Example() { f struct { g int `tagexpr:"$"` } + h int `tagexpr:"$>minVal"` } vm := tagexpr.New("tagexpr") @@ -44,6 +45,7 @@ func Example() { f: struct { g int `tagexpr:"$"` }{1}, + h: 10, } tagExpr, err := vm.Run(t) @@ -61,6 +63,8 @@ func Example() { fmt.Println(tagExpr.Eval("e")) fmt.Println(tagExpr.Eval("e2")) fmt.Println(tagExpr.Eval("f.g")) + fmt.Println(tagExpr.EvalWithEnv("h", map[string]interface{}{"minVal": 9})) + fmt.Println(tagExpr.EvalWithEnv("h", map[string]interface{}{"minVal": 11})) // Output: // true @@ -71,4 +75,6 @@ func Example() { // true // false // 1 + // true + // false } diff --git a/expr.go b/expr.go index c4ad802..73765da 100644 --- a/expr.go +++ b/expr.go @@ -23,6 +23,9 @@ import ( "github.com/andeya/goutil" ) +type variableKeyType string +const variableKey variableKeyType = "__ENV_KEY__" + // Expr expression type Expr struct { expr ExprNode @@ -107,6 +110,9 @@ func (p *Expr) parseOperand(expr *string) (e ExprNode) { if e = readNilExprNode(expr); e != nil { return e } + if e = readVariableExprNode(expr); e != nil { + return e + } return nil } @@ -170,6 +176,11 @@ func (p *Expr) run(field string, tagExpr *TagExpr) interface{} { return p.expr.Run(context.Background(), field, tagExpr) } +func (p *Expr) runWithEnv(field string, tagExpr *TagExpr, env map[string]interface{}) interface{} { + ctx := context.WithValue(context.Background(), variableKey, env) + return p.expr.Run(ctx, field, tagExpr) +} + /** * Priority: * () ! bool float64 string nil diff --git a/expr_test.go b/expr_test.go index 5a273ef..76456b1 100644 --- a/expr_test.go +++ b/expr_test.go @@ -131,6 +131,36 @@ func TestExpr(t *testing.T) { } } +func TestExprWithEnv(t *testing.T) { + var cases = []struct { + expr string + val interface{} + }{ + // env: a = 10, b = "string value", + {expr: "a", val: 10.0}, + {expr: "b", val: "string value"}, + {expr: "a>10", val: false}, + {expr: "a<11", val: true}, + {expr: "a+1", val: 11.0}, + {expr: "a==10", val: true}, + } + + for _, c := range cases { + t.Log(c.expr) + vm, err := parseExpr(c.expr) + if err != nil { + t.Fatal(err) + } + val := vm.runWithEnv("", nil, map[string]interface{}{"a": 10, "b": "string value"}) + if !reflect.DeepEqual(val, c.val) { + if f, ok := c.val.(float64); ok && math.IsNaN(f) && math.IsNaN(val.(float64)) { + continue + } + t.Fatalf("expr: %q, got: %v, expect: %v", c.expr, val, c.val) + } + } +} + func TestPriority(t *testing.T) { var cases = []struct { expr string @@ -200,8 +230,6 @@ func TestSyntaxIncorrect(t *testing.T) { incorrectExpr string }{ {incorrectExpr: "1 + + 'a'"}, - {incorrectExpr: "len"}, - {incorrectExpr: "regexp"}, {incorrectExpr: "regexp()"}, {incorrectExpr: "regexp('^'+'a','a')"}, {incorrectExpr: "regexp('^a','a','b')"}, diff --git a/spec_operand.go b/spec_operand.go index 405ad90..b18e6ce 100644 --- a/spec_operand.go +++ b/spec_operand.go @@ -169,6 +169,52 @@ func (ne *nilExprNode) Run(ctx context.Context, currField string, tagExpr *TagEx return ne.val } +type variableExprNode struct { + exprBackground + boolOpposite *bool + val string +} + +func (ve *variableExprNode) String() string { + return fmt.Sprintf("%v", ve.val) +} + +func (ve *variableExprNode) Run(ctx context.Context, variableName string, _ *TagExpr) interface{} { + envObj := ctx.Value(variableKey) + if envObj == nil { + return nil + } + + env := envObj.(map[string]interface{}) + if len(env) == 0 { + return nil + } + + if value, ok := env[ve.val]; ok && value != nil { + return realValue(value, ve.boolOpposite, nil) + } else { + return nil + } +} + +var variableRegex = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*`) + +func readVariableExprNode(expr *string) ExprNode { + last, boolOpposite := getOpposite(expr, "!") + variable := variableRegex.FindString(last) + if variable == "" { + return nil + } + + *expr = (*expr)[len(*expr)-len(last)+len(variable):] + + return &variableExprNode{ + val: variable, + boolOpposite: boolOpposite, + } +} + + func getBoolAndSignOpposite(expr *string) (last string, boolOpposite *bool, signOpposite *bool) { last = strings.TrimLeft(last, "+") last, boolOpposite = getOpposite(expr, "!") diff --git a/tagexpr.go b/tagexpr.go index 5fb02e3..288f351 100644 --- a/tagexpr.go +++ b/tagexpr.go @@ -872,6 +872,34 @@ func (t *TagExpr) Eval(exprSelector string) interface{} { return expr.run(base, targetTagExpr) } +// EvalWithEnv evaluates the value with the given env +// NOTE: +// +// format: fieldName, fieldName.exprName, fieldName1.fieldName2.exprName1 +// result types: float64, string, bool, nil +func (t *TagExpr) EvalWithEnv(exprSelector string, env map[string]interface{})interface{} { + expr, ok := t.s.exprs[exprSelector] + if !ok { + // Compatible with single mode or the expression with the name @ + if strings.HasSuffix(exprSelector, ExprNameSeparator) { + exprSelector = exprSelector[:len(exprSelector)-1] + if strings.HasSuffix(exprSelector, ExprNameSeparator) { + exprSelector = exprSelector[:len(exprSelector)-1] + } + expr, ok = t.s.exprs[exprSelector] + } + if !ok { + return nil + } + } + dir, base := splitFieldSelector(exprSelector) + targetTagExpr, err := t.checkout(dir) + if err != nil { + return nil + } + return expr.runWithEnv(base, targetTagExpr, env) +} + // Range loop through each tag expression. // When fn returns false, interrupt traversal and return false. // NOTE: