CEL.Value (CEL v0.2.0)

Copy Markdown View Source

CEL values, as evaluation returns them and as variables can be bound.

Values are tagged so that CEL's distinctions survive, most importantly int versus uint and string versus bytes:

CELValue
null:null
bool, int, uint, double{:bool, true}, {:int, -1}, {:uint, 1}, {:double, 1.5} (:nan, :infinity and :negative_infinity for the special doubles)
string, bytes{:string, "a"}, {:bytes, <<1>>}
google.protobuf.Timestamp, Duration{:timestamp, unix_nanoseconds}, {:duration, nanoseconds}
list, map{:list, [value]}, {:map, %{value => value}}
a message{:message, name, payload}, its CEL.TypeProvider's name and payload
an enum value{:enum, name, number}, equal to {:int, number}
optional_type{:optional, :none}, {:optional, {:some, value}}
a type{:type, _}; type_name/1 gives its name, such as "int"

to_native/1 turns a value into a plain Elixir term. The constructors (int/1, string/1, ...) build values, for example in a custom function's result.

Summary

Functions

Converts a CEL value into a plain Elixir term, discarding the type tags.

The name of a type value, such as type(x) returns: "int", "string", "null_type", "list", "map", "type", "optional_type", "google.protobuf.Timestamp", or a message's or enum's full name.

Types

double_value()

@type double_value() :: float() | special_double()

special_double()

@type special_double() :: :nan | :infinity | :negative_infinity

t()

@type t() ::
  :null
  | {:bool, boolean()}
  | {:int, integer()}
  | {:uint, non_neg_integer()}
  | {:double, double_value()}
  | {:string, String.t()}
  | {:bytes, binary()}
  | CEL.Runtime.Time.timestamp()
  | CEL.Runtime.Time.duration()
  | {:enum, CEL.TypeProvider.name(), integer()}
  | {:object, String.t(), binary()}
  | {:list, [t()]}
  | {:map, %{required(t()) => t()}}
  | {:message, CEL.TypeProvider.name(), CEL.TypeProvider.payload()}
  | {:optional, :none | {:some, t()}}
  | {:type, term()}

Functions

bool(value)

@spec bool(boolean()) :: t()

bytes(value)

@spec bytes(binary()) :: t()

double(value)

@spec double(double_value()) :: t()

duration(value)

@spec duration(integer()) :: t()

enum(type, value)

@spec enum(CEL.TypeProvider.name(), integer()) :: t()

int(value)

@spec int(integer()) :: t()

list(values)

@spec list([t()]) :: t()

map(values)

@spec map(%{required(t()) => t()}) :: t()

null()

@spec null() :: t()

object(type_url, value)

@spec object(String.t(), binary()) :: t()

optional_none()

@spec optional_none() :: t()

optional_some(value)

@spec optional_some(t()) :: t()

string(value)

@spec string(String.t()) :: t()

timestamp(value)

@spec timestamp(integer()) :: t()

to_native(value)

@spec to_native(t()) :: term()

Converts a CEL value into a plain Elixir term, discarding the type tags.

CEL entry points (CEL.Program.eval/2, eval/3) return tagged values of type t/0 — {:int, 5}, {:string, "x"}, nested {:list, …}/{:map, …} — so that distinctions plain Elixir would erase (int vs uint, string vs bytes) survive evaluation. to_native/1 strips those tags off when you just want an ordinary value back; it is the rough inverse of infer/1.

Distinctions with no plain-Elixir counterpart are necessarily lost:

  • int and uint both become a plain integer/0.
  • string and bytes both become a plain binary/0.
  • an enum becomes its integer value.
  • a type becomes its name, as type_name/1 gives it.
  • null and an empty optional both become nil.
  • a non-finite double stays the atom :nan/:infinity/:negative_infinity, since Elixir floats cannot represent those.

A message becomes its payload, which its CEL.TypeProvider owns: for PB.CEL.types/1, the message's fields. Values that have no native Elixir form pass through unchanged in their t/0 shape: timestamp/duration and an un-decoded object (an Any whose type the provider does not know).

Examples

iex> CEL.Value.to_native({:int, 5})
5

iex> CEL.Value.to_native({:list, [{:int, 1}, {:string, "a"}]})
[1, "a"]

iex> CEL.Value.to_native({:map, %{{:string, "k"} => {:bool, true}}})
%{"k" => true}

type_name(arg)

@spec type_name(t()) :: String.t()

The name of a type value, such as type(x) returns: "int", "string", "null_type", "list", "map", "type", "optional_type", "google.protobuf.Timestamp", or a message's or enum's full name.

These are CEL's runtime type names, as in cel-go and cel-cpp: they have no parameters, so the type of [1] is "list". Type values are equal when their names are.

iex> {:ok, type} = CEL.eval("type([1])")
iex> CEL.Value.type_name(type)
"list"

uint(value)

@spec uint(non_neg_integer()) :: t()