Extended Standard Library¶
The Rust cel crate implements the CEL core specification: the
has/all/exists/exists_one/map/filter macros, the
int/uint/double/string/bytes/timestamp/duration conversions,
size, and the string predicates contains/startsWith/endsWith/matches.
cel.stdlib adds the functions that the core crate does not ship, grouped
into libraries that mirror cel-go's
extension libraries. These functions are opt-in: evaluate() and
compile() expose only the Rust-native standard library by default.
Enabling the extensions¶
Register the functions on a [Context][cel.Context] and pass that context to
evaluate() (or Program.execute()):
import cel
from cel.stdlib import add_stdlib_to_context
ctx = cel.Context()
add_stdlib_to_context(ctx)
assert cel.evaluate('"Hello World".lowerAscii()', ctx) == "hello world"
assert cel.evaluate("math.greatest([3, 1, 2])", ctx) == 3
To register only some libraries, pass their names:
import cel
from cel.stdlib import add_stdlib_to_context
ctx = cel.Context()
add_stdlib_to_context(ctx, extensions=["math", "lists"])
assert cel.evaluate("[1, 1, 2, 3].distinct()", ctx) == [1, 2, 3]
The cel command-line tool enables every extension automatically.
Calling convention¶
Because CEL treats x.f(a) as sugar for f(x, a), every function below can be
called either as a method or as a free function. Namespaced functions such as
math.greatest use their dotted name.
import cel
from cel.stdlib import add_stdlib_to_context
ctx = cel.Context()
add_stdlib_to_context(ctx)
# Method and free-function forms are equivalent.
assert cel.evaluate('"hello".charAt(0)', ctx) == "h"
assert cel.evaluate('charAt("hello", 0)', ctx) == "h"
Libraries¶
core¶
bool, dyn, type, min, max.
import cel
from cel.stdlib import add_stdlib_to_context
ctx = cel.Context()
add_stdlib_to_context(ctx)
assert cel.evaluate('bool("true")', ctx) is True
assert cel.evaluate("dyn(5)", ctx) == 5
assert cel.evaluate("type(1) == type(2)", ctx) is True
assert cel.evaluate("min(3, 1, 2)", ctx) == 1
assert cel.evaluate("max([4, 9, 2])", ctx) == 9
type() limitations
type(x) returns the CEL type name as a string (e.g. "int"), so
type(x) == type(y) works but comparing against a bare type identifier
(type(x) == int) does not. Because Python has a single integer type, a CEL
uint is reported as "int".
strings¶
charAt, indexOf, lastIndexOf, substring, replace, split, join,
lowerAscii, upperAscii, trim, reverse, strings.quote.
import cel
from cel.stdlib import add_stdlib_to_context
ctx = cel.Context()
add_stdlib_to_context(ctx)
assert cel.evaluate('"hello world".indexOf("o")', ctx) == 4
assert cel.evaluate('"a,b,c".split(",")', ctx) == ["a", "b", "c"]
assert cel.evaluate('["a", "b", "c"].join("-")', ctx) == "a-b-c"
assert cel.evaluate('" padded ".trim()', ctx) == "padded"
assert cel.evaluate('"abc".reverse()', ctx) == "cba"
math¶
math.greatest, math.least, math.abs, math.sign, math.ceil,
math.floor, math.round, math.trunc, math.isNaN, math.isInf,
math.isFinite, math.sqrt, and the bit operations math.bitOr,
math.bitAnd, math.bitXor, math.bitNot, math.bitShiftLeft,
math.bitShiftRight.
import cel
from cel.stdlib import add_stdlib_to_context
ctx = cel.Context()
add_stdlib_to_context(ctx)
assert cel.evaluate("math.abs(-7)", ctx) == 7
assert cel.evaluate("math.round(2.5)", ctx) == 3.0 # half away from zero
assert cel.evaluate("math.sqrt(16.0)", ctx) == 4.0
assert cel.evaluate("math.bitOr(5, 2)", ctx) == 7
sets¶
sets.contains, sets.equivalent, sets.intersects.
import cel
from cel.stdlib import add_stdlib_to_context
ctx = cel.Context()
add_stdlib_to_context(ctx)
assert cel.evaluate("sets.contains([1, 2, 3], [1, 2])", ctx) is True
assert cel.evaluate("sets.equivalent([1, 2], [2, 1])", ctx) is True
assert cel.evaluate("sets.intersects([1, 2], [2, 3])", ctx) is True
encoders¶
base64.encode, base64.decode.
import cel
from cel.stdlib import add_stdlib_to_context
ctx = cel.Context()
add_stdlib_to_context(ctx)
assert cel.evaluate('base64.encode(b"hello")', ctx) == "aGVsbG8="
assert cel.evaluate('base64.decode("aGVsbG8=")', ctx) == b"hello"
lists¶
contains, distinct, flatten, slice, sort, reverse, first, last,
lists.range.
import cel
from cel.stdlib import add_stdlib_to_context
ctx = cel.Context()
add_stdlib_to_context(ctx)
assert cel.evaluate("[1, 2, 3].contains(2)", ctx) is True
assert cel.evaluate("[1, 1, 2, 3].distinct()", ctx) == [1, 2, 3]
assert cel.evaluate("[[1, 2], [3]].flatten()", ctx) == [1, 2, 3]
assert cel.evaluate("[3, 1, 2].sort()", ctx) == [1, 2, 3]
assert cel.evaluate("lists.range(3)", ctx) == [0, 1, 2]
first and last return CEL optional values, so combine them with the
optional member methods:
import cel
from cel.stdlib import add_stdlib_to_context
ctx = cel.Context()
add_stdlib_to_context(ctx)
assert cel.evaluate("[10, 20, 30].first().value()", ctx) == 10
assert cel.evaluate("[].first().orValue(-1)", ctx) == -1
contains and the built-in string overload
The Rust core provides a string-only contains. The lists extension adds
a multi-type contains for lists and maps, but the built-in string overload
still takes precedence for string.contains(string).
Registering your own functions¶
cel.stdlib is built on the ordinary custom-function API, so you can register
your own functions the same way. A function registered under one name is
callable both as f(x) and x.f():
import cel
ctx = cel.Context()
ctx.add_function("shout", lambda s: s.upper() + "!")
assert cel.evaluate('"hi".shout()', ctx) == "HI!"
assert cel.evaluate('shout("hi")', ctx) == "HI!"
See Extending CEL for more.