309
vendor/github.com/hashicorp/go-bexpr/evaluate.go
generated
vendored
Normal file
309
vendor/github.com/hashicorp/go-bexpr/evaluate.go
generated
vendored
Normal file
@@ -0,0 +1,309 @@
|
||||
package bexpr
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-bexpr/grammar"
|
||||
"github.com/mitchellh/pointerstructure"
|
||||
)
|
||||
|
||||
var byteSliceTyp reflect.Type = reflect.TypeOf([]byte{})
|
||||
|
||||
func primitiveEqualityFn(kind reflect.Kind) func(first interface{}, second reflect.Value) bool {
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return doEqualBool
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return doEqualInt64
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return doEqualUint64
|
||||
case reflect.Float32:
|
||||
return doEqualFloat32
|
||||
case reflect.Float64:
|
||||
return doEqualFloat64
|
||||
case reflect.String:
|
||||
return doEqualString
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func doEqualBool(first interface{}, second reflect.Value) bool {
|
||||
return first.(bool) == second.Bool()
|
||||
}
|
||||
|
||||
func doEqualInt64(first interface{}, second reflect.Value) bool {
|
||||
return first.(int64) == second.Int()
|
||||
}
|
||||
|
||||
func doEqualUint64(first interface{}, second reflect.Value) bool {
|
||||
return first.(uint64) == second.Uint()
|
||||
}
|
||||
|
||||
func doEqualFloat32(first interface{}, second reflect.Value) bool {
|
||||
return first.(float32) == float32(second.Float())
|
||||
}
|
||||
|
||||
func doEqualFloat64(first interface{}, second reflect.Value) bool {
|
||||
return first.(float64) == second.Float()
|
||||
}
|
||||
|
||||
func doEqualString(first interface{}, second reflect.Value) bool {
|
||||
return first.(string) == second.String()
|
||||
}
|
||||
|
||||
// Get rid of 0 to many levels of pointers to get at the real type
|
||||
func derefType(rtype reflect.Type) reflect.Type {
|
||||
for rtype.Kind() == reflect.Ptr {
|
||||
rtype = rtype.Elem()
|
||||
}
|
||||
return rtype
|
||||
}
|
||||
|
||||
func doMatchMatches(expression *grammar.MatchExpression, value reflect.Value) (bool, error) {
|
||||
if !value.Type().ConvertibleTo(byteSliceTyp) {
|
||||
return false, fmt.Errorf("Value of type %s is not convertible to []byte", value.Type())
|
||||
}
|
||||
|
||||
var re *regexp.Regexp
|
||||
var ok bool
|
||||
if expression.Value.Converted != nil {
|
||||
re, ok = expression.Value.Converted.(*regexp.Regexp)
|
||||
}
|
||||
if !ok || re == nil {
|
||||
var err error
|
||||
re, err = regexp.Compile(expression.Value.Raw)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Failed to compile regular expression %q: %v", expression.Value.Raw, err)
|
||||
}
|
||||
expression.Value.Converted = re
|
||||
}
|
||||
|
||||
return re.Match(value.Convert(byteSliceTyp).Interface().([]byte)), nil
|
||||
}
|
||||
|
||||
func doMatchEqual(expression *grammar.MatchExpression, value reflect.Value) (bool, error) {
|
||||
// NOTE: see preconditions in evaluategrammar.MatchExpressionRecurse
|
||||
eqFn := primitiveEqualityFn(value.Kind())
|
||||
if eqFn == nil {
|
||||
return false, errors.New("unable to find suitable primitive comparison function for matching")
|
||||
}
|
||||
matchValue, err := getMatchExprValue(expression, value.Kind())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error getting match value in expression: %w", err)
|
||||
}
|
||||
return eqFn(matchValue, value), nil
|
||||
}
|
||||
|
||||
func doMatchIn(expression *grammar.MatchExpression, value reflect.Value) (bool, error) {
|
||||
matchValue, err := getMatchExprValue(expression, value.Kind())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error getting match value in expression: %w", err)
|
||||
}
|
||||
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Map:
|
||||
found := value.MapIndex(reflect.ValueOf(matchValue))
|
||||
return found.IsValid(), nil
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
itemType := derefType(value.Type().Elem())
|
||||
kind := itemType.Kind()
|
||||
switch kind {
|
||||
case reflect.Interface:
|
||||
// If it's an interface, that is, the type was []interface{}, we
|
||||
// have to treat each element individually, checking each element's
|
||||
// type/kind and rederiving the match value.
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
item := value.Index(i).Elem()
|
||||
itemType := derefType(item.Type())
|
||||
kind := itemType.Kind()
|
||||
// We need to special case errors here. The reason is that in an
|
||||
// interface slice there can be a mix/match of types, but the
|
||||
// coerce functions expect a certain type. So the expression
|
||||
// passed in might be `"true" in "/my/slice"` but the value it's
|
||||
// checking against might be an integer, thus it will try to
|
||||
// coerce "true" to an integer and fail. However, all of the
|
||||
// functions use strconv which has a specific error type for
|
||||
// syntax errors, so as a special case in this situation, don't
|
||||
// error on a strconv.ErrSyntax, just continue on to the next
|
||||
// element.
|
||||
matchValue, err = getMatchExprValue(expression, kind)
|
||||
if err != nil {
|
||||
if errors.Is(err, strconv.ErrSyntax) {
|
||||
continue
|
||||
}
|
||||
return false, errors.New(`error getting interface slice match value in expression`)
|
||||
}
|
||||
eqFn := primitiveEqualityFn(kind)
|
||||
if eqFn == nil {
|
||||
return false, fmt.Errorf(`unable to find suitable primitive comparison function for "in" comparison in interface slice: %s`, kind)
|
||||
}
|
||||
// the value will be the correct type as we verified the itemType
|
||||
if eqFn(matchValue, reflect.Indirect(item)) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
|
||||
default:
|
||||
// Otherwise it's a concrete type and we can essentially cache the
|
||||
// answers. First we need to re-derive the match value for equality
|
||||
// assertion.
|
||||
matchValue, err = getMatchExprValue(expression, kind)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error getting match value in expression: %w", err)
|
||||
}
|
||||
eqFn := primitiveEqualityFn(kind)
|
||||
if eqFn == nil {
|
||||
return false, errors.New(`unable to find suitable primitive comparison function for "in" comparison`)
|
||||
}
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
item := value.Index(i)
|
||||
// the value will be the correct type as we verified the itemType
|
||||
if eqFn(matchValue, reflect.Indirect(item)) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
case reflect.String:
|
||||
return strings.Contains(value.String(), matchValue.(string)), nil
|
||||
|
||||
default:
|
||||
return false, fmt.Errorf("Cannot perform in/contains operations on type %s for selector: %q", kind, expression.Selector)
|
||||
}
|
||||
}
|
||||
|
||||
func doMatchIsEmpty(matcher *grammar.MatchExpression, value reflect.Value) (bool, error) {
|
||||
// NOTE: see preconditions in evaluategrammar.MatchExpressionRecurse
|
||||
return value.Len() == 0, nil
|
||||
}
|
||||
|
||||
func getMatchExprValue(expression *grammar.MatchExpression, rvalue reflect.Kind) (interface{}, error) {
|
||||
if expression.Value == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch rvalue {
|
||||
case reflect.Bool:
|
||||
return CoerceBool(expression.Value.Raw)
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return CoerceInt64(expression.Value.Raw)
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return CoerceUint64(expression.Value.Raw)
|
||||
|
||||
case reflect.Float32:
|
||||
return CoerceFloat32(expression.Value.Raw)
|
||||
|
||||
case reflect.Float64:
|
||||
return CoerceFloat64(expression.Value.Raw)
|
||||
|
||||
default:
|
||||
return expression.Value.Raw, nil
|
||||
}
|
||||
}
|
||||
|
||||
func evaluateMatchExpression(expression *grammar.MatchExpression, datum interface{}, opt ...Option) (bool, error) {
|
||||
opts := getOpts(opt...)
|
||||
ptr := pointerstructure.Pointer{
|
||||
Parts: expression.Selector.Path,
|
||||
Config: pointerstructure.Config{
|
||||
TagName: opts.withTagName,
|
||||
ValueTransformationHook: opts.withHookFn,
|
||||
},
|
||||
}
|
||||
val, err := ptr.Get(datum)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error finding value in datum: %w", err)
|
||||
}
|
||||
|
||||
if jn, ok := val.(json.Number); ok {
|
||||
if jni, err := jn.Int64(); err == nil {
|
||||
val = jni
|
||||
} else if jnf, err := jn.Float64(); err == nil {
|
||||
val = jnf
|
||||
} else {
|
||||
return false, fmt.Errorf("unable to convert json number %s to int or float", jn)
|
||||
}
|
||||
}
|
||||
|
||||
rvalue := reflect.Indirect(reflect.ValueOf(val))
|
||||
switch expression.Operator {
|
||||
case grammar.MatchEqual:
|
||||
return doMatchEqual(expression, rvalue)
|
||||
case grammar.MatchNotEqual:
|
||||
result, err := doMatchEqual(expression, rvalue)
|
||||
if err == nil {
|
||||
return !result, nil
|
||||
}
|
||||
return false, err
|
||||
case grammar.MatchIn:
|
||||
return doMatchIn(expression, rvalue)
|
||||
case grammar.MatchNotIn:
|
||||
result, err := doMatchIn(expression, rvalue)
|
||||
if err == nil {
|
||||
return !result, nil
|
||||
}
|
||||
return false, err
|
||||
case grammar.MatchIsEmpty:
|
||||
return doMatchIsEmpty(expression, rvalue)
|
||||
case grammar.MatchIsNotEmpty:
|
||||
result, err := doMatchIsEmpty(expression, rvalue)
|
||||
if err == nil {
|
||||
return !result, nil
|
||||
}
|
||||
return false, err
|
||||
case grammar.MatchMatches:
|
||||
return doMatchMatches(expression, rvalue)
|
||||
case grammar.MatchNotMatches:
|
||||
result, err := doMatchMatches(expression, rvalue)
|
||||
if err == nil {
|
||||
return !result, nil
|
||||
}
|
||||
return false, err
|
||||
default:
|
||||
return false, fmt.Errorf("Invalid match operation: %d", expression.Operator)
|
||||
}
|
||||
}
|
||||
|
||||
func evaluate(ast grammar.Expression, datum interface{}, opt ...Option) (bool, error) {
|
||||
switch node := ast.(type) {
|
||||
case *grammar.UnaryExpression:
|
||||
switch node.Operator {
|
||||
case grammar.UnaryOpNot:
|
||||
result, err := evaluate(node.Operand, datum, opt...)
|
||||
return !result, err
|
||||
}
|
||||
case *grammar.BinaryExpression:
|
||||
switch node.Operator {
|
||||
case grammar.BinaryOpAnd:
|
||||
result, err := evaluate(node.Left, datum, opt...)
|
||||
if err != nil || !result {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return evaluate(node.Right, datum, opt...)
|
||||
|
||||
case grammar.BinaryOpOr:
|
||||
result, err := evaluate(node.Left, datum, opt...)
|
||||
if err != nil || result {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return evaluate(node.Right, datum, opt...)
|
||||
}
|
||||
case *grammar.MatchExpression:
|
||||
return evaluateMatchExpression(node, datum, opt...)
|
||||
}
|
||||
return false, fmt.Errorf("Invalid AST node")
|
||||
}
|
||||
Reference in New Issue
Block a user