CEL.TypeProvider behaviour (CEL v0.2.0)

Copy Markdown View Source

The message and enum types an environment knows, and how to read and build their values.

A provider is a {module, state} tuple, given to CEL.Env.build/1 as :types. module implements this behaviour, and every callback receives state first. Without :types there are no message or enum types; the CEL types, including the protobuf well-known types CEL builds in (google.protobuf.Timestamp, Duration, Any, Value, ListValue, Struct and the wrappers), are always available.

PB.CEL.types/1 is the provider for protobuf messages:

CEL.Env.build(types: PB.CEL.types(MyApp.Schema), container: "my.pkg")

Names

A message, enum or extension name is whatever term the provider uses for it, an atom or a string. CEL only compares names with == and shows them with to_string/1, and never makes one from text itself: names in an expression become the provider's through find_type/2, field names through find_field/3. to_string(name) must be the fully qualified name that find_type/2 resolves back to name.

Values carry the provider's names: a message is {:message, name, payload}, where the provider owns payload, and an enum value is {:enum, name, number}.

Summary

Types

A message, enum or extension name, in the provider's form.

A message value's payload, owned by the provider.

The provider's own state, passed to every callback.

t()

A provider: a module implementing this behaviour, and its state.

Callbacks

The number of an enum value, by the value's name.

Whether two messages of the type message are equal.

Looks up a field of a message type by name, or an extension by its fully qualified name.

Resolves a fully qualified name, without a leading dot, to a message or enum type.

Converts a variable's binding into a CEL value when the variable is declared with one of the provider's message or enum types and is not bound to a CEL value already.

Reads a field of a message, its default when the field is unset. field is one find_field/3 returned for the message's type.

Whether a field of a message is set, as CEL's has() tests it.

Builds the value of a message literal from the fields it sets, in the order written. Fields set to an empty optional are left out.

The fields a message literal must set, by name. Optional; without it, none are required.

Unpacks the payload of a google.protobuf.Any literal: the message its type URL names, decoded from value. :error when the provider does not know the type, which leaves the literal an object value. Optional; without it, every Any literal stays an object value.

Types

name()

@type name() :: atom() | String.t()

A message, enum or extension name, in the provider's form.

payload()

@type payload() :: term()

A message value's payload, owned by the provider.

state()

@type state() :: term()

The provider's own state, passed to every callback.

t()

@type t() :: {module(), state()}

A provider: a module implementing this behaviour, and its state.

Callbacks

enum_value(state, enum, value)

@callback enum_value(state(), enum :: name(), value :: String.t()) ::
  {:ok, integer()} | :error

The number of an enum value, by the value's name.

equal?(state, message, payload, payload)

@callback equal?(state(), message :: name(), payload(), payload()) :: boolean()

Whether two messages of the type message are equal.

find_field(state, message, field)

@callback find_field(state(), message :: name(), field :: String.t()) ::
  {:ok, CEL.Field.t()} | :error

Looks up a field of a message type by name, or an extension by its fully qualified name.

find_type(state, t)

@callback find_type(state(), String.t()) :: {:ok, :message | :enum, name()} | :error

Resolves a fully qualified name, without a leading dot, to a message or enum type.

from_native(state, name, term)

@callback from_native(state(), name(), term()) :: {:ok, CEL.Value.t()} | {:error, term()}

Converts a variable's binding into a CEL value when the variable is declared with one of the provider's message or enum types and is not bound to a CEL value already.

get_field(state, payload, t)

@callback get_field(state(), payload(), CEL.Field.t()) ::
  {:ok, CEL.Value.t()} | {:error, term()}

Reads a field of a message, its default when the field is unset. field is one find_field/3 returned for the message's type.

has_field?(state, payload, t)

@callback has_field?(state(), payload(), CEL.Field.t()) :: boolean()

Whether a field of a message is set, as CEL's has() tests it.

new_message(state, message, list)

@callback new_message(state(), message :: name(), [{CEL.Field.t(), CEL.Value.t()}]) ::
  {:ok, CEL.Value.t()} | {:error, term()}

Builds the value of a message literal from the fields it sets, in the order written. Fields set to an empty optional are left out.

required_fields(state, message)

(optional)
@callback required_fields(state(), message :: name()) :: [String.t()]

The fields a message literal must set, by name. Optional; without it, none are required.

unpack_any(state, type_url, value)

(optional)
@callback unpack_any(state(), type_url :: String.t(), value :: binary()) ::
  {:ok, CEL.Value.t()} | :error | {:error, term()}

Unpacks the payload of a google.protobuf.Any literal: the message its type URL names, decoded from value. :error when the provider does not know the type, which leaves the literal an object value. Optional; without it, every Any literal stays an object value.