mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-29 17:58:15 +08:00
`lib/utils/typical` is a new library for building predicate parsers based on the examples set by `lib/utils/parse` and `e/lib/loginrule`. The commit updates `lib/utils/parse` to use the `typical` library to implement the two parsers defined here. This removes a lot of code in `ast.go` that was written for each specific function to do type checking and deal with subexpressions. This is all handled by the `typical` library now. My hope is that this will make it much easier to add new functions in the future and to share them with other parsers.
920 lines
31 KiB
Go
920 lines
31 KiB
Go
// Copyright 2023 Gravitational, Inc
|
|
//
|
|
// 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.
|
|
|
|
// typical (TYPed predICAte Library) is a library for building better predicate
|
|
// expression parsers faster. It is built on top of
|
|
// [predicate](github.com/gravitational/predicate).
|
|
//
|
|
// typical helps you (and forces you) to separate parse and evaluation into two
|
|
// distinct stages so that parsing can be cached and evaluation can be fast.
|
|
// Expressions can also be parsed to check their syntax without needing to
|
|
// evaluate them with some specific input.
|
|
//
|
|
// Functions can be defined by providing implementations accepting and returning
|
|
// values of ordinary types, the library handles the details that enable any
|
|
// function argument to be a subexpression that evaluates to the correct type.
|
|
//
|
|
// By default, typical provides strong type checking at parse time without any
|
|
// reflection during evaluation. If you define a function that evaluates to
|
|
// type any, you are opting in to evaluation-time type checking everywhere that
|
|
// function is called, but types will still be checked for all other parts of
|
|
// the expression. If you define a function that accepts an interface type it
|
|
// will be called via reflection during evaluation, but interface satisfaction
|
|
// is still checked at parse time.
|
|
package typical
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/vulcand/predicate"
|
|
)
|
|
|
|
// Expression is a generic interface representing a parsed predicate expression
|
|
// which can be evaluated with environment type TEnv to produce a result of type
|
|
// TResult.
|
|
type Expression[TEnv, TResult any] interface {
|
|
// Evaluate evaluates the already parsed predicate expression with the given
|
|
// environment.
|
|
Evaluate(environment TEnv) (TResult, error)
|
|
}
|
|
|
|
// ParserSpec defines a predicate language.
|
|
type ParserSpec struct {
|
|
// Variables defines all of the literals and variables available to
|
|
// expressions in the predicate language. It is a map from variable name to
|
|
// definition.
|
|
Variables map[string]Variable
|
|
|
|
// Functions defines all of the functions available to expressions in the
|
|
// predicate language.
|
|
Functions map[string]Function
|
|
|
|
// Methods defines all of the methods available to expressions in the
|
|
// predicate language. Methods are just functions that take their receiver
|
|
// as their first argument.
|
|
Methods map[string]Function
|
|
}
|
|
|
|
// Variable holds the definition of a literal or variable. It is expected to be
|
|
// either a literal static value, or the result of calling DynamicVariable or
|
|
// DynamicMap.
|
|
type Variable any
|
|
|
|
// Function holds the definition of a function. It is expected to be the result
|
|
// of calling one of (Unary|Binary|Ternary)(Variadic)?Function.
|
|
type Function interface {
|
|
buildExpression(name string, args ...any) (any, error)
|
|
}
|
|
|
|
// Parser is a predicate expression parser configured to parse expressions of a
|
|
// specific expression language.
|
|
type Parser[TEnv, TResult any] struct {
|
|
spec ParserSpec
|
|
pred predicate.Parser
|
|
options parserOptions
|
|
}
|
|
|
|
type parserOptions struct {
|
|
invalidNamespaceHack bool
|
|
}
|
|
|
|
// ParserOption is an optional option for configuring a Parser.
|
|
type ParserOption func(*parserOptions)
|
|
|
|
// WithInvalidNamespaceHack is necessary because old parser versions
|
|
// accidentally allowed "<anything>.trait" as an alias for "external.trait". Some
|
|
// people wrote typos and now we have to maintain this.
|
|
// See https://github.com/gravitational/teleport/pull/21551
|
|
func WithInvalidNamespaceHack() ParserOption {
|
|
return func(opts *parserOptions) {
|
|
opts.invalidNamespaceHack = true
|
|
}
|
|
}
|
|
|
|
// NewParser creates a predicate expression parser with the given specification.
|
|
func NewParser[TEnv, TResult any](spec ParserSpec, opts ...ParserOption) (*Parser[TEnv, TResult], error) {
|
|
var options parserOptions
|
|
for _, opt := range opts {
|
|
opt(&options)
|
|
}
|
|
|
|
p := &Parser[TEnv, TResult]{
|
|
spec: spec,
|
|
options: options,
|
|
}
|
|
def := predicate.Def{
|
|
GetIdentifier: p.getIdentifier,
|
|
GetProperty: getProperty[TEnv],
|
|
Functions: make(map[string]any, len(spec.Functions)),
|
|
Methods: make(map[string]any, len(spec.Methods)),
|
|
Operators: predicate.Operators{
|
|
NOT: not[TEnv],
|
|
AND: and[TEnv](),
|
|
OR: or[TEnv](),
|
|
EQ: eq[TEnv](),
|
|
NEQ: neq[TEnv](),
|
|
},
|
|
}
|
|
|
|
for name, f := range spec.Functions {
|
|
name, f := name, f
|
|
def.Functions[name] = func(args ...any) (any, error) {
|
|
return f.buildExpression(name, args...)
|
|
}
|
|
}
|
|
for name, f := range spec.Methods {
|
|
name, f := name, f
|
|
def.Methods[name] = func(args ...any) (any, error) {
|
|
return f.buildExpression(name, args...)
|
|
}
|
|
}
|
|
|
|
pred, err := predicate.NewParser(def)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "creating predicate parser")
|
|
}
|
|
p.pred = pred
|
|
return p, nil
|
|
}
|
|
|
|
// Parse parses the given expression string to produce an
|
|
// Expression[TEnv, TResult]. The returned Expression can be safely cached and
|
|
// called multiple times with different TEnv inputs.
|
|
func (p *Parser[TEnv, TResult]) Parse(expression string) (Expression[TEnv, TResult], error) {
|
|
if expression == "" {
|
|
return nil, trace.BadParameter("empty expression")
|
|
}
|
|
result, err := p.pred.Parse(expression)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing expression")
|
|
}
|
|
expr, err := coerce[TEnv, TResult](result)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "expression evaluated to unexpected type")
|
|
}
|
|
return expr, nil
|
|
}
|
|
|
|
func (p *Parser[TEnv, TResult]) getIdentifier(selector []string) (any, error) {
|
|
remaining := selector[:]
|
|
var joined string
|
|
for len(remaining) > 0 {
|
|
if len(joined) > 0 {
|
|
joined += "."
|
|
}
|
|
joined += remaining[0]
|
|
remaining = remaining[1:]
|
|
v, ok := p.spec.Variables[joined]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if len(remaining) == 0 {
|
|
// Exact match
|
|
return v, nil
|
|
}
|
|
// Allow map lookups with map.key instead of map["key"]
|
|
if len(remaining) == 1 {
|
|
expr, err := getProperty[TEnv](v, remaining[0])
|
|
if err == nil {
|
|
return expr, nil
|
|
}
|
|
// if err != nil just continue rather than returning a
|
|
// potentially confusing index expression error when this wasn't
|
|
// actually a map
|
|
}
|
|
}
|
|
|
|
if p.options.invalidNamespaceHack && len(selector) < 3 {
|
|
// With invalidNamespaceHack enabled, anything not matched above is an
|
|
// alias for "external"
|
|
external, ok := p.spec.Variables["external"]
|
|
if !ok {
|
|
return nil, trace.BadParameter(`invalidNamespaceHack enabled but "external" not present (this is a bug)`)
|
|
}
|
|
switch len(selector) {
|
|
case 1:
|
|
return external, nil
|
|
case 2:
|
|
expr, err := getProperty[TEnv](external, selector[1])
|
|
return expr, trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
return nil, UnknownIdentifierError(joined)
|
|
}
|
|
|
|
// UnknownIdentifierError is an error type that can be used to identify errors
|
|
// related to an unknown identifier in an expression.
|
|
type UnknownIdentifierError string
|
|
|
|
func (u UnknownIdentifierError) Error() string {
|
|
return fmt.Sprintf("unknown identifier: %q", string(u))
|
|
}
|
|
|
|
func (u UnknownIdentifierError) Identifier() string {
|
|
return string(u)
|
|
}
|
|
|
|
// getProperty is a helper for parsing map[key] expressions and returns either a
|
|
// propertyExpr or a dynamicMapExpr.
|
|
func getProperty[TEnv any](mapVal, keyVal any) (any, error) {
|
|
keyExpr, err := coerce[TEnv, string](keyVal)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing key of index expression")
|
|
}
|
|
|
|
if mapExpr, ok := mapVal.(Expression[TEnv, map[string]string]); ok {
|
|
return propertyExpr[TEnv, string]{mapExpr, keyExpr}, nil
|
|
}
|
|
if mapExpr, ok := mapVal.(Expression[TEnv, map[string][]string]); ok {
|
|
return propertyExpr[TEnv, []string]{mapExpr, keyExpr}, nil
|
|
}
|
|
if dynamicMap, ok := mapVal.(indexExpressionBuilder[TEnv]); ok {
|
|
return dynamicMap.buildIndexExpression(keyExpr), nil
|
|
}
|
|
return nil, trace.Wrap(unexpectedTypeError[map[string]string](mapVal), "cannot take index of unexpected type")
|
|
}
|
|
|
|
type propertyExpr[TEnv, TValues any] struct {
|
|
mapExpr Expression[TEnv, map[string]TValues]
|
|
keyExpr Expression[TEnv, string]
|
|
}
|
|
|
|
func (p propertyExpr[TEnv, TValues]) Evaluate(env TEnv) (TValues, error) {
|
|
var nul TValues
|
|
m, err := p.mapExpr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating base of index expression")
|
|
}
|
|
k, err := p.keyExpr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating key of index expression")
|
|
}
|
|
return m[k], nil
|
|
}
|
|
|
|
type indexExpressionBuilder[TEnv any] interface {
|
|
buildIndexExpression(keyExpr Expression[TEnv, string]) any
|
|
}
|
|
|
|
// Getter is a generic interface for map-like values that allow you to Get a
|
|
// TValues by key
|
|
type Getter[TValues any] interface {
|
|
Get(key string) (TValues, error)
|
|
}
|
|
|
|
type dynamicMap[TEnv, TValues any, TMap Getter[TValues]] struct {
|
|
accessor func(TEnv) (TMap, error)
|
|
}
|
|
|
|
// DynamicMap returns a definition for a variable that can be indexed with
|
|
// map[key] or map.key syntax to get a TValues, or passed directly to another
|
|
// function as TMap. TMap must implement Getter[TValues]. Each time the
|
|
// variable is reference in an expression, [accessor] will be called to retrieve
|
|
// the Getter.
|
|
func DynamicMap[TEnv any, TValues any, TMap Getter[TValues]](accessor func(TEnv) (TMap, error)) Variable {
|
|
return dynamicMap[TEnv, TValues, TMap]{accessor}
|
|
}
|
|
|
|
func (d dynamicMap[TEnv, TValues, TMap]) Evaluate(env TEnv) (TMap, error) {
|
|
m, err := d.accessor(env)
|
|
return m, trace.Wrap(err)
|
|
}
|
|
|
|
//nolint:unused // https://github.com/dominikh/go-tools/issues/1294
|
|
func (d dynamicMap[TEnv, TValues, TMap]) buildIndexExpression(keyExpr Expression[TEnv, string]) any {
|
|
return dynamicVariable[TEnv, TValues]{func(env TEnv) (TValues, error) {
|
|
var nul TValues
|
|
key, err := keyExpr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating key of index expression")
|
|
}
|
|
m, err := d.accessor(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating base of index expression")
|
|
}
|
|
v, err := m.Get(key)
|
|
return v, trace.Wrap(err, "getting value from dynamic map")
|
|
}}
|
|
}
|
|
|
|
type funcGetter[TValues any] func(key string) (TValues, error)
|
|
|
|
func (f funcGetter[TValues]) Get(key string) (TValues, error) {
|
|
return f(key)
|
|
}
|
|
|
|
// DynamicMapFunction returns a definition for a variable that can be indexed
|
|
// with map[key] or map.key syntax to get a TValues. Each time the
|
|
// variable is indexed in an expression, [getFunc] will be called to retrieve
|
|
// the value.
|
|
func DynamicMapFunction[TEnv, TValues any](getFunc func(env TEnv, key string) (TValues, error)) Variable {
|
|
return DynamicMap[TEnv, TValues](func(env TEnv) (funcGetter[TValues], error) {
|
|
return funcGetter[TValues](func(key string) (TValues, error) {
|
|
return getFunc(env, key)
|
|
}), nil
|
|
})
|
|
}
|
|
|
|
type dynamicVariable[TEnv, TVar any] struct {
|
|
accessor func(TEnv) (TVar, error)
|
|
}
|
|
|
|
// DynamicVariable returns a normal variable definition. Whenever the variable is
|
|
// accessed during evaluation of an expression, accessor will be called to fetch
|
|
// the value of the variable from the current evaluation environment.
|
|
func DynamicVariable[TEnv, TVar any](accessor func(TEnv) (TVar, error)) Variable {
|
|
return dynamicVariable[TEnv, TVar]{accessor}
|
|
}
|
|
|
|
func (d dynamicVariable[TEnv, TVar]) Evaluate(env TEnv) (TVar, error) {
|
|
result, err := d.accessor(env)
|
|
return result, trace.Wrap(err)
|
|
}
|
|
|
|
type unaryFunction[TEnv, TArg, TResult any] struct {
|
|
impl func(TArg) (TResult, error)
|
|
}
|
|
|
|
// UnaryFunction returns a definition for a function that can be called with one
|
|
// argument. The argument may be a literal or a subexpression.
|
|
func UnaryFunction[TEnv, TArg, TResult any](impl func(TArg) (TResult, error)) Function {
|
|
return unaryFunction[TEnv, TArg, TResult]{impl}
|
|
}
|
|
|
|
func (f unaryFunction[TEnv, TArg, TResult]) buildExpression(name string, args ...any) (any, error) {
|
|
if len(args) != 1 {
|
|
return nil, trace.BadParameter("function (%s) accepts 1 argument, given %d", name, len(args))
|
|
}
|
|
argExpr, err := coerce[TEnv, TArg](args[0])
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing argument to (%s)", name)
|
|
}
|
|
return unaryFuncExpr[TEnv, TArg, TResult]{
|
|
name: name,
|
|
impl: f.impl,
|
|
argExpr: argExpr,
|
|
}, nil
|
|
}
|
|
|
|
type unaryFuncExpr[TEnv, TArg, TResult any] struct {
|
|
name string
|
|
impl func(TArg) (TResult, error)
|
|
argExpr Expression[TEnv, TArg]
|
|
}
|
|
|
|
func (e unaryFuncExpr[TEnv, TArg, TResult]) Evaluate(env TEnv) (TResult, error) {
|
|
var nul TResult
|
|
arg, err := e.argExpr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating argument to function (%s)", e.name)
|
|
}
|
|
res, err := e.impl(arg)
|
|
return res, trace.Wrap(err, "evaluating function (%s)", e.name)
|
|
}
|
|
|
|
type binaryFunction[TEnv, TArg1, TArg2, TResult any] struct {
|
|
impl func(TArg1, TArg2) (TResult, error)
|
|
}
|
|
|
|
// BinaryFunction returns a definition for a function that can be called with
|
|
// two arguments. The arguments may be literals or subexpressions.
|
|
func BinaryFunction[TEnv, TArg1, TArg2, TResult any](impl func(TArg1, TArg2) (TResult, error)) Function {
|
|
return binaryFunction[TEnv, TArg1, TArg2, TResult]{impl}
|
|
}
|
|
|
|
func (f binaryFunction[TEnv, TArg1, TArg2, TResult]) buildExpression(name string, args ...any) (any, error) {
|
|
if len(args) != 2 {
|
|
return nil, trace.BadParameter("function (%s) accepts 2 arguments, given %d", name, len(args))
|
|
}
|
|
arg1Expr, err := coerce[TEnv, TArg1](args[0])
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing first argument to (%s)", name)
|
|
}
|
|
arg2Expr, err := coerce[TEnv, TArg2](args[1])
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing second argument to (%s)", name)
|
|
}
|
|
return binaryFuncExpr[TEnv, TArg1, TArg2, TResult]{
|
|
name: name,
|
|
impl: f.impl,
|
|
arg1Expr: arg1Expr,
|
|
arg2Expr: arg2Expr,
|
|
}, nil
|
|
}
|
|
|
|
type binaryFuncExpr[TEnv, TArg1, TArg2, TResult any] struct {
|
|
name string
|
|
impl func(TArg1, TArg2) (TResult, error)
|
|
arg1Expr Expression[TEnv, TArg1]
|
|
arg2Expr Expression[TEnv, TArg2]
|
|
}
|
|
|
|
func (e binaryFuncExpr[TEnv, TArg1, TArg2, TResult]) Evaluate(env TEnv) (TResult, error) {
|
|
var nul TResult
|
|
arg1, err := e.arg1Expr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating first argument to function (%s)", e.name)
|
|
}
|
|
arg2, err := e.arg2Expr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating second argument to function (%s)", e.name)
|
|
}
|
|
res, err := e.impl(arg1, arg2)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating function (%s)", e.name)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
type ternaryFunction[TEnv, TArg1, TArg2, TArg3, TResult any] struct {
|
|
impl func(TArg1, TArg2, TArg3) (TResult, error)
|
|
}
|
|
|
|
// TernaryFunction returns a definition for a function that can be called with
|
|
// three arguments. The arguments may be literals or subexpressions.
|
|
func TernaryFunction[TEnv, TArg1, TArg2, TArg3, TResult any](impl func(TArg1, TArg2, TArg3) (TResult, error)) Function {
|
|
return ternaryFunction[TEnv, TArg1, TArg2, TArg3, TResult]{impl}
|
|
}
|
|
|
|
func (f ternaryFunction[TEnv, TArg1, TArg2, TArg3, TResult]) buildExpression(name string, args ...any) (any, error) {
|
|
if len(args) != 3 {
|
|
return nil, trace.BadParameter("function (%s) accepts 3 arguments, given %d", name, len(args))
|
|
}
|
|
arg1Expr, err := coerce[TEnv, TArg1](args[0])
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing first argument to (%s)", name)
|
|
}
|
|
arg2Expr, err := coerce[TEnv, TArg2](args[1])
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing second argument to (%s)", name)
|
|
}
|
|
arg3Expr, err := coerce[TEnv, TArg3](args[2])
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing third argument to (%s)", name)
|
|
}
|
|
return ternaryFuncExpr[TEnv, TArg1, TArg2, TArg3, TResult]{
|
|
name: name,
|
|
impl: f.impl,
|
|
arg1Expr: arg1Expr,
|
|
arg2Expr: arg2Expr,
|
|
arg3Expr: arg3Expr,
|
|
}, nil
|
|
}
|
|
|
|
type ternaryFuncExpr[TEnv, TArg1, TArg2, TArg3, TResult any] struct {
|
|
name string
|
|
impl func(TArg1, TArg2, TArg3) (TResult, error)
|
|
arg1Expr Expression[TEnv, TArg1]
|
|
arg2Expr Expression[TEnv, TArg2]
|
|
arg3Expr Expression[TEnv, TArg3]
|
|
}
|
|
|
|
func (e ternaryFuncExpr[TEnv, TArg1, TArg2, TArg3, TResult]) Evaluate(env TEnv) (TResult, error) {
|
|
var nul TResult
|
|
arg1, err := e.arg1Expr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating first argument to function (%s)", e.name)
|
|
}
|
|
arg2, err := e.arg2Expr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating second argument to function (%s)", e.name)
|
|
}
|
|
arg3, err := e.arg3Expr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating third argument to function (%s)", e.name)
|
|
}
|
|
res, err := e.impl(arg1, arg2, arg3)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating function (%s)", e.name)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
type unaryVariadicFunction[TEnv, TVarArgs, TResult any] struct {
|
|
impl func(...TVarArgs) (TResult, error)
|
|
}
|
|
|
|
// UnaryVariadicFunction returns a definition for a function that can be called
|
|
// with any number of arguments of a single type. The arguments may be literals
|
|
// or subexpressions.
|
|
func UnaryVariadicFunction[TEnv, TVarArgs, TResult any](impl func(...TVarArgs) (TResult, error)) Function {
|
|
return unaryVariadicFunction[TEnv, TVarArgs, TResult]{impl}
|
|
}
|
|
|
|
func (f unaryVariadicFunction[TEnv, TVarArgs, TResult]) buildExpression(name string, args ...any) (any, error) {
|
|
varArgExprs := make([]Expression[TEnv, TVarArgs], len(args))
|
|
for i, arg := range args {
|
|
argExpr, err := coerce[TEnv, TVarArgs](arg)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing argument %d to function (%s)", i+1, name)
|
|
}
|
|
varArgExprs[i] = argExpr
|
|
}
|
|
return unaryVariadicFuncExpr[TEnv, TVarArgs, TResult]{
|
|
name: name,
|
|
impl: f.impl,
|
|
varArgExprs: varArgExprs,
|
|
}, nil
|
|
}
|
|
|
|
type unaryVariadicFuncExpr[TEnv, TVarArgs, TResult any] struct {
|
|
name string
|
|
impl func(...TVarArgs) (TResult, error)
|
|
varArgExprs []Expression[TEnv, TVarArgs]
|
|
}
|
|
|
|
func (e unaryVariadicFuncExpr[TEnv, TVarArgs, TResult]) Evaluate(env TEnv) (TResult, error) {
|
|
var nul TResult
|
|
varArgs := make([]TVarArgs, len(e.varArgExprs))
|
|
for i, argExpr := range e.varArgExprs {
|
|
arg, err := argExpr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating argument %d to function (%s)", i+1, e.name)
|
|
}
|
|
varArgs[i] = arg
|
|
}
|
|
res, err := e.impl(varArgs...)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating function (%s)", e.name)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
type binaryVariadicFunction[TEnv, TArg1, TVarArgs, TResult any] struct {
|
|
impl func(TArg1, ...TVarArgs) (TResult, error)
|
|
}
|
|
|
|
// BinaryVariadicFunction returns a definition for a function that can be called
|
|
// with one or more arguments. The arguments may be literals or subexpressions.
|
|
func BinaryVariadicFunction[TEnv, TArg1, TVarArgs, TResult any](impl func(TArg1, ...TVarArgs) (TResult, error)) Function {
|
|
return binaryVariadicFunction[TEnv, TArg1, TVarArgs, TResult]{impl}
|
|
}
|
|
|
|
func (f binaryVariadicFunction[TEnv, TArg1, TVarArgs, TResult]) buildExpression(name string, args ...any) (any, error) {
|
|
if len(args) == 0 {
|
|
return nil, trace.BadParameter("function (%s) accepts 1 or more argument, given %d", name, len(args))
|
|
}
|
|
arg1Expr, err := coerce[TEnv, TArg1](args[0])
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing first argument to function (%s)", name)
|
|
}
|
|
args = args[1:]
|
|
varArgExprs := make([]Expression[TEnv, TVarArgs], len(args))
|
|
for i, arg := range args {
|
|
argExpr, err := coerce[TEnv, TVarArgs](arg)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing argument %d to function (%s)", i+2, name)
|
|
}
|
|
varArgExprs[i] = argExpr
|
|
}
|
|
return binaryVariadicFuncExpr[TEnv, TArg1, TVarArgs, TResult]{
|
|
name: name,
|
|
impl: f.impl,
|
|
arg1Expr: arg1Expr,
|
|
varArgExprs: varArgExprs,
|
|
}, nil
|
|
}
|
|
|
|
type binaryVariadicFuncExpr[TEnv, TArg1, TVarArgs, TResult any] struct {
|
|
name string
|
|
impl func(TArg1, ...TVarArgs) (TResult, error)
|
|
arg1Expr Expression[TEnv, TArg1]
|
|
varArgExprs []Expression[TEnv, TVarArgs]
|
|
}
|
|
|
|
func (e binaryVariadicFuncExpr[TEnv, TArg1, TVarArgs, TResult]) Evaluate(env TEnv) (TResult, error) {
|
|
var nul TResult
|
|
arg1, err := e.arg1Expr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating first argument to function (%s)", e.name)
|
|
}
|
|
varArgs := make([]TVarArgs, len(e.varArgExprs))
|
|
for i, argExpr := range e.varArgExprs {
|
|
arg, err := argExpr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating argument %d to function (%s)", i+2, e.name)
|
|
}
|
|
varArgs[i] = arg
|
|
}
|
|
res, err := e.impl(arg1, varArgs...)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating function (%s)", e.name)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
type ternaryVariadicFunction[TEnv, TArg1, TArg2, TVarArgs, TResult any] struct {
|
|
impl func(TArg1, TArg2, ...TVarArgs) (TResult, error)
|
|
}
|
|
|
|
// TernaryVariadicFunction returns a definition for a function that can be called
|
|
// with one or more arguments. The arguments may be literals or subexpressions.
|
|
func TernaryVariadicFunction[TEnv, TArg1, Targ2, TVarArgs, TResult any](impl func(TArg1, Targ2, ...TVarArgs) (TResult, error)) Function {
|
|
return ternaryVariadicFunction[TEnv, TArg1, Targ2, TVarArgs, TResult]{impl}
|
|
}
|
|
|
|
func (f ternaryVariadicFunction[TEnv, TArg1, TArg2, TVarArgs, TResult]) buildExpression(name string, args ...any) (any, error) {
|
|
if len(args) < 2 {
|
|
return nil, trace.BadParameter("function (%s) accepts 2 or more arguments, given %d", name, len(args))
|
|
}
|
|
arg1Expr, err := coerce[TEnv, TArg1](args[0])
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing first argument to function (%s)", name)
|
|
}
|
|
arg2Expr, err := coerce[TEnv, TArg2](args[1])
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing second argument to function (%s)", name)
|
|
}
|
|
args = args[2:]
|
|
varArgExprs := make([]Expression[TEnv, TVarArgs], len(args))
|
|
for i, arg := range args {
|
|
argExpr, err := coerce[TEnv, TVarArgs](arg)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing argument %d to function (%s)", i+3, name)
|
|
}
|
|
varArgExprs[i] = argExpr
|
|
}
|
|
return ternaryVariadicFuncExpr[TEnv, TArg1, TArg2, TVarArgs, TResult]{
|
|
name: name,
|
|
impl: f.impl,
|
|
arg1Expr: arg1Expr,
|
|
arg2Expr: arg2Expr,
|
|
varArgExprs: varArgExprs,
|
|
}, nil
|
|
}
|
|
|
|
type ternaryVariadicFuncExpr[TEnv, TArg1, TArg2, TVarArgs, TResult any] struct {
|
|
name string
|
|
impl func(TArg1, TArg2, ...TVarArgs) (TResult, error)
|
|
arg1Expr Expression[TEnv, TArg1]
|
|
arg2Expr Expression[TEnv, TArg2]
|
|
varArgExprs []Expression[TEnv, TVarArgs]
|
|
}
|
|
|
|
func (e ternaryVariadicFuncExpr[TEnv, TArg1, TArg2, TVarArgs, TResult]) Evaluate(env TEnv) (TResult, error) {
|
|
var nul TResult
|
|
arg1, err := e.arg1Expr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating first argument to function (%s)", e.name)
|
|
}
|
|
arg2, err := e.arg2Expr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating second argument to function (%s)", e.name)
|
|
}
|
|
varArgs := make([]TVarArgs, len(e.varArgExprs))
|
|
for i, argExpr := range e.varArgExprs {
|
|
arg, err := argExpr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating argument %d to function (%s)", i+3, e.name)
|
|
}
|
|
varArgs[i] = arg
|
|
}
|
|
res, err := e.impl(arg1, arg2, varArgs...)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err, "evaluating function (%s)", e.name)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
type booleanOperator[TEnv, TArgs any] struct {
|
|
name string
|
|
f func(a, b TArgs) bool
|
|
}
|
|
|
|
func (b booleanOperator[TEnv, TArgs]) buildExpression(lhs, rhs any) (Expression[TEnv, bool], error) {
|
|
lhsExpr, err := coerce[TEnv, TArgs](lhs)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing lhs of (%s) operator", b.name)
|
|
}
|
|
rhsExpr, err := coerce[TEnv, TArgs](rhs)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing rhs of (%s) operator", b.name)
|
|
}
|
|
return booleanOperatorExpr[TEnv, TArgs]{b.name, b.f, lhsExpr, rhsExpr}, nil
|
|
}
|
|
|
|
type booleanOperatorExpr[TEnv, TArgs any] struct {
|
|
name string
|
|
f func(a, b TArgs) bool
|
|
lhsExpr, rhsExpr Expression[TEnv, TArgs]
|
|
}
|
|
|
|
func (b booleanOperatorExpr[TEnv, TArgs]) Evaluate(env TEnv) (bool, error) {
|
|
lhs, err := b.lhsExpr.Evaluate(env)
|
|
if err != nil {
|
|
return false, trace.Wrap(err, "evaluating lhs of (%s) operator", b.name)
|
|
}
|
|
rhs, err := b.rhsExpr.Evaluate(env)
|
|
if err != nil {
|
|
return false, trace.Wrap(err, "evaluating rhs of (%s) operator", b.name)
|
|
}
|
|
return b.f(lhs, rhs), nil
|
|
}
|
|
|
|
func and[TEnv any]() func(lhs, rhs any) (Expression[TEnv, bool], error) {
|
|
return booleanOperator[TEnv, bool]{
|
|
name: "&&",
|
|
f: func(lhs, rhs bool) bool { return lhs && rhs },
|
|
}.buildExpression
|
|
}
|
|
|
|
func or[TEnv any]() func(lhs, rhs any) (Expression[TEnv, bool], error) {
|
|
return booleanOperator[TEnv, bool]{
|
|
name: "||",
|
|
f: func(lhs, rhs bool) bool { return lhs || rhs },
|
|
}.buildExpression
|
|
}
|
|
|
|
func eq[TEnv any]() func(lhs, rhs any) (Expression[TEnv, bool], error) {
|
|
return booleanOperator[TEnv, string]{
|
|
name: "==",
|
|
f: func(lhs, rhs string) bool { return lhs == rhs },
|
|
}.buildExpression
|
|
}
|
|
|
|
func neq[TEnv any]() func(lhs, rhs any) (Expression[TEnv, bool], error) {
|
|
return booleanOperator[TEnv, string]{
|
|
name: "!=",
|
|
f: func(lhs, rhs string) bool { return lhs != rhs },
|
|
}.buildExpression
|
|
}
|
|
|
|
type notExpr[TEnv any] struct {
|
|
tExpr Expression[TEnv, bool]
|
|
}
|
|
|
|
func (e notExpr[TEnv]) Evaluate(env TEnv) (bool, error) {
|
|
t, err := e.tExpr.Evaluate(env)
|
|
if err != nil {
|
|
return false, trace.Wrap(err, "evaluating target of (!) operator")
|
|
}
|
|
return !t, nil
|
|
}
|
|
|
|
func not[TEnv any](a any) (Expression[TEnv, bool], error) {
|
|
tExpr, err := coerce[TEnv, bool](a)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "parsing target of (!) operator")
|
|
}
|
|
return notExpr[TEnv]{tExpr}, nil
|
|
}
|
|
|
|
type LiteralExpr[TEnv, T any] struct {
|
|
Value T
|
|
}
|
|
|
|
func (l LiteralExpr[TEnv, T]) Evaluate(TEnv) (T, error) {
|
|
return l.Value, nil
|
|
}
|
|
|
|
// coerce is called at parse time to attempt to convert arg to an
|
|
// Expression[TEnv, TArg]. In most cases (for valid expressions) we can convert
|
|
// all arguments to known types at parse time so that reflection is not
|
|
// necessary at evaluation time.
|
|
func coerce[TEnv, TArg any](arg any) (Expression[TEnv, TArg], error) {
|
|
if typedArgExpr, ok := arg.(Expression[TEnv, TArg]); ok {
|
|
// This is already an expression returning exactly the expected type, it
|
|
// can be returned unmodified.
|
|
return typedArgExpr, nil
|
|
}
|
|
|
|
// any(*new(TArg)) is a trick to create an interface wrapping an instance of
|
|
// the generic type TArg so that we can do a type assertion on TArg.
|
|
if _, ok := any(*new(TArg)).([]string); ok {
|
|
// If we are expecting a []string and given a string, wrap it in a slice.
|
|
// This happens at parse time without reflection during evaluation.
|
|
// It's probably possible to do this for any slice type with heavy use
|
|
// of reflect, but for now []string is sufficient.
|
|
//
|
|
// This enables functions like strings.upper(str) to accept lists or
|
|
// single strings, which is common in existing predicate expressions.
|
|
var sliceExpr Expression[TEnv, []string]
|
|
switch typedArg := arg.(type) {
|
|
case string:
|
|
sliceExpr = LiteralExpr[TEnv, []string]{[]string{typedArg}}
|
|
case []string:
|
|
// This case will be necessary if we caught a slice literal.
|
|
sliceExpr = LiteralExpr[TEnv, []string]{typedArg}
|
|
case Expression[TEnv, string]:
|
|
sliceExpr = dynamicVariable[TEnv, []string]{
|
|
func(env TEnv) ([]string, error) {
|
|
str, err := typedArg.Evaluate(env)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return []string{str}, nil
|
|
},
|
|
}
|
|
default:
|
|
return nil, unexpectedTypeError[TArg](arg)
|
|
}
|
|
// We know TArg is []string so this assertion is safe.
|
|
return sliceExpr.(Expression[TEnv, TArg]), nil
|
|
}
|
|
|
|
if anyExpr, ok := arg.(Expression[TEnv, any]); ok {
|
|
// This is an expression evaluating to (any). We cannot check
|
|
// the type until evaluation time. The programmer must opt into this
|
|
// behavior by configuring a function that returns (any).
|
|
//
|
|
// This enables the result of functions like
|
|
// "ifelse(condition, optionA, optionB)" (which can return any type) to
|
|
// be used as arguments to other functions expecting specific types.
|
|
return dynamicVariable[TEnv, TArg]{
|
|
func(env TEnv) (TArg, error) {
|
|
var nul TArg
|
|
a, err := anyExpr.Evaluate(env)
|
|
if err != nil {
|
|
return nul, trace.Wrap(err)
|
|
}
|
|
if typedArg, ok := a.(TArg); ok {
|
|
return typedArg, nil
|
|
}
|
|
return nul, unexpectedTypeError[TArg](a)
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
if evaluateMethod, ok := reflect.TypeOf(arg).MethodByName("Evaluate"); ok {
|
|
// Sanity check the Evaluate method actually returns two results,
|
|
// the second of which must be an error
|
|
if evaluateMethod.Type.NumOut() != 2 ||
|
|
reflect.PointerTo(evaluateMethod.Type.Out(1)) != reflect.TypeOf((*error)(nil)) {
|
|
return nil, unexpectedTypeError[TArg](arg)
|
|
}
|
|
|
|
// This argument is an Expr with a result type other than TArg or any.
|
|
// If TArg is an interface and the result type implements TArg, we can
|
|
// make it work. TArg is likely to be (any).
|
|
//
|
|
// This enables functions like "ifelse(condition, optionA, optionB)" to
|
|
// accept any type as an argument, and methods like
|
|
// 'traits["groups"].remove("admins")' to work on any receiver
|
|
// implementing a "remover" interface.
|
|
//
|
|
// Since the result of arg has an unknown type, we can not assert it to
|
|
// a specific Expr[TEnv, T], so we must check the interface
|
|
// implementation and call the Evaluate method via reflection.
|
|
|
|
// Make sure that the result type implements TArg at parse time, and
|
|
// return a parse error if not.
|
|
expectedType := reflect.TypeOf(new(TArg)).Elem()
|
|
resultType := evaluateMethod.Type.Out(0)
|
|
if expectedType.Kind() != reflect.Interface || !resultType.Implements(expectedType) {
|
|
// The result type does not implement TArg
|
|
return nil, unexpectedTypeError[TArg](arg)
|
|
}
|
|
|
|
return dynamicVariable[TEnv, TArg]{
|
|
func(env TEnv) (TArg, error) {
|
|
// The first argument to the method is the receiver.
|
|
resultValues := evaluateMethod.Func.Call([]reflect.Value{
|
|
reflect.ValueOf(arg), reflect.ValueOf(env),
|
|
})
|
|
// We can safely assert the results to TArg and error below
|
|
// because the method return types were checked above.
|
|
err := resultValues[1].Interface()
|
|
if err != nil {
|
|
var nul TArg
|
|
return nul, trace.Wrap(err.(error))
|
|
}
|
|
result := resultValues[0].Interface().(TArg)
|
|
return result, nil
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
if typedArg, ok := arg.(TArg); ok {
|
|
// This argument is a literal matching TArg. This must be checked last
|
|
// in case TArg is (any).
|
|
return LiteralExpr[TEnv, TArg]{typedArg}, nil
|
|
}
|
|
|
|
return nil, unexpectedTypeError[TArg](arg)
|
|
}
|
|
|
|
func unexpectedTypeError[TExpected any](v any) error {
|
|
prefix := fmt.Sprintf("expected type %s, ", reflect.TypeOf((*TExpected)(nil)).Elem())
|
|
evaluateMethod, ok := reflect.TypeOf(v).MethodByName("Evaluate")
|
|
if !ok {
|
|
// This isn't an expr
|
|
return trace.BadParameter(prefix+"got value (%+v) with type (%T)", v, v)
|
|
}
|
|
resultType := evaluateMethod.Type.Out(0)
|
|
return trace.BadParameter(prefix+"got expression returning type (%s)", resultType)
|
|
}
|