|
| 1 | +# RsCel Usage Guide |
| 2 | + |
| 3 | +RsCel implements the [Common Expression Language (CEL)](https://github.com/google/cel-spec) in Rust. This guide describes the expression syntax exposed by `rscel` and the default macros, type constructors, and functions that are preloaded when you build a `BindContext` with `BindContext::new()`. |
| 4 | + |
| 5 | +## Running an Expression |
| 6 | + |
| 7 | +```rust |
| 8 | +use rscel::{BindContext, CelContext, CelValue}; |
| 9 | + |
| 10 | +fn main() -> rscel::CelResult<()> { |
| 11 | + let mut programs = CelContext::new(); |
| 12 | + let mut bindings = BindContext::new(); |
| 13 | + |
| 14 | + programs.add_program_str("main", "greeting + ' ' + subject")?; |
| 15 | + bindings.bind_param("greeting", "hello".into()); |
| 16 | + bindings.bind_param("subject", "world".into()); |
| 17 | + |
| 18 | + let value = programs.exec("main", &bindings)?; |
| 19 | + assert_eq!(value, "hello world".into()); |
| 20 | + Ok(()) |
| 21 | +} |
| 22 | +``` |
| 23 | + |
| 24 | +`BindContext::new()` registers the macros and functions listed below and exposes CEL types ("int", "string", etc.) so that they can be compared or invoked as constructors inside an expression. |
| 25 | + |
| 26 | +## Language Basics |
| 27 | + |
| 28 | +- **Literals**: signed integers (`123`), unsigned integers (`123u`), floating point numbers (`3.14`, `.5`, `1.`), quoted strings (`"foo"` or `'foo'` with `\u`/`\x` escapes), byte strings (`b"abc"`), booleans, and `null`. |
| 29 | +- **Lists**: `[expr1, expr2, ...]`. Indexing uses zero-based integers; negative indices are allowed when compiled with the `neg_index` feature. |
| 30 | +- **Maps/Objects**: `{ 'key': value, other_key: value }`. Keys must resolve to strings at runtime. |
| 31 | +- **Access**: `obj.field` looks up a field or method; `value[index]` indexes lists, strings, bytes, or maps. |
| 32 | +- **Operators**: arithmetic (`+ - * / %`), comparison (`< <= > >= == !=`), logical (`!`, `&&`, `||` with short-circuit semantics), and membership (`lhs in rhs`). String membership checks substring containment; map membership checks for a key. |
| 33 | +- **Conditionals**: `condition ? when_true : when_false`. |
| 34 | +- **Match expressions**: `match value { case x < 0: ..., case type(string): ..., case _: ... }` supports comparison patterns, type tests (`int`, `uint`, `float`, `string`, `bool`, `bytes`, `list`, `object`, `null`, `timestamp`, `duration`), and a wildcard case. |
| 35 | +- **Format strings**: `f"hello {name}!"` interpolates expressions that must evaluate to strings. |
| 36 | +- **Truthiness**: numbers are truthy when non-zero, collections when non-empty, timestamps/durations/types always truthy, and `null`/errors are falsy. Logical operators and macros rely on this notion. |
| 37 | +- **Errors**: runtime errors propagate as special values; most helpers short-circuit when they encounter `CelError` instances. |
| 38 | + |
| 39 | +## Default Macros (`default_macros.rs`) |
| 40 | + |
| 41 | +Macros operate on unresolved bytecode and therefore require identifiers for loop variables. All macros are available both at compile and runtime. |
| 42 | + |
| 43 | +| Macro | Signature | Description | |
| 44 | +| --- | --- | --- | |
| 45 | +| `has(expr)` | `has(expr)` | Evaluates the expression and returns `true` unless a binding or attribute error occurs; other errors propagate. | |
| 46 | +| `coalesce(expr...)` | `coalesce(e1, e2, ...)` | Returns the first argument that does not resolve to `null` and is not a binding/attribute error; resolves each expression in order. | |
| 47 | +| `list.all(var, predicate)` | `[1,2,3].all(x, x < 5)` | Binds each element to `var` and ensures every iteration is truthy. Returns `false` upon the first falsy result. | |
| 48 | +| `list.exists(var, predicate)` | `[items].exists(x, test)` | Returns `true` as soon as one predicate evaluates truthy. | |
| 49 | +| `list.exists_one(var, predicate)` | `[items].exists_one(x, test)` | Requires exactly one truthy evaluation; returns `false` if zero or more than one match. | |
| 50 | +| `list.filter(var, predicate)` | `[items].filter(x, keep?)` | Builds a list of elements whose predicate is truthy. When invoked on a map, the identifier receives each key and returns the list of kept keys. | |
| 51 | +| `list.map(var, mapper)` | `[items].map(x, expr)` | Collects the mapper result for every element. A ternary form `[items].map(x, predicate, mapper)` first evaluates `predicate` and only maps elements where it is truthy. With maps, the variable receives each key and returns a list of mapped values. | |
| 52 | +| `list.reduce(acc, item, step, initial)` | `[items].reduce(curr, next, step_expr, seed)` | Initializes `curr` with `seed`; for each element binds `next` to the element, `curr` to the running total, evaluates `step_expr`, and stores the result back in `curr`. Returns the final accumulator. | |
| 53 | + |
| 54 | +## Type Constructors (`type_funcs.rs`) |
| 55 | + |
| 56 | +These helpers can be invoked either as global functions (`int(value)`) or as methods (`value.int()` when dispatched that way) and mirror CEL's built-in type conversion rules. |
| 57 | + |
| 58 | +| Function | Accepted Inputs | Output / Notes | |
| 59 | +| --- | --- | --- | |
| 60 | +| `bool(x)` | `bool`, supported strings (`"1"`, `"0"`, `"t"`, `"f"`, case-insensitive `"true"`/`"false"`), and under the optional `type_prop` feature any value | Returns a boolean or raises `value()` on invalid strings. | |
| 61 | +| `int(x)` | `int`, `uint`, `float`, `bool`, `string` (parsed as base-10), `timestamp` | Converts to `i64`; parsing failures raise `value()` errors. | |
| 62 | +| `uint(x)` | `uint`, `int`, `float`, `bool`, `string` | Converts to `u64`; negative numbers or invalid strings raise errors. | |
| 63 | +| `double(x)` / `float(x)` | `float`, `int`, `uint`, `bool`, `string` | Produces an `f64`; parsing errors surface as `value()` errors. | |
| 64 | +| `string(x)` | numbers, strings, UTF-8 bytes, timestamps (RFC3339), durations, others | Converts to string; non UTF-8 bytes produce an error. | |
| 65 | +| `bytes(x)` | strings, existing bytes | Returns a byte array (`CelBytes`). | |
| 66 | +| `timestamp()` | no args, RFC3339 / RFC2822 / `DateTime<Utc>` / epoch seconds (`int`/`uint`) | Returns a UTC timestamp; invalid formats raise errors. | |
| 67 | +| `duration(x)` | ISO-like duration strings understood by `duration_str`, integer seconds, `(seconds, nanos)` pair, `chrono::Duration` | Returns a `Duration`; invalid formats raise errors. | |
| 68 | +| `dyn(x)` | any value | Identity; exposes the dynamic value type. | |
| 69 | +| `type(x)` | any value | Returns the CEL type descriptor (e.g., `int`, `string`, `timestamp`). | |
| 70 | + |
| 71 | +The constructor names (`int`, `uint`, `double`, `bytes`, etc.) are also exported as type values in the global scope, allowing comparisons such as `type(value) == int`. |
| 72 | + |
| 73 | +## Default Functions (`default_funcs.rs`) |
| 74 | + |
| 75 | +All functions can be called as free functions (`size(list)`) or as methods (`list.size()`, `'text'.contains('t')`). They return CEL values and propagate errors when arguments are invalid. |
| 76 | + |
| 77 | +### Collection helpers |
| 78 | + |
| 79 | +- `size(value)` – Length of a string, bytes, or list. |
| 80 | +- `sort(list)` – Returns a new list sorted using CEL ordering; non-comparable members yield `invalid_op` errors. |
| 81 | +- `zip(list1, list2, ...)` – Zips multiple lists into a list of same-length tuples (shortest list wins); arguments must all be lists. |
| 82 | +- `min(arg1, arg2, ...)` / `max(...)` – Vararg numeric/string comparator that returns the min/max; at least one argument required. |
| 83 | + |
| 84 | +### String and text helpers |
| 85 | + |
| 86 | +- `contains`, `containsI` – Substring containment (case-sensitive / case-insensitive). |
| 87 | +- `startsWith`, `startsWithI`, `endsWith`, `endsWithI` – Prefix/suffix checks. |
| 88 | +- `matches` – Returns `true` if a regex matches the entire string; invalid regex patterns raise a `value()` error. |
| 89 | +- `matchCaptures` – Returns a list of capture groups (entire match first) or `null` if the regex does not match. |
| 90 | +- `matchReplace`, `matchReplaceOnce` – Regex replacement across all matches or only the first match. |
| 91 | +- `remove` – Removes all non-overlapping occurrences of a literal substring. |
| 92 | +- `replace` – Literal string replacement. |
| 93 | +- `split`, `rsplit` – Split on a literal delimiter from the left/right. |
| 94 | +- `splitAt` – Splits at an index, returning `[left, right]`. |
| 95 | +- `splitWhiteSpace` – Splits on any Unicode whitespace. |
| 96 | +- `trim`, `trimStart`, `trimEnd` – Trim ASCII whitespace. |
| 97 | +- `trimStartMatches`, `trimEndMatches` – Trim a literal prefix/suffix repeatedly. |
| 98 | +- `toLower`, `toUpper` – Case conversion. |
| 99 | + |
| 100 | +All string helpers expect `this` to be a string; non-string inputs produce `value()` errors. |
| 101 | + |
| 102 | +### Math & numeric helpers |
| 103 | + |
| 104 | +- `abs(number)` – Absolute value for `int`, `uint`, and `double`. |
| 105 | +- `sqrt(number)` – Square root returning `double`. |
| 106 | +- `pow(base, exponent)` – Exponentiation for numeric combinations (integer exponents for integral bases). |
| 107 | +- `log(number)` – Base-10 logarithm (`ilog10` for integers/unsigned integers). |
| 108 | +- `lg(number)` - Base-2 logarithm |
| 109 | +- `ceil(number)`, `floor(number)`, `round(number)` – Standard rounding family; integral inputs are returned unchanged. |
| 110 | + |
| 111 | +### Time & date helpers |
| 112 | + |
| 113 | +All time functions operate on `timestamp()` or `duration()` results. Where noted, a second optional argument is an IANA timezone string (e.g., `"America/Los_Angeles"`) which is resolved using `chrono_tz`. |
| 114 | + |
| 115 | +- `getDate(timestamp[, timezone])` – Day of month (1–31). |
| 116 | +- `getDayOfMonth(timestamp[, timezone])` – Zero-based day of month (0–30). |
| 117 | +- `getDayOfWeek(timestamp[, timezone])` – Day of week (`0` = Sunday). |
| 118 | +- `getDayOfYear(timestamp[, timezone])` – Zero-based day of year. |
| 119 | +- `getFullYear(timestamp[, timezone])` – Four-digit year. |
| 120 | +- `getMonth(timestamp[, timezone])` – Zero-based month (`0` = January). |
| 121 | +- `getHours(timestamp | duration[, timezone])` – Hour of day or total hours of a duration. |
| 122 | +- `getMinutes(timestamp | duration[, timezone])` – Minute of hour or total minutes of a duration. |
| 123 | +- `getSeconds(timestamp | duration[, timezone])` – Second of minute or total seconds of a duration. |
| 124 | +- `getMilliseconds(timestamp | duration[, timezone])` – Millisecond component or total milliseconds of a duration. |
| 125 | +- `now()` – Current UTC timestamp (no arguments). |
| 126 | + |
| 127 | +### Unit conversion |
| 128 | + |
| 129 | +- `uomConvert(value, from_unit, to_unit)` – Converts between supported units using the [`uom`](https://docs.rs/uom) crate. Units are case-insensitive and trimmed of leading `°`. Supported categories: |
| 130 | + - **Mass**: kilogram (`kg`), gram (`g`), milligram, pound (`lb`, `lbs`), ounce (`oz`), stone, slug, ton/tonne. |
| 131 | + - **Volume**: liter (`l`), milliliter, gallon, quart (liquid/dry), pint (liquid/dry), cup, fluid ounce, tablespoon, teaspoon, cubic meter/foot/yard. |
| 132 | + - **Speed**: meter per second (`m/s`), kilometer per hour (`km/h`, `kph`), mile per hour (`mph`), knot, foot per second (`ft/s`, `fps`). |
| 133 | + - **Temperature**: Celsius (`C`), Fahrenheit (`F`), Kelvin (`K`). |
| 134 | + |
| 135 | + Conversions only succeed within the same category. Invalid or mixed-unit requests raise argument errors. |
| 136 | + |
| 137 | +### Miscellaneous helpers |
| 138 | + |
| 139 | +- `size(value)` – See Collections. |
| 140 | +- `sort(list)` – See Collections. |
| 141 | +- `zip(list, ...)` – See Collections. |
| 142 | + |
| 143 | +## Putting it together |
| 144 | + |
| 145 | +```text |
| 146 | +// Filter, map, and aggregate a bound list of structs. |
| 147 | +accounts.map(a, |
| 148 | + a.balance_cents > 0, |
| 149 | + { |
| 150 | + 'id': a.id, |
| 151 | + 'balance': a.balance_cents / 100 |
| 152 | + } |
| 153 | +).reduce(total, acct, total + acct.balance, 0) |
| 154 | +``` |
| 155 | + |
| 156 | +Combine macros and helpers freely. Errors or type mismatches surface as `CelError` instances, so guard with `has()` or `coalesce()` where appropriate. |
| 157 | + |
| 158 | +## Extending the environment |
| 159 | + |
| 160 | +You can bind additional values, functions, and macros via `BindContext::bind_param`, `bind_func`, and `bind_macro`. All defaults documented above remain available unless you intentionally replace them. |
| 161 | + |
0 commit comments