open-consul/vendor/github.com/hashicorp/go-bexpr/bexpr.go

163 lines
4.5 KiB
Go
Raw Normal View History

// bexpr is an implementation of a generic boolean expression evaluator.
// The general goal is to be able to evaluate some expression against some
// arbitrary data and get back a boolean of whether or not the data
// was matched by the expression
package bexpr
import (
"fmt"
"reflect"
)
const (
defaultMaxMatches = 32
defaultMaxRawValueLength = 512
)
// MatchExpressionEvaluator is the interface to implement to provide custom evaluation
// logic for a selector. This could be used to enable synthetic fields or other
// more complex logic that the default behavior does not support
type MatchExpressionEvaluator interface {
// FieldConfigurations returns the configuration for this field and any subfields
// it may have. It must be valid to call this method on nil.
FieldConfigurations() FieldConfigurations
// EvaluateMatch returns whether there was a match or not. We are not also
// expecting any errors because all the validation bits are handled
// during parsing and cross checking against the output of FieldConfigurations.
EvaluateMatch(sel Selector, op MatchOperator, value interface{}) (bool, error)
}
type Evaluator struct {
// The syntax tree
ast Expression
// A few configurations for extra validation of the AST
config EvaluatorConfig
// Once an expression has been run against a particular data type it cannot be executed
// against a different data type. Some coerced value memoization occurs which would
// be invalid against other data types.
boundType reflect.Type
// The field configuration of the boundType
fields FieldConfigurations
}
// Extra configuration used to perform further validation on a parsed
// expression and to aid in the evaluation process
type EvaluatorConfig struct {
// Maximum number of matching expressions allowed. 0 means unlimited
// This does not include and, or and not expressions within the AST
MaxMatches int
// Maximum length of raw values. 0 means unlimited
MaxRawValueLength int
// The Registry to use for validating expressions for a data type
// If nil the `DefaultRegistry` will be used. To disable using a
// registry all together you can set this to `NilRegistry`
Registry Registry
}
func CreateEvaluator(expression string, config *EvaluatorConfig) (*Evaluator, error) {
return CreateEvaluatorForType(expression, config, nil)
}
func CreateEvaluatorForType(expression string, config *EvaluatorConfig, dataType interface{}) (*Evaluator, error) {
ast, err := Parse("", []byte(expression))
if err != nil {
return nil, err
}
eval := &Evaluator{ast: ast.(Expression)}
if config == nil {
config = &eval.config
}
err = eval.validate(config, dataType, true)
if err != nil {
return nil, err
}
return eval, nil
}
func (eval *Evaluator) Evaluate(datum interface{}) (bool, error) {
if eval.fields == nil {
err := eval.validate(&eval.config, datum, true)
if err != nil {
return false, err
}
} else if reflect.TypeOf(datum) != eval.boundType {
return false, fmt.Errorf("This evaluator can only be used to evaluate matches against %s", eval.boundType)
}
return evaluate(eval.ast, datum, eval.fields)
}
func (eval *Evaluator) validate(config *EvaluatorConfig, dataType interface{}, updateEvaluator bool) error {
if config == nil {
return fmt.Errorf("Invalid config")
}
var fields FieldConfigurations
var err error
var rtype reflect.Type
if dataType != nil {
registry := DefaultRegistry
if config.Registry != nil {
registry = config.Registry
}
switch t := dataType.(type) {
case reflect.Type:
rtype = t
case *reflect.Type:
rtype = *t
case reflect.Value:
rtype = t.Type()
case *reflect.Value:
rtype = t.Type()
default:
rtype = reflect.TypeOf(dataType)
}
fields, err = registry.GetFieldConfigurations(rtype)
if err != nil {
return err
}
if len(fields) < 1 {
return fmt.Errorf("Data type %s has no evaluatable fields", rtype.String())
}
}
maxMatches := config.MaxMatches
if maxMatches == 0 {
maxMatches = defaultMaxMatches
}
maxRawValueLength := config.MaxRawValueLength
if maxRawValueLength == 0 {
maxRawValueLength = defaultMaxRawValueLength
}
err = validate(eval.ast, fields, config.MaxMatches, config.MaxRawValueLength)
if err != nil {
return err
}
if updateEvaluator {
eval.config = *config
eval.fields = fields
eval.boundType = rtype
}
return nil
}
// Validates an existing expression against a possibly different configuration
func (eval *Evaluator) Validate(config *EvaluatorConfig, dataType interface{}) error {
return eval.validate(config, dataType, false)
}