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:
| CEL | Value |
|---|---|
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
@type double_value() :: float() | special_double()
@type special_double() :: :nan | :infinity | :negative_infinity
@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
@spec double(double_value()) :: t()
@spec enum(CEL.TypeProvider.name(), integer()) :: t()
@spec null() :: t()
@spec optional_none() :: t()
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:
intanduintboth become a plaininteger/0.stringandbytesboth become a plainbinary/0.- an
enumbecomes its integer value. - a
typebecomes its name, astype_name/1gives it. nulland an emptyoptionalboth becomenil.- a non-finite
doublestays 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}
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"
@spec uint(non_neg_integer()) :: t()