Skip to content

Extension Libraries

Everything beyond the core spec is an opt-in CelLibrary. A library bundles three things that register together: parse-time macros, runtime functions, and checker declarations (see internals for why all three exist).

using Celly.Extensions;

var env = CelEnv.Create(new CelEnvSettings
{
    Libraries =
    [
        OptionalsLibrary.Instance,
        StringsLibrary.Instance,
        MathLibrary.Instance,
        EncodersLibrary.Instance,
        BindingsLibrary.Instance,
        TwoVarComprehensionsLibrary.Instance,
        NetworkLibrary.Instance,
    ],
});

Enabling everything (e.g. to run the conformance suite)

Extensions are opt-in — a bare environment can't evaluate optional.of(...), 'x'.substring(1), math.greatest(...) etc., because those functions aren't registered. This is the same model as cel-go (ext.Strings(), cel.OptionalTypes(), …). To turn on the full feature set, add every library plus protobuf support:

using Celly.Extensions;
using Celly.Protobuf;

var registry = ProtoTypeRegistry.FromFiles(/* your proto descriptors */);
var env = CelEnv.Create(new CelEnvSettings
{
    TypeProvider = registry,
    Adapter = registry,
    Libraries =
    [
        OptionalsLibrary.Instance, StringsLibrary.Instance, MathLibrary.Instance,
        EncodersLibrary.Instance, BindingsLibrary.Instance, BlockLibrary.Instance,
        TwoVarComprehensionsLibrary.Instance, ProtosLibrary.Instance, NetworkLibrary.Instance,
    ],
});

See Conformance Testing for the exact configuration the test suite uses (and why running without it reports a misleadingly low pass rate).

Optionals

First-class "maybe a value" without null-tripping errors:

optional.of(5)                    // optional(5)
optional.none()                   // empty
optional.ofNonZeroValue('')       // empty ('' is a zero value)

m.?key                            // optional field select: none if missing
list[?10]                         // optional index: none if out of range
opt.hasValue()                    // bool
opt.value()                       // unwrap (error if none)
opt.orValue('default')            // unwrap with fallback
optA.or(optB)                     // first non-empty
opt.optMap(v, v * 2)              // transform if present
[?opt1, 3]                        // list literal: include only if present
{?'k': opt}                       // map/struct entry: include only if present

Chaining works through plain selects too: optional.of(m).a.b.orValue(x) — a missing step anywhere collapses to none instead of erroring.

Strings

Code-point-correct helpers on string (from the cel-spec strings extension):

'hello'.charAt(1)                 // 'e'
'hello mellow'.indexOf('ello', 2) // 7
['a', 'b'].join('-')              // 'a-b'
'hello'.replace('l', 'L', 1)      // 'heLlo'
'a,b,c'.split(',', 2)             // ['a', 'b,c']
'tacocat'.substring(1, 4)         // 'aco'
'  x  '.trim()                    // 'x'
'TacoCat'.lowerAscii()            // 'tacocat'
'foo'.reverse()                   // 'oof'
strings.quote('a"b')              // '"a\"b"'

'%s scored %.2f (%d percent)'.format(['amy', 9.5, 95])
// printf-style: %s %d %f %e %x %X %o %b %% with optional precision

Math

math.greatest(1, 5, 3)            // 5 (variadic, mixed numerics allowed)
math.least([2, 1.5, 3u])          // 1.5
math.ceil(1.2)  math.floor(1.8)  math.round(1.5)  math.trunc(-1.9)
math.abs(-5)    math.sign(-3.0)   math.sqrt(2)
math.isNaN(x)   math.isInf(x)     math.isFinite(x)
math.bitAnd(5, 3)  math.bitOr(...)  math.bitXor(...)  math.bitNot(x)
math.bitShiftLeft(1, 4)           // 16; shifts >= 64 yield 0, negative shift errors

Bindings & Block

cel.bind(v, expensive(), v + v)   // evaluate once, use many times

cel.block (+ cel.index) is the optimizer-internal form used by conformance tests — supported, but you'd normally never write it by hand.

Two-variable comprehensions

Index+value over lists, key+value over maps:

[1, 2, 3].all(i, v, v > i)                      // index, value
{'a': 1}.exists(k, v, k == 'a' && v == 1)       // key, value
[10, 20].transformList(i, v, v + i)             // [10, 21]
{'a': 1}.transformMap(k, v, v * 2)              // {'a': 2}
{'a': 1}.transformMapEntry(k, v, {v: k})        // invert: {1: 'a'}

Encoders

base64.encode(b'hello')           // 'aGVsbG8='
base64.decode('aGVsbG8')          // b'hello' (missing padding tolerated)

Proto2 extensions

proto.getExt(msg, my.pkg.int32_ext)     // read an extension field
proto.hasExt(msg, my.pkg.int32_ext)     // presence
msg.`my.pkg.int32_ext`                  // equivalent backtick form

Networking

Opaque net.IP and net.CIDR types with Go-compatible semantics:

ip('192.168.0.1').isGlobalUnicast()
isIP('not-an-ip')                        // false
cidr('192.168.0.0/24').containsIP('192.168.0.9')
cidr('192.168.0.1/24').masked()          // cidr('192.168.0.0/24')
ip('::ffff:c0a8:1') == ip('192.168.0.1') // true — IPv4-mapped normalizes
type(ip('::1')) == net.IP