The Common Expression Language (CEL) for Elixir: a small, safe expression language for rules, filters and policies. cel_ex parses, type-checks and evaluates CEL expressions against variables and messages.

It has no dependencies and knows nothing of protobuf. CEL's own types are always available, including the protobuf well-known types it builds in; message and enum types come from a CEL.TypeProvider you supply.

Installation

Add cel_ex to your dependencies in mix.exs:

def deps do
  [
    {:cel_ex, "~> 0.1"}
  ]
end

Quick start

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}}

For an expression evaluated many times, compile it once:

{:ok, program} = CEL.compile("age >= 18 && 'vip' in tags", env)

CEL.Program.eval(program, %{age: 30, tags: ["vip"]})
#=> {:ok, {:bool, true}}

CEL.compile/2 parses and type-checks; type errors are reported there, with the line and column of the problem, rather than when evaluating. Results are tagged CEL.Value terms, so {:int, 1} and {:uint, 1} stay distinct; CEL.Value.to_native/1 turns one back into a plain term.

What it does

  • Parse, check and evaluate CEL, with the standard macros and the strings, math, encoders and optional-value extensions — CEL, CEL.Env, CEL.Program.
  • Custom functions called from expressions — CEL.Function.
  • Unknown inputs: bind a variable to {:unknown, reason} and get back the inputs a result was waiting for — CEL.Unknown.
  • Inspection of checked expressions before evaluating them: the variables and functions they reference, their type and their tree — CEL.Checked, CEL.Expr.

Documentation

Message types

An environment's message and enum types come from a CEL.TypeProvider, given to CEL.Env.build/1 as :types. A provider is a {module, state} tuple; the module says which types exist, what their fields are, and how to read and build their values.

For protobuf messages, the pb_cel package provides one: PB.CEL.types/1 makes the messages and enums of a pb schema the types of an environment.

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}}

Conformance

CEL runs the upstream cel-spec simple conformance suite. The runner lives in the pb_cel project, because the conformance fixtures are protobuf messages; see pb_cel's conformance status for the results and the intentionally unsupported areas.

License

MIT