// Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package conditionparser import ( "fmt" "go/ast" "go/parser" "go/token" "reflect" "strconv" "strings" "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" ) var ( ErrInvalidOp = errors.Error("invalid operation") ErrFieldNotFound = errors.Error("field not found") ErrOutOfIndex = errors.Error("out of index") ErrFuncArgument = errors.Error("invalid function arguments") ErrMethodNotFound = errors.Error("method not found") ) func IsValid(exprStr string) bool { _, err := parser.ParseExpr(exprStr) if err != nil { return false } return true } func EvalString(exprStr string, input interface{}) (string, error) { if len(exprStr) == 0 { return "", nil } expr, err := parser.ParseExpr(exprStr) if err != nil { return "", errors.Wrapf(err, "parse expr %s error", exprStr) } result, err := eval(expr, input) if err != nil { return "", errors.Wrap(err, "eval") } switch strVal := result.(type) { case string: return strVal, nil case *jsonutils.JSONString: return strVal.GetString() default: return fmt.Sprintf("%s", strVal), nil } } func EvalBool(exprStr string, input interface{}) (bool, error) { if len(exprStr) == 0 { return true, nil } expr, err := parser.ParseExpr(exprStr) if err != nil { return false, errors.Wrapf(err, "parse expr %s", exprStr) } result, err := eval(expr, input) if err != nil { return false, errors.Wrap(err, "eval") } switch bVal := result.(type) { case bool: return bVal, nil case *jsonutils.JSONBool: return bVal.Bool() case []interface{}, *jsonutils.JSONArray: arrX := getArray(result) for i := 0; i < len(arrX); i += 1 { val := getBool(arrX[i]) if val { return true, nil } } } return false, nil } func eval(expr ast.Expr, input interface{}) (interface{}, error) { switch expr.(type) { case *ast.BinaryExpr: v, err := evalBinary(expr.(*ast.BinaryExpr), input) if err != nil { return nil, errors.Wrap(err, "evalBinary") } return v, nil case *ast.UnaryExpr: v, err := evalUnary(expr.(*ast.UnaryExpr), input) if err != nil { return nil, errors.Wrap(err, "evalUnary") } return v, nil case *ast.SelectorExpr: v, err := evalSelector(expr.(*ast.SelectorExpr), input) if err != nil { return nil, errors.Wrap(err, "evalSelector") } return v, nil case *ast.Ident: v, err := evalIdent(expr.(*ast.Ident), input) if err != nil { return nil, errors.Wrap(err, "evalIdent") } return v, nil case *ast.ParenExpr: v, err := evalParen(expr.(*ast.ParenExpr), input) if err != nil { return nil, errors.Wrap(err, "evalParen") } return v, nil case *ast.CallExpr: v, err := evalCall(expr.(*ast.CallExpr), input) if err != nil { return nil, errors.Wrap(err, "evalCall") } return v, nil case *ast.IndexExpr: v, err := evalIndex(expr.(*ast.IndexExpr), input) if err != nil { return nil, errors.Wrap(err, "evalIndex") } return v, nil case *ast.BasicLit: v, err := evalBasicLit(expr.(*ast.BasicLit), input) if err != nil { return nil, errors.Wrap(err, "evalBasicLit") } return v, nil default: return nil, errors.Wrapf(ErrInvalidOp, "unsupported expr type %s", reflect.TypeOf(expr)) } } func evalBasicLit(expr *ast.BasicLit, input interface{}) (interface{}, error) { switch expr.Kind { case token.IDENT: switch input.(type) { case *jsonutils.JSONDict: jsonX := input.(*jsonutils.JSONDict) return jsonX.Get(expr.Value) default: return nil, errors.Wrapf(ErrInvalidOp, "evalBasicLit unsupported type %s", reflect.TypeOf(input)) } case token.INT: return strconv.Atoi(expr.Value) case token.FLOAT: return strconv.ParseFloat(expr.Value, 64) case token.CHAR: return expr.Value[1 : len(expr.Value)-1][0], nil case token.STRING: return expr.Value[1 : len(expr.Value)-1], nil default: return nil, errors.Wrapf(ErrInvalidOp, "evalBasicLit unsupported kind %s", expr.Kind) } } func evalIndex(expr *ast.IndexExpr, input interface{}) (interface{}, error) { X, err := eval(expr.X, input) if err != nil { return nil, err } indexV, err := eval(expr.Index, input) if err != nil { return nil, err } switch X.(type) { case []interface{}, *jsonutils.JSONArray: arrX := getArray(X) indexI := getInt(indexV) if indexI >= 0 && indexI < int64(len(arrX)) { return arrX[indexI], nil } else { return nil, ErrOutOfIndex } case *jsonutils.JSONDict: jsonX := X.(*jsonutils.JSONDict) field := getString(indexV) return getJSONProperty(jsonX, field) case string: strX := X.(string) indexI := getInt(indexV) if indexI >= 0 && indexI < int64(len(strX)) { return strX[indexI], nil } else { return nil, ErrOutOfIndex } default: return nil, err } } func args2Strings(args []interface{}) []string { strs := make([]string, len(args)) for i := 0; i < len(args); i += 1 { strs[i] = getString(args[i]) } return strs } func args2Ints(args []interface{}) []int { ints := make([]int, len(args)) for i := 0; i < len(args); i += 1 { ints[i] = int(getInt(args[i])) } return ints } func evalCallInternal(funcV interface{}, args []interface{}) (interface{}, error) { switch funcV.(type) { case []interface{}, *jsonutils.JSONArray: arrFuncV := getArray(funcV) ret := make([]interface{}, len(arrFuncV)) for i := 0; i < len(arrFuncV); i += 1 { reti, err := evalCallInternal(arrFuncV[i], args) if err != nil { return nil, err } ret[i] = reti } return ret, nil case *funcCaller: caller := funcV.(*funcCaller) switch caller.caller.(type) { case string: strX := caller.caller.(string) switch caller.method { case "startswith": strArgs := args2Strings(args) if len(strArgs) != 1 { return nil, ErrFuncArgument } return strings.HasPrefix(strX, strArgs[0]), nil case "endswith": strArgs := args2Strings(args) if len(strArgs) != 1 { return nil, ErrFuncArgument } return strings.HasSuffix(strX, strArgs[0]), nil case "contains": strArgs := args2Strings(args) if len(strArgs) != 1 { return nil, ErrFuncArgument } return strings.Contains(strX, strArgs[0]), nil case "in": var strArgs []string if len(args) == 0 { return nil, ErrFuncArgument } else if len(args) == 1 { switch args[0].(type) { case string, *jsonutils.JSONString: strArgs = []string{args[0].(string)} case []interface{}, *jsonutils.JSONArray: strArgs = args2Strings(getArray(args[0])) default: return nil, ErrFuncArgument } } else { strArgs = args2Strings(args) } return utils.IsInStringArray(strX, strArgs), nil case "len": if len(args) > 0 { return nil, ErrFuncArgument } return len(strX), nil case "substr": intArgs := args2Ints(args) var o1, o2 int if len(intArgs) == 1 { o1 = 0 o2 = intArgs[0] } else if len(intArgs) == 2 { o1 = intArgs[0] o2 = intArgs[1] } else { return nil, ErrFuncArgument } if o1 < 0 { o1 = len(strX) + o1 } if o2 < 0 { o2 = len(strX) + o2 } if o1 < 0 || o1 >= len(strX) { return nil, ErrFuncArgument } if o2 <= o1 || o2 > len(strX) { return nil, ErrFuncArgument } return strX[o1:o2], nil default: return nil, ErrInvalidOp } case *jsonutils.JSONDict: jsonX := caller.caller.(*jsonutils.JSONDict) switch caller.method { case "contains": strArgs := args2Strings(args) return jsonX.Contains(strArgs...), nil case "len": if len(args) > 0 { return nil, ErrFuncArgument } return jsonX.Size(), nil case "keys": if len(args) > 0 { return nil, ErrFuncArgument } return jsonutils.NewStringArray(jsonX.SortedKeys()), nil default: return nil, ErrMethodNotFound } case []interface{}, *jsonutils.JSONArray: array := getArray(caller.caller) switch caller.method { case "len": if len(args) > 0 { return nil, ErrFuncArgument } return len(array), nil case "contains": if len(args) < 1 { return nil, ErrFuncArgument } for j := 0; j < len(args); j += 1 { find := false for i := 0; i < len(array); i += 1 { findInf, err := evalBinaryInternal(array[i], args[j], token.EQL) if err != nil { return nil, err } switch findInf.(type) { case bool: if findInf.(bool) { find = true } default: return nil, ErrInvalidOp } } if !find { return false, nil } } return true, nil default: return nil, ErrMethodNotFound } default: return nil, ErrInvalidOp } default: return nil, ErrInvalidOp } } func evalCall(expr *ast.CallExpr, input interface{}) (interface{}, error) { funcV, err := eval(expr.Fun, input) if err != nil { return nil, err } args := make([]interface{}, len(expr.Args)) for i := 0; i < len(expr.Args); i += 1 { args[i], err = eval(expr.Args[i], input) if err != nil { return nil, err } } return evalCallInternal(funcV, args) } func evalParen(expr *ast.ParenExpr, input interface{}) (interface{}, error) { return eval(expr.X, input) } func getJSONProperty(json *jsonutils.JSONDict, identStr string) (jsonutils.JSONObject, error) { if json.Contains(identStr) { return json.Get(identStr) } else { identArray := jsonutils.GetArrayOfPrefix(json, identStr) if len(identArray) > 0 { return jsonutils.NewArray(identArray...), nil } else { return nil, ErrFieldNotFound } } } func evalIdent(expr *ast.Ident, input interface{}) (interface{}, error) { if input == nil { return expr.Name, nil } else { switch input.(type) { case *jsonutils.JSONDict: json := input.(*jsonutils.JSONDict) return getJSONProperty(json, expr.Name) default: return nil, errors.Wrapf(ErrInvalidOp, "evalIdent unsupported type %s", reflect.TypeOf(input)) } } } type funcCaller struct { caller interface{} method string } func getArray(X interface{}) []interface{} { switch X.(type) { case *jsonutils.JSONArray: arr := X.(*jsonutils.JSONArray) ret := make([]interface{}, arr.Size()) for i := 0; i < arr.Size(); i += 1 { ret[i], _ = arr.GetAt(i) } return ret default: return X.([]interface{}) } } func evalSelectorInternal(X interface{}, identStr string) (interface{}, error) { switch X.(type) { case *jsonutils.JSONDict: json := X.(*jsonutils.JSONDict) ret, err := getJSONProperty(json, identStr) if err == ErrFieldNotFound { return &funcCaller{caller: X, method: identStr}, nil } else { return ret, errors.Wrapf(err, "getJSONProperty %s", identStr) } case []interface{}, *jsonutils.JSONArray: if identStr == "len" || identStr == "contains" { return &funcCaller{caller: X, method: identStr}, nil } arrX := getArray(X) ret := make([]interface{}, len(arrX)) for i := 0; i < len(arrX); i += 1 { reti, err := evalSelectorInternal(arrX[i], identStr) if err != nil { return nil, err } ret[i] = reti } return ret, nil case string, *jsonutils.JSONString: return &funcCaller{caller: getString(X), method: identStr}, nil default: return nil, errors.Wrapf(ErrInvalidOp, "unsupported type %s", reflect.TypeOf(X)) } } func evalSelector(expr *ast.SelectorExpr, input interface{}) (interface{}, error) { X, err := eval(expr.X, input) if err != nil { return nil, errors.Wrap(ErrInvalidOp, "eval selector X") } ident, err := evalIdent(expr.Sel, nil) if err != nil { return nil, errors.Wrap(ErrInvalidOp, "eval selector Sel") } identStr := ident.(string) return evalSelectorInternal(X, identStr) } func evalUnaryInternal(X interface{}, op token.Token) (interface{}, error) { switch X.(type) { case []interface{}, *jsonutils.JSONArray: arrX := getArray(X) ret := make([]interface{}, len(arrX)) for i := 0; i < len(arrX); i += 1 { reti, err := evalUnaryInternal(arrX[i], op) if err != nil { return nil, err } ret[i] = reti } return ret, nil case bool, *jsonutils.JSONBool: boolX := getBool(X) switch op { case token.NOT: return !boolX, nil default: return nil, ErrInvalidOp } case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, *jsonutils.JSONInt: intX := getInt(X) switch op { case token.SUB: return -intX, nil default: return nil, ErrInvalidOp } case float32, float64, *jsonutils.JSONFloat: floatX := getFloat(X) switch op { case token.SUB: return -floatX, nil default: return nil, ErrInvalidOp } default: return nil, ErrInvalidOp } } func evalUnary(expr *ast.UnaryExpr, input interface{}) (interface{}, error) { X, err := eval(expr.X, input) if err != nil { return nil, err } op := expr.Op return evalUnaryInternal(X, op) } func getString(val interface{}) string { switch val.(type) { case *jsonutils.JSONString: jsonVal, _ := val.(*jsonutils.JSONString).GetString() return jsonVal default: return val.(string) } } func getBool(val interface{}) bool { switch val.(type) { case *jsonutils.JSONBool: jsonVal, _ := val.(*jsonutils.JSONBool).Bool() return jsonVal default: return val.(bool) } } func getInt(val interface{}) int64 { switch val.(type) { case *jsonutils.JSONInt: jsonVal, _ := val.(*jsonutils.JSONInt).Int() return jsonVal default: return reflect.ValueOf(val).Int() } } func getFloat(val interface{}) float64 { switch val.(type) { case *jsonutils.JSONFloat: jsonVal, _ := val.(*jsonutils.JSONFloat).Float() return jsonVal default: return reflect.ValueOf(val).Float() } } func evalBinaryInternal(X, Y interface{}, op token.Token) (interface{}, error) { switch X.(type) { case []interface{}, *jsonutils.JSONArray: arrX := getArray(X) ret := make([]interface{}, len(arrX)) for i := 0; i < len(arrX); i += 1 { reti, err := evalBinaryInternal(arrX[i], Y, op) if err != nil { return nil, errors.Wrapf(err, "X.evalBinaryInternal at [%d]", i) } ret[i] = reti } return ret, nil } switch Y.(type) { case []interface{}, *jsonutils.JSONArray: arrY := getArray(Y) ret := make([]interface{}, len(arrY)) for i := 0; i < len(arrY); i += 1 { reti, err := evalBinaryInternal(X, arrY[i], op) if err != nil { return nil, errors.Wrapf(err, "Y.evalBinaryInternal at [%d]", i) } ret[i] = reti } return ret, nil } switch X.(type) { case bool, *jsonutils.JSONBool: switch Y.(type) { case bool, *jsonutils.JSONBool: boolX := getBool(X) boolY := getBool(Y) switch op { case token.LAND: return boolX && boolY, nil case token.LOR: return boolX || boolY, nil default: return nil, errors.Wrapf(ErrInvalidOp, "bool op %v", op) } default: return nil, errors.Wrap(ErrInvalidOp, "mismatch bool type") } case string, *jsonutils.JSONString: switch Y.(type) { case string, *jsonutils.JSONString: strX := getString(X) strY := getString(Y) switch op { case token.ADD: return strX + strY, nil case token.EQL: return strX == strY, nil case token.NEQ: return strX != strY, nil default: return nil, errors.Wrapf(ErrInvalidOp, "string op %v", op) } default: return nil, errors.Wrap(ErrInvalidOp, "mismatch string type") } case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, *jsonutils.JSONInt: intX := getInt(X) switch Y.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, *jsonutils.JSONInt: intY := getInt(Y) return evalIntegerOp(intX, intY, op) case float32, float64, jsonutils.JSONFloat: floatX := float64(intX) floatY := getFloat(Y) return evalFloatOp(floatX, floatY, op) default: return nil, errors.Wrap(ErrInvalidOp, "mismatch type int") } case float32, float64, *jsonutils.JSONFloat: floatX := getFloat(X) switch Y.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, *jsonutils.JSONInt: floatY := float64(getInt(Y)) return evalFloatOp(floatX, floatY, op) case float32, float64, *jsonutils.JSONFloat: floatY := getFloat(Y) return evalFloatOp(floatX, floatY, op) default: return nil, errors.Wrap(ErrInvalidOp, "mismatch type float") } default: return nil, errors.Wrapf(ErrInvalidOp, "unsupported op type %v", reflect.TypeOf(X)) } } func evalBinary(bExpr *ast.BinaryExpr, input interface{}) (interface{}, error) { X, err := eval(bExpr.X, input) if err != nil { return nil, errors.Wrap(err, "eval binary left") } Y, err := eval(bExpr.Y, input) if err != nil { return nil, errors.Wrap(err, "eval binary right") } return evalBinaryInternal(X, Y, bExpr.Op) } func evalIntegerOp(X, Y int64, op token.Token) (interface{}, error) { switch op { case token.ADD: return X + Y, nil case token.SUB: return X - Y, nil case token.MUL: return X * Y, nil case token.QUO: return X / Y, nil case token.REM: return X % Y, nil case token.AND: return X & Y, nil case token.OR: return X | Y, nil case token.XOR: return X ^ Y, nil case token.SHL: return X << uint64(Y), nil case token.SHR: return X >> uint64(Y), nil case token.AND_NOT: return X &^ Y, nil case token.EQL: return X == Y, nil case token.LSS: return X < Y, nil case token.GTR: return X > Y, nil case token.NEQ: return X != Y, nil case token.LEQ: return X <= Y, nil case token.GEQ: return X >= Y, nil default: return nil, ErrInvalidOp } } func evalFloatOp(X, Y float64, op token.Token) (interface{}, error) { switch op { case token.ADD: return X + Y, nil case token.SUB: return X - Y, nil case token.MUL: return X * Y, nil case token.QUO: return X / Y, nil case token.EQL: return X == Y, nil case token.LSS: return X < Y, nil case token.GTR: return X > Y, nil case token.NEQ: return X != Y, nil case token.LEQ: return X <= Y, nil case token.GEQ: return X >= Y, nil default: return nil, ErrInvalidOp } }