typical

package
v0.0.0-...-5c79d48 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 15, 2024 License: AGPL-3.0 Imports: 9 Imported by: 0

Documentation

Overview

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.

Example
/*
 * Teleport
 * Copyright (C) 2023  Gravitational, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package main

import (
	"fmt"
	"slices"

	"github.com/gravitational/teleport/lib/utils/typical"
)

type expressionEnv struct {
	traits map[string][]string
	labels func(key string) string
}

func main() {
	parser, err := typical.NewParser[expressionEnv, bool](typical.ParserSpec{
		Variables: map[string]typical.Variable{
			"traits": typical.DynamicVariable(func(e expressionEnv) (map[string][]string, error) {
				return e.traits, nil
			}),
			"labels": typical.DynamicMapFunction(func(e expressionEnv, key string) (string, error) {
				return e.labels(key), nil
			}),
			"true":  true,
			"false": false,
		},
		Functions: map[string]typical.Function{
			"contains": typical.BinaryFunction[expressionEnv](func(list []string, item string) (bool, error) {
				return slices.Contains(list, item), nil
			}),
			"contains_all": typical.BinaryVariadicFunction[expressionEnv](func(list []string, strs ...string) (bool, error) {
				for _, str := range strs {
					if !slices.Contains(list, str) {
						return false, nil
					}
				}
				return true, nil
			}),
		},
	})
	if err != nil {
		fmt.Println(err)
		return
	}

	env := expressionEnv{
		traits: map[string][]string{
			"groups": {"devs", "security"},
		},
		labels: func(key string) string {
			if key == "owner" {
				return "devs"
			}
			return ""
		},
	}

	for _, expr := range []string{
		`contains(traits["groups"], labels["owner"])`,
		`contains_all(traits["groups"], "devs", "admins")`,
		`contains(traits["groups"], false)`,
	} {
		parsed, err := parser.Parse(expr)
		if err != nil {
			fmt.Println("parse error:", err)
			continue
		}
		match, err := parsed.Evaluate(env)
		if err != nil {
			fmt.Println("evaluation error:", err)
			continue
		}
		fmt.Println(match)
	}
}
Output:

true
false
parse error: parsing expression
	parsing second argument to (contains)
		expected type string, got value (false) with type (bool)

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CachedParser

type CachedParser[TEnv, TResult any] struct {
	Parser[TEnv, TResult]
	// contains filtered or unexported fields
}

CachedParser is a Parser that caches each parsed expression.

func NewCachedParser

func NewCachedParser[TEnv, TResult any](spec ParserSpec, opts ...ParserOption) (*CachedParser[TEnv, TResult], error)

NewCachedParser creates a cached predicate expression parser with the given specification.

func (*CachedParser[TEnv, TResult]) Parse

func (c *CachedParser[TEnv, TResult]) Parse(expression string) (Expression[TEnv, TResult], error)

Parse checks if [expression] is already present in the cache and returns the cached version if present, or else parses the expression to produce an Expression[TEnv, TResult] which is stored in the cache and returned.

type Expression

type Expression[TEnv, TResult any] interface {
	// Evaluate evaluates the already parsed predicate expression with the given
	// environment.
	Evaluate(environment TEnv) (TResult, error)
}

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 Function

type Function interface {
	// contains filtered or unexported methods
}

Function holds the definition of a function. It is expected to be the result of calling one of (Unary|Binary|Ternary)(Variadic)?Function.

func BinaryFunction

func BinaryFunction[TEnv, TArg1, TArg2, TResult any](impl func(TArg1, TArg2) (TResult, error)) Function

BinaryFunction returns a definition for a function that can be called with two arguments. The arguments may be literals or subexpressions.

func BinaryVariadicFunction

func BinaryVariadicFunction[TEnv, TArg1, TVarArgs, TResult any](impl func(TArg1, ...TVarArgs) (TResult, error)) Function

BinaryVariadicFunction returns a definition for a function that can be called with one or more arguments. The arguments may be literals or subexpressions.

func TernaryFunction

func TernaryFunction[TEnv, TArg1, TArg2, TArg3, TResult any](impl func(TArg1, TArg2, TArg3) (TResult, error)) Function

TernaryFunction returns a definition for a function that can be called with three arguments. The arguments may be literals or subexpressions.

func TernaryVariadicFunction

func TernaryVariadicFunction[TEnv, TArg1, Targ2, TVarArgs, TResult any](impl func(TArg1, Targ2, ...TVarArgs) (TResult, error)) Function

TernaryVariadicFunction returns a definition for a function that can be called with one or more arguments. The arguments may be literals or subexpressions.

func UnaryFunction

func UnaryFunction[TEnv, TArg, TResult any](impl func(TArg) (TResult, error)) Function

UnaryFunction returns a definition for a function that can be called with one argument. The argument may be a literal or a subexpression.

func UnaryFunctionWithEnv

func UnaryFunctionWithEnv[TEnv, TArg, TResult any](impl func(TEnv, TArg) (TResult, error)) Function

UnaryFunctionWithEnv returns a definition for a function that can be called with one argument. The argument may be a literal or a subexpression. The [impl] will be called with the evaluation env as the first argument, followed by the actual argument passed in the expression.

func UnaryVariadicFunction

func UnaryVariadicFunction[TEnv, TVarArgs, TResult any](impl func(...TVarArgs) (TResult, error)) Function

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.

type Getter

type Getter[TValues any] interface {
	Get(key string) (TValues, error)
}

Getter is a generic interface for map-like values that allow you to Get a TValues by key

type LiteralExpr

type LiteralExpr[TEnv, T any] struct {
	Value T
}

func (LiteralExpr[TEnv, T]) Evaluate

func (l LiteralExpr[TEnv, T]) Evaluate(TEnv) (T, error)

type Parser

type Parser[TEnv, TResult any] struct {
	// contains filtered or unexported fields
}

Parser is a predicate expression parser configured to parse expressions of a specific expression language.

func NewParser

func NewParser[TEnv, TResult any](spec ParserSpec, opts ...ParserOption) (*Parser[TEnv, TResult], error)

NewParser creates a predicate expression parser with the given specification.

func (*Parser[TEnv, TResult]) Parse

func (p *Parser[TEnv, TResult]) Parse(expression string) (Expression[TEnv, TResult], error)

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.

type ParserOption

type ParserOption func(*parserOptions)

ParserOption is an optional option for configuring a Parser.

func WithInvalidNamespaceHack

func WithInvalidNamespaceHack() ParserOption

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

type ParserSpec

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
}

ParserSpec defines a predicate language.

type UnknownIdentifierError

type UnknownIdentifierError string

UnknownIdentifierError is an error type that can be used to identify errors related to an unknown identifier in an expression.

func (UnknownIdentifierError) Error

func (u UnknownIdentifierError) Error() string

func (UnknownIdentifierError) Identifier

func (u UnknownIdentifierError) Identifier() string

type Variable

type Variable any

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.

func DynamicMap

func DynamicMap[TEnv any, TValues any, TMap Getter[TValues]](accessor func(TEnv) (TMap, error)) Variable

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 DynamicMapFunction

func DynamicMapFunction[TEnv, TValues any](getFunc func(env TEnv, key string) (TValues, error)) Variable

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 DynamicVariable

func DynamicVariable[TEnv, TVar any](accessor func(TEnv) (TVar, error)) Variable

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL