mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
[Feature] round() JMESPath function (#7489)
* adding roundoff Signed-off-by: Rexbeast2 <ssukhveer514@gmail.com> * removing unnecessary Signed-off-by: Rexbeast2 <ssukhveer514@gmail.com> * adding test Signed-off-by: Rexbeast2 <ssukhveer514@gmail.com> * adding edge case Signed-off-by: Rexbeast2 <ssukhveer514@gmail.com> * fixing error Signed-off-by: Rexbeast2 <ssukhveer514@gmail.com> * updating function call Signed-off-by: Rexbeast2 <ssukhveer514@gmail.com> * updating function jpRound Signed-off-by: Rexbeast2 <ssukhveer514@gmail.com> * error handling negative Signed-off-by: Rexbeast2 <ssukhveer514@gmail.com> * fix Signed-off-by: Rexbeast2 <ssukhveer514@gmail.com> * fix linter Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * parsing Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * cleanup Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix tests Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> --------- Signed-off-by: Rexbeast2 <ssukhveer514@gmail.com> Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> Co-authored-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
parent
26e5bd76c7
commit
998a14c660
4 changed files with 124 additions and 16 deletions
|
@ -30,25 +30,29 @@ type scalar struct {
|
|||
float64
|
||||
}
|
||||
|
||||
func parseArithemticOperands(arguments []interface{}, operator string) (operand, operand, error) {
|
||||
op := [2]operand{nil, nil}
|
||||
for i := 0; i < 2; i++ {
|
||||
if tmp, err := validateArg(divide, arguments, i, reflect.Float64); err == nil {
|
||||
var sc scalar
|
||||
sc.float64 = tmp.Float()
|
||||
op[i] = sc
|
||||
} else if tmp, err = validateArg(divide, arguments, i, reflect.String); err == nil {
|
||||
if q, err := resource.ParseQuantity(tmp.String()); err == nil {
|
||||
op[i] = quantity{Quantity: q}
|
||||
} else if d, err := time.ParseDuration(tmp.String()); err == nil {
|
||||
op[i] = duration{Duration: d}
|
||||
}
|
||||
func parseArithemticOperand(arguments []interface{}, index int, operator string) (operand, error) {
|
||||
if tmp, err := validateArg(operator, arguments, index, reflect.Float64); err == nil {
|
||||
return scalar{float64: tmp.Float()}, nil
|
||||
} else if tmp, err = validateArg(operator, arguments, index, reflect.String); err == nil {
|
||||
if q, err := resource.ParseQuantity(tmp.String()); err == nil {
|
||||
return quantity{Quantity: q}, nil
|
||||
} else if d, err := time.ParseDuration(tmp.String()); err == nil {
|
||||
return duration{Duration: d}, nil
|
||||
}
|
||||
}
|
||||
if op[0] == nil || op[1] == nil {
|
||||
return nil, nil, formatError(genericError, operator, "invalid operands")
|
||||
return nil, formatError(genericError, operator, "invalid operand")
|
||||
}
|
||||
|
||||
func parseArithemticOperands(arguments []interface{}, operator string) (operand, operand, error) {
|
||||
left, err := parseArithemticOperand(arguments, 0, operator)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return op[0], op[1], nil
|
||||
right, err := parseArithemticOperand(arguments, 1, operator)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return left, right, nil
|
||||
}
|
||||
|
||||
// Quantity +|- Quantity -> Quantity
|
||||
|
|
|
@ -768,6 +768,75 @@ func Test_Modulo(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_Round(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
test string
|
||||
expectedResult interface{}
|
||||
err bool
|
||||
retFloat bool
|
||||
}{
|
||||
// Scalar
|
||||
{
|
||||
name: "Scalar roundoff Quantity -> error",
|
||||
test: "round(`23`, '12Ki')",
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "Scalar roundoff Duration -> error",
|
||||
test: "round(`21`, '5s')",
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "Scalar roundoff Scalar -> Scalar",
|
||||
test: "round(`9.414675`, `2`)",
|
||||
expectedResult: 9.41,
|
||||
retFloat: true,
|
||||
},
|
||||
{
|
||||
name: "Scalar roundoff zero -> error",
|
||||
test: "round(`14.123`, `6`)",
|
||||
expectedResult: 14.123,
|
||||
retFloat: true,
|
||||
},
|
||||
// round with non int values
|
||||
{
|
||||
name: "Scalar roundoff Non int -> error",
|
||||
test: "round(`14`, `1.5`)",
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "Scalar roundoff negative int -> error",
|
||||
test: "round(`14`, `-2`)",
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
jp, err := newJMESPath(cfg, tc.test)
|
||||
assert.NilError(t, err)
|
||||
|
||||
result, err := jp.Search("")
|
||||
if !tc.err {
|
||||
assert.NilError(t, err)
|
||||
} else {
|
||||
assert.Assert(t, err != nil)
|
||||
return
|
||||
}
|
||||
|
||||
if tc.retFloat {
|
||||
equal, ok := result.(float64)
|
||||
assert.Assert(t, ok)
|
||||
assert.Equal(t, equal, tc.expectedResult.(float64))
|
||||
} else {
|
||||
equal, ok := result.(string)
|
||||
assert.Assert(t, ok)
|
||||
assert.Equal(t, equal, tc.expectedResult.(string))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestScalar_Multiply(t *testing.T) {
|
||||
type fields struct {
|
||||
float64 float64
|
||||
|
|
|
@ -12,6 +12,7 @@ const (
|
|||
zeroDivisionError = errorPrefix + "Zero divisor passed"
|
||||
nonIntModuloError = errorPrefix + "Non-integer argument(s) passed for modulo"
|
||||
typeMismatchError = errorPrefix + "Types mismatch"
|
||||
nonIntRoundError = errorPrefix + "Non-integer argument(s) passed for round off"
|
||||
)
|
||||
|
||||
func formatError(format string, function string, values ...interface{}) error {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
@ -58,6 +59,7 @@ var (
|
|||
multiply = "multiply"
|
||||
divide = "divide"
|
||||
modulo = "modulo"
|
||||
round = "round"
|
||||
base64Decode = "base64_decode"
|
||||
base64Encode = "base64_encode"
|
||||
pathCanonicalize = "path_canonicalize"
|
||||
|
@ -307,6 +309,17 @@ func GetFunctions(configuration config.Configuration) []FunctionEntry {
|
|||
},
|
||||
ReturnType: []jpType{jpAny},
|
||||
Note: "divisor must be non-zero, arguments must be integers",
|
||||
}, {
|
||||
FunctionEntry: gojmespath.FunctionEntry{
|
||||
Name: round,
|
||||
Arguments: []argSpec{
|
||||
{Types: []jpType{jpNumber}},
|
||||
{Types: []jpType{jpNumber}},
|
||||
},
|
||||
Handler: jpRound,
|
||||
},
|
||||
ReturnType: []jpType{jpNumber},
|
||||
Note: "does roundoff to upto the given decimal places",
|
||||
}, {
|
||||
FunctionEntry: gojmespath.FunctionEntry{
|
||||
Name: base64Decode,
|
||||
|
@ -865,6 +878,27 @@ func jpModulo(arguments []interface{}) (interface{}, error) {
|
|||
return op1.Modulo(op2)
|
||||
}
|
||||
|
||||
func jpRound(arguments []interface{}) (interface{}, error) {
|
||||
op, err := validateArg(round, arguments, 0, reflect.Float64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length, err := validateArg(round, arguments, 1, reflect.Float64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
intLength, err := intNumber(length.Float())
|
||||
if err != nil {
|
||||
return nil, formatError(nonIntRoundError, round)
|
||||
}
|
||||
if intLength < 0 {
|
||||
return nil, formatError(argOutOfBoundsError, round)
|
||||
}
|
||||
shift := math.Pow(10, float64(intLength))
|
||||
rounded := math.Round(op.Float()*shift) / shift
|
||||
return rounded, nil
|
||||
}
|
||||
|
||||
func jpBase64Decode(arguments []interface{}) (interface{}, error) {
|
||||
var err error
|
||||
str, err := validateArg("", arguments, 0, reflect.String)
|
||||
|
|
Loading…
Reference in a new issue