# `CEL`
[🔗](https://github.com/hansihe/pb_ex/blob/v0.2.0/cel_ex/lib/cel.ex#L1)

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/2` turns source text into a `cel.expr.ParsedExpr` map.
  * `check/2` type-checks it against a `CEL.Env` (the variables, functions
    and message types it may use) into a `CEL.Checked`, which can be
    inspected with `CEL.Checked.expr/1` and
    `CEL.Checked.references/1`.
  * `compile/2` bundles a checked expression with its environment into a
    `CEL.Program`, which `CEL.Program.eval/2` evaluates 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 `t: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.Error`s 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.

# `parsed`

```elixir
@type parsed() :: %{expr: map(), source_info: map()}
```

A parsed expression: a `cel.expr.ParsedExpr` map with `:expr` and
`:source_info`, as `parse/2` returns.

# `check`

```elixir
@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.

# `check!`

```elixir
@spec check!(String.t() | parsed(), CEL.Env.t()) :: CEL.Checked.t()
```

Checks CEL source or a parsed expression, raising on parse or check errors.

# `compile`

```elixir
@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`.

# `compile!`

```elixir
@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.

# `eval`

```elixir
@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`.

# `eval!`

```elixir
@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.

# `parse`

```elixir
@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 in `CEL.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 to `true`.

Raises `CEL.OptionError` for an unknown option or an invalid value.

# `parse!`

```elixir
@spec parse!(
  String.t(),
  keyword()
) :: parsed()
```

Parses CEL source into a `cel.expr.ParsedExpr` map, raising on errors.

# `select`

```elixir
@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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
