# Writing BNDRY Policies BNDRY uses **CEL** (Common Expression Language) to power our entity risk ratings and policy engine. **CEL** is a simple expression language that can be used to evaluate expressions in a safe and efficient way. ## Literals | **Comment** | // (line comments only) | | --- | --- | | **Boolean** | true, false | | **Integer** | 42, -17, 0x2A (hex) | | **Unsigned** | 42u, 0x2Au | | **Float** | 3.14, -0.5, 6.022e23 | | **String** | "foo", 'bar', """multiline""" | | **Bytes** | b"data", b'data' | | **List** | [1, 2, 3] | | **Map** | {"a": 1, "b": 2} | | **Null** | null | ### Strings Strings can be enclosed in single quotes or double quotes. For multiline strings, use triple quotes (`"""` or `'''`). ```cel "Hello\nWorld" 'Single quoted' """Multiline string""" ``` Raw strings (prefixed with `r`) don't interpret escape sequences: ```cel r"\n\t" // literal backslash-n-backslash-t ``` ### Bytes Bytes literals are prefixed with `b`: ```cel b"hello" b'\xF0\x9F\xA4\xAA' // UTF-8 encoding of 🤪 ``` ## Operators | **Arithmetic** | +, -, *, /, % (modulus) | | --- | --- | | **Comparison** | ==, !=, <, >, <=, >= | | **Logical** | ! (not), && (and), || (or) | | **Conditional** | ? : (ternary) | | **Membership** | in | | **Indexing** | [], . | ### Membership Operators The `in` operator checks if an item exists in a list or a key exists in a map. ```cel 2 in [1, 2, 3] // true "name" in {"name": "John"} // true ``` ### Field Selection Access fields of messages or map values with `.` or `[]`: ```cel user.name user["name"] ``` Access list elements with `[]`: ```cel items[0] // first element items[-1] // last element ``` ### Conditional Operator The ternary operator evaluates a condition and returns one of two values: ```cel score >= 60 ? "pass" : "fail" ``` ### Logical Operators The `&&` and `||` operators provide commutative evaluation (may evaluate in any order): ```cel user.admin && user.active error || true // returns true, ignoring error ``` For traditional short-circuit evaluation, use the ternary operator: ```cel // Short-circuit AND condition ? second_condition : false // Short-circuit OR condition ? true : second_condition ``` ## String Functions ### contains(str, substring) Checks if a string contains a substring. ```cel "hello world".contains("world") // true ``` ### startsWith(str, prefix) Checks if a string starts with a prefix. ```cel "hello world".startsWith("hello") // true ``` ### endsWith(str, suffix) Checks if a string ends with a suffix. ```cel "hello world".endsWith("world") // true ``` ### matches(str, regex) Tests if a string matches a regular expression pattern (RE2 syntax). ```cel "foobar".matches("foo.*") // true matches("test123", "[a-z]+[0-9]+") // true ``` ### size(str) Returns the length of a string in Unicode code points. ```cel "hello".size() // 5 size("world!") // 6 ``` ## List Functions ### size(list) Returns the number of elements in a list. ```cel [1, 2, 3].size() // 3 size([]) // 0 ``` ### in operator Checks if a value exists in a list. ```cel 2 in [1, 2, 3] // true 5 in [1, 2, 3] // false ``` ## Map Functions ### size(map) Returns the number of entries in a map. ```cel {"a": 1, "b": 2}.size() // 2 ``` ### in operator Checks if a key exists in a map. ```cel "name" in {"name": "John", "age": 30} // true ``` ## Comprehension Macros ### has(field) Tests whether a field is set in a message or whether a key exists in a map. ```cel has(user.address) has(map.key_name) ``` ### all(var, predicate) Tests if all elements satisfy a condition. ```cel [1, 2, 3].all(x, x > 0) // true {"a": 1, "b": 2}.all(k, k != 'c') // true ``` ### exists(var, predicate) Tests if any element satisfies a condition. ```cel [1, 2, 3].exists(x, x > 2) // true [].exists(x, x > 0) // false ``` ### exists_one(var, predicate) Tests if exactly one element satisfies a condition. ```cel [1, 2, 3].exists_one(x, x == 2) // true [2, 2, 3].exists_one(x, x == 2) // false ``` ### map(var, transform) Transforms each element using an expression. ```cel [1, 2, 3].map(x, x * 2) // [2, 4, 6] ["a", "b"].map(s, s.upperAscii()) // ["A", "B"] ``` With filter predicate: ```cel [1, 2, 3, 4].map(x, x % 2 == 0, x * 2) // [4, 8] ``` ### filter(var, predicate) Returns elements that satisfy a condition. ```cel [1, 2, 3, 4].filter(x, x % 2 == 0) // [2, 4] {"a": 1, "b": 2}.filter(k, k == "a") // ["a"] ``` ## Timestamp and Duration Functions ### timestamp(string) Creates a timestamp from an RFC3339 string. ```cel timestamp("2023-08-26T12:39:00-07:00") ``` ### duration(string) Creates a duration from a string (supports h, m, s, ms, us, ns). ```cel duration("1h30m") duration("500ms") duration("-1.5h") ``` ### Timestamp Methods Get components of a timestamp: ```cel timestamp("2023-12-25T12:30:45Z").getFullYear() // 2023 timestamp("2023-12-25T12:30:45Z").getMonth() // 11 (December, 0-indexed) timestamp("2023-12-25T12:30:45Z").getDate() // 25 timestamp("2023-12-25T12:30:45Z").getHours() // 12 timestamp("2023-12-25T12:30:45Z").getMinutes() // 30 timestamp("2023-12-25T12:30:45Z").getSeconds() // 45 timestamp("2023-12-25T12:30:45Z").getDayOfWeek() // 1 (Monday) timestamp("2023-12-25T12:30:45Z").getDayOfYear() // 358 ``` With timezone: ```cel timestamp("2023-12-25T00:00:00Z").getDate("America/Los_Angeles") // 24 ``` ### Duration Methods Convert or extract from durations: ```cel duration("1h30m").getHours() // 1 duration("1h30m").getMinutes() // 90 duration("1.234s").getMilliseconds() // 234 ``` ### Arithmetic with Time Add or subtract time values: ```cel timestamp("2023-01-01T00:00:00Z") + duration("24h") timestamp("2023-01-10T12:00:00Z") - timestamp("2023-01-10T00:00:00Z") duration("1h") + duration("30m") ``` ## Type Conversion Functions ### int(value) Converts to a signed integer. ```cel int("123") // 123 int(3.14) // 3 int(timestamp("2023-08-26T12:00:00Z")) // Unix epoch seconds ``` ### uint(value) Converts to an unsigned integer. ```cel uint("123") // 123u uint(3.14) // 3u ``` ### double(value) Converts to a double. ```cel double(10) // 10.0 double("3.14") // 3.14 ``` ### string(value) Converts to a string. ```cel string(123) // "123" string(true) // "true" string(b'hello') // "hello" string(duration("1m1ms")) // "60.001s" ``` ### bytes(value) Converts to bytes. ```cel bytes("hello") // b'hello' bytes("🤪") // b'\xF0\x9F\xA4\xAA' ``` ### bool(value) Converts to boolean. ```cel bool("true") // true bool("FALSE") // false ``` ### type(value) Returns the type of a value. ```cel type(123) // int type("hello") // string type([1, 2]) // list ``` ### dyn(value) Marks a value as dynamically typed (for type-checking purposes). ```cel dyn([1, 3.14, "foo"]) // list(dyn) ``` ## Bytes Functions ### size(bytes) Returns the number of bytes. ```cel b'hello'.size() // 5 size(b'\xF0\x9F\xA4\xAA') // 4 ``` ## Common Patterns ### Safe Navigation Check field existence before accessing: ```cel has(user.profile) && user.profile.name == "John" ``` ### List Processing ```cel // Filter then transform orders.filter(o, o.total > 100).map(o, o.id) // Count matching items users.filter(u, u.age >= 18).size() // Check all items items.all(item, item.quantity > 0) ``` ### Map Operations ```cel // Check if key exists has(config.timeout) // Get value with default has(settings.retries) ? settings.retries : 3 // Filter map to list of keys {"a": 10, "b": 5, "c": 20}.filter(k, map[k] > 10) // ["c"] ``` ### Time-based Logic ```cel // Check if timestamp is in range now > timestamp("2023-01-01T00:00:00Z") && now < timestamp("2024-01-01T00:00:00Z") // Calculate age duration(now - user.birthdate).getHours() / 24 / 365 ``` ### String Validation ```cel // Email-like pattern user.email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$") // Required fields user.name.size() > 0 && user.email.size() > 0 ``` ## Error Handling CEL expressions can produce errors at runtime. Common errors include: - `no_matching_overload`: Function called with wrong argument types - `no_such_field`: Accessing a non-existent field - Division by zero - Type conversion failures - Overflow errors Use logical operators to handle potential errors: ```cel // This won't error even if division by zero occurs on left side x / 0 || true // returns true // Safe field access has(obj.field) && obj.field > 10 ``` ## Best Practices 1. **Use `has()` before accessing optional fields** ```cel has(user.address) && user.address.city == "NYC" ``` 2. **Prefer explicit type conversions** ```cel int(userInput) + 5 // instead of relying on implicit conversion ``` 3. **Use macros for list/map operations** ```cel items.all(x, x.valid) // instead of manual iteration ``` 4. **Chain operations clearly** ```cel users .filter(u, u.active) .map(u, u.email) .exists(e, e.endsWith("@company.com")) ``` 5. **Handle null values explicitly** ```cel user.name != null && user.name.size() > 0 ```