github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/caveats/env.go (about)

     1  package caveats
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/authzed/cel-go/cel"
     7  
     8  	"github.com/authzed/spicedb/pkg/caveats/types"
     9  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    10  )
    11  
    12  // Environment defines the evaluation environment for a caveat.
    13  type Environment struct {
    14  	variables map[string]types.VariableType
    15  }
    16  
    17  // NewEnvironment creates and returns a new environment for compiling a caveat.
    18  func NewEnvironment() *Environment {
    19  	return &Environment{
    20  		variables: map[string]types.VariableType{},
    21  	}
    22  }
    23  
    24  // EnvForVariables returns a new environment constructed for the given variables.
    25  func EnvForVariables(vars map[string]types.VariableType) (*Environment, error) {
    26  	e := NewEnvironment()
    27  	for varName, varType := range vars {
    28  		err := e.AddVariable(varName, varType)
    29  		if err != nil {
    30  			return nil, err
    31  		}
    32  	}
    33  	return e, nil
    34  }
    35  
    36  // MustEnvForVariables returns a new environment constructed for the given variables
    37  // or panics.
    38  func MustEnvForVariables(vars map[string]types.VariableType) *Environment {
    39  	env, err := EnvForVariables(vars)
    40  	if err != nil {
    41  		panic(err)
    42  	}
    43  	return env
    44  }
    45  
    46  // AddVariable adds a variable with the given type to the environment.
    47  func (e *Environment) AddVariable(name string, varType types.VariableType) error {
    48  	if _, ok := e.variables[name]; ok {
    49  		return fmt.Errorf("variable `%s` already exists", name)
    50  	}
    51  
    52  	e.variables[name] = varType
    53  	return nil
    54  }
    55  
    56  // EncodedParametersTypes returns the map of encoded parameters for the environment.
    57  func (e *Environment) EncodedParametersTypes() map[string]*core.CaveatTypeReference {
    58  	return types.EncodeParameterTypes(e.variables)
    59  }
    60  
    61  // asCelEnvironment converts the exported Environment into an internal CEL environment.
    62  func (e *Environment) asCelEnvironment() (*cel.Env, error) {
    63  	opts := make([]cel.EnvOption, 0, len(e.variables)+len(types.CustomTypes)+2)
    64  
    65  	// Add the custom types and functions.
    66  	for _, customTypeOpts := range types.CustomTypes {
    67  		opts = append(opts, customTypeOpts...)
    68  	}
    69  	opts = append(opts, types.CustomMethodsOnTypes...)
    70  
    71  	// Set options.
    72  	// DefaultUTCTimeZone: ensure all timestamps are evaluated at UTC
    73  	opts = append(opts, cel.DefaultUTCTimeZone(true))
    74  
    75  	// OptionalTypes: enable optional typing syntax, e.g. `sometype?.foo`
    76  	// See: https://github.com/google/cel-spec/wiki/proposal-246
    77  	opts = append(opts, cel.OptionalTypes(cel.OptionalTypesVersion(0)))
    78  
    79  	// EnableMacroCallTracking: enables tracking of call macros so when we call AstToString we get
    80  	// back out the expected expressions.
    81  	// See: https://github.com/authzed/cel-go/issues/474
    82  	opts = append(opts, cel.EnableMacroCallTracking())
    83  
    84  	// ParserExpressionSizeLimit: disable the size limit for codepoints in expressions.
    85  	// This has to be disabled due to us padding out the whitespace in expression parsing based on
    86  	// schema size. We instead do our own expression size check in the Compile method.
    87  	// TODO(jschorr): Remove this once the whitespace hack is removed.
    88  	opts = append(opts, cel.ParserExpressionSizeLimit(-1))
    89  
    90  	for name, varType := range e.variables {
    91  		opts = append(opts, cel.Variable(name, varType.CelType()))
    92  	}
    93  	return cel.NewEnv(opts...)
    94  }