The Common Expression Language (CEL): parse, type-check and evaluate CEL expressions against variables and messages.
env = CEL.Env.build(vars: [age: :int, tags: {:list, :string}])
CEL.eval("age >= 18 && 'vip' in tags", %{age: 30, tags: ["vip"]}, env)
#=> {:ok, {:bool, true}}An expression goes through three steps, each also available on its own:
parse/2turns source text into acel.expr.ParsedExprmap.check/2type-checks it against aCEL.Env(the variables, functions and message types it may use) into aCEL.Checked, which can be inspected withCEL.Checked.expr/1andCEL.Checked.references/1.compile/2bundles a checked expression with its environment into aCEL.Program, whichCEL.Program.eval/2evaluates as many times as needed.
eval/3 does all three for a one-off evaluation.
Messages
CEL's own types are always there, including the protobuf well-known types
it builds in: google.protobuf.Timestamp and Duration, the wrappers,
Any, Value, ListValue and Struct. Message and enum types come from a
CEL.TypeProvider, given to CEL.Env.build/1 as :types. For protobuf
messages, PB.CEL.types/1 is one:
env =
CEL.Env.build(
types: PB.CEL.types(MyApp.Schema),
container: "my.pkg",
vars: [user: {:message, "my.pkg.User"}]
)
CEL.eval("user.age >= 18", %{user: %{age: 30}}, env)
#=> {:ok, {:bool, true}}Values
Variables are bound by name (atom or string keys) to plain Elixir terms, which
are converted using the variable's declared type: integers, floats, strings,
booleans, lists and maps, and DateTime for :timestamp. A variable of a
message or enum type takes what its provider converts, for PB.CEL.types/1
a message as PB.decode/4 returns it and an enum value's name as an atom or
its number. nil binds a wrapper variable to null.
Results are CEL.Value.t/0 terms, tagged so that CEL's distinctions
survive: {:int, 1} and {:uint, 1}, {:string, "a"} and {:bytes, "a"},
{:enum, :"pkg.Enum", 2}, with message and enum types named as their
provider names them. CEL.Value.to_native/1 turns one back into a plain
term.
Results
Evaluation returns {:ok, value}, {:error, %CEL.Error{}}, or
{:unknown, %CEL.Unknown{}} when the result depends on a variable bound
to {:unknown, reason} or on a custom function that returned one; see
CEL.Unknown. Parse and check errors are CEL.Errors too, with the
line and column of the problem in the source.
Custom functions
Register CEL.Function structs with CEL.Env.build/1's :functions
option. See the "Custom CEL functions" guide.
Summary
Functions
Checks CEL source or a parsed expression into a CEL.Checked value.
Checks CEL source or a parsed expression, raising on parse or check errors.
Checks CEL source or a parsed expression and bundles the result with the
supplied environment as a CEL.Program.
Checks CEL source or a parsed expression and bundles it with env, raising
on parse or check errors.
Evaluates CEL source against env with the supplied variable bindings.
Evaluates CEL source against env, raising on parse, check, or eval errors
and on unknown results.
Parses CEL source into a cel.expr.ParsedExpr map.
Parses CEL source into a cel.expr.ParsedExpr map, raising on errors.
Reads name.f1.f2... from values as evaluating that expression with env
would, but tells an unset field apart from one set to its default.
Types
Functions
@spec check(String.t() | parsed(), CEL.Env.t()) :: {:ok, CEL.Checked.t()} | {:error, CEL.Error.t()}
Checks CEL source or a parsed expression into a CEL.Checked value.
The second argument must be a CEL.Env.
Passing a pre-parsed expression skips parser resource limits and assumes the
AST came from parse/2 or another trusted producer.
@spec check!(String.t() | parsed(), CEL.Env.t()) :: CEL.Checked.t()
Checks CEL source or a parsed expression, raising on parse or check errors.
@spec compile(String.t() | parsed(), CEL.Env.t()) :: {:ok, CEL.Program.t()} | {:error, CEL.Error.t()}
Checks CEL source or a parsed expression and bundles the result with the
supplied environment as a CEL.Program.
Programs carry their environment so eval callers do not have to re-pass
types or function declarations on every call. See CEL.Program.
@spec compile!(String.t() | parsed(), CEL.Env.t()) :: CEL.Program.t()
Checks CEL source or a parsed expression and bundles it with env, raising
on parse or check errors.
@spec eval(String.t(), keyword() | map(), CEL.Env.t()) :: {:ok, CEL.Value.t()} | {:unknown, CEL.Unknown.t()} | {:error, CEL.Error.t()}
Evaluates CEL source against env with the supplied variable bindings.
This is the one-shot convenience that parses, checks, and evaluates source
in a single call. To compile once and evaluate many times, build a
CEL.Program with compile/2 and evaluate it with
CEL.Program.eval/2.
The evaluator returns tagged CEL.Value terms so CEL runtime
distinctions such as int/uint and string/bytes are preserved.
A variable bound to {:unknown, reason} is unknown. When the result depends
on unknown inputs, the result is {:unknown, %CEL.Unknown{}}; see
CEL.Unknown.
@spec eval!(String.t(), keyword() | map(), CEL.Env.t()) :: CEL.Value.t()
Evaluates CEL source against env, raising on parse, check, or eval errors
and on unknown results.
@spec parse( String.t(), keyword() ) :: {:ok, parsed()} | {:error, CEL.Error.t()}
Parses CEL source into a cel.expr.ParsedExpr map.
Options
:location- where the source came from, such as a file name. Parse and check errors report it inCEL.Error's:location. Defaults to"<input>".:macros- whether to expand the standard macros (has,all,exists,exists_one,map,filter,cel.bind,optMap,optFlatMap). Defaults totrue.
Raises CEL.OptionError for an unknown option or an invalid value.
Parses CEL source into a cel.expr.ParsedExpr map, raising on errors.
@spec select(CEL.Env.t(), keyword() | map(), atom() | String.t(), [String.t()]) :: {:ok, CEL.Value.t()} | :absent | {:unknown, CEL.Unknown.t()} | {:error, CEL.Error.t()}
Reads name.f1.f2... from values as evaluating that expression with env
would, but tells an unset field apart from one set to its default.
Returns :absent when has() is false at some step of the path: a field
with presence that is unset (for a proto3 field without presence, one that
holds its default), or a map key that is missing. Otherwise returns what
evaluation would. path is a list of field names or map keys, as in the
{:var, name, path} arguments of CEL.Checked.references/1; name must
be a variable declared in env.
env = CEL.Env.build(vars: [req: {:map, :string, :string}])
CEL.select(env, %{req: %{"a" => ""}}, "req", ["a"])
#=> {:ok, {:string, ""}}
CEL.select(env, %{req: %{}}, "req", ["a"])
#=> :absent