cel-go

repository·master·Indexed 25 days ago

https://github.com/cel-expr/cel-go

A Go implementation of the Common Expression Language (CEL), a non-Turing complete language designed for simplicity, speed, safety, and portability. It provides capabilities for lightweight expression evaluation, including support for Protocol Buffers, custom functions, macros, and extended libraries for math, strings, lists, and sets.

Tokens
10.4K
Snippets
27
Records
71
Agent score
84%

What's inside cel-go

  1. Understand the CEL Policy format

    master

    CEL Policy is a YAML-based format that extends the standard Common Expression Language (CEL). While standard CEL is limited to simple expressions without variables or modules, CEL Policy allows for expression composition, variable reuse, and nested rules. It is designed to be runtime-compatible with CEL, maintaining the same performance and safety guarantees, and is inspired by Kubernetes Admission Policy.

    A policy consists of a named instance of a rule which contains conditional outputs and sub-rules. The default evaluation semantic is FIRST_MATCH (top-down).

  2. Quickstart: Compile and Evaluate Expressions

    master

    To use CEL-Go, use cel.Compile() to create a program from an expression string. You must declare any variables used in the expression using cel.Variable(name, type). Once compiled, use prg.Eval(map[string]any) to execute the expression with a provided context of variables.

    package main
    
    import (
    	"fmt"
    	"log"
    
    	"github.com/google/cel-go/cel"
    )
    
    func main() {
    	prg, err := cel.Compile(`"Hello world! I'm " + name + "."`,
    		cel.Variable("name", cel.StringType),
    	)
    	if err != nil {
    		log.Fatalln(err)
    	}
    	out, _, err := prg.Eval(map[string]any{
    		"name": "CEL",
    	})
    	if err != nil {
    		log.Fatalln(err)
    	}
    	fmt.Println(out)
    	// Output: Hello world! I'm CEL.
    }
  3. Set up a CEL environment

    master

    To use CEL, you must first create an environment that defines the variables and their types available to the expressions. Use cel.NewEnv and provide cel.Variable definitions for each expected variable.

    import "github.com/google/cel-go/cel"
    
    env, err := cel.NewEnv(
        cel.Variable("name", cel.StringType),
        cel.Variable("group", cel.StringType),
    )
  4. Define a CEL Policy structure

    master

    A CEL Policy is defined using a YAML structure with the following top-level fields:

    • name (string): A system-specific identifier.
    • description (string): A human-readable description.
    • imports (list[string]): A list of aliases for simplifying type names.
    • rule (object): The primary entry point for computations.

    Rule Components

    Variables

    A rule can define an ordered list of variables. Variables are lazily evaluated and memoized. A variable must be declared before it is referenced.

    variables:
      - name: first_item
        expression: "1"
      - name: list_of_items
        expression: "[variables.first_item, 2, 3, 4]"

    Match and Condition

    The match block contains a sequence of matches evaluated top-down.

    • condition: A CEL expression that must evaluate to a bool. If absent, it defaults to true.
    • output: The result returned when the condition is met. All output expressions in a policy must have compatible types.

    Nesting

    You can nest a rule inside a match block to create scoped variables or fallback behavior (chaining). An unconditional nested rule (one without a condition) allows the engine to step back to the parent rule if no inner match is found.

    rule:
      match:
        - condition: "outer == 'condition_a'"
          rule:
            match:
              - condition: "inner == 'condition_a_1'"
                output: "'outer_a_inner_1'"
              - output: "'outer_a_inner_default'"
        - output: "'outer_default'"
  5. Use the CEL REPL

    master

    The CEL REPL is a command-line tool for experimenting with CEL expressions. By default, any input is interpreted as a CEL expression to evaluate. To modify the evaluation environment (variables, functions, options), use special commands prefixed with %.

    $ cd ./repl/main
    $ go run .
    CEL REPL
    %exit or EOF to quit.
    
    cel-repl> %let x = 10
    cel-repl> x + 5
    15 : int
  6. Use Two-Variable Comprehensions in CEL

    master

    Two-variable comprehensions allow you to iterate over lists or maps using two variables (e.g., index and value for lists, or key and value for maps). This extension provides advanced macros for testing predicates and transforming collections.

    Predicate Testing Macros

    • all: Returns true if all elements satisfy the predicate. Short-circuits on false.
      • <list>.all(indexVar, valueVar, <predicate>)
      • <map>.all(keyVar, valueVar, <predicate>)
    • exists: Returns true if any element satisfies the predicate. Short-circuits on true.
      • <list>.exists(indexVar, valueVar, <predicate>)
      • <map>.exists(keyVar, valueVar, <predicate>)
    • existsOne: Returns true if exactly one element satisfies the predicate. Does not short-circuit.
      • <list>.existsOne(indexVar, valueVar, <predicate>)
      • <map>.existsOne(keyVar, valueVar, <predicate>)

    Transformation Macros

    • transformList: Converts a collection into a list. Supports optional filtering.
      • <list>.transformList(indexVar, valueVar, <transform>)
      • <list>.transformList(indexVar, valueVar, <filter>, <transform>)
      • <map>.transformList(keyVar, valueVar, <transform>)
      • <map>.transformList(keyVar, valueVar, <filter>, <transform>)
    • transformMap: Converts a collection into a map where the key remains fixed and the value is transformed. Supports optional filtering.
      • <list>.transformMap(indexVar, valueVar, <transform>)
      • <list>.transformMap(indexVar, valueVar, <filter>, <transform>)
      • <map>.transformMap(keyVar, valueVar, <transform>)
      • <map>.transformMap(keyVar, valueVar, <filter>, <transform>)
    • transformMapEntry: Converts a collection into a map using a map literal as the transform. If the transform produces duplicate keys, it will error.
      • <list>.transformMapEntry(indexVar, valueVar, <transform>)
      • <list>.transformMapEntry(indexVar, valueVar, <filter>, <transform>)
      • <map>.transformMapEntry(keyVar, valueVar, <transform>)
      • `<map>.transformMapEntry(keyVar, valueVar, <filter>, <transform>)
  7. Handle partial state and unknown values

    master

    CEL supports evaluation with partial state using commutative logical operators && and ||. If an error or unknown value is encountered on one side, the other side is evaluated to determine the outcome.

    To track these unknowns, enable cel.OptTrackState via cel.EvalOptions. The details returned by Eval() can then be used with interpreter.Prune to generate a residual expression (an expression containing only the variables that were actually needed for the result).

  8. Use Imports to simplify complex types

    master

    The imports field allows you to use simple names for complex types (like protocol buffers) instead of fully qualified names within your CEL expressions.

    Without imports:

    rule:
      match:
        -   output: >
          dev.cel.example.ComplexDocument{
            title: "Example Document"
          }

    With imports:

    imports:
      -   name: dev.cel.example.ComplexDocument
    
    rule:
      match:
        -   output: >
            ComplexDocument{
              title: "Example Document"
            }
  9. Install the CEL REPL

    master

    To use the CEL REPL as a standalone binary, clone the repository, navigate to the REPL directory, and build it using Go. You can then move the resulting binary to a location in your $PATH to run it from anywhere.

    $ git clone git@github.com:google/cel-go.git ./cel-go
    $ cd ./cel-go/repl/main
    $ go build -o repl .
    # e.g. to your $PATH
    $ mv ./repl <install location>
  10. Perform Type Conversions and Introspection

    master

    CEL supports explicit type casting using int(), uint(), double(), string(), bytes(), and dyn(). You can inspect types using the type(x) function.

    prg, _ := cel.Compile(`type(42) == int`)
    out, _, _ := prg.Eval(cel.NoVars())
    fmt.Println(out) // true