Skip to content

CEL expressions

Dwara uses the Common Expression Language (CEL) for dynamic, safely-sandboxed expressions throughout the config. CEL is a small, side-effect-free expression language designed for embedding in applications -- it evaluates against a typed context and produces a typed result, with no I/O, no allocation beyond a budget, and a compile-time type check.

When to use this

Use CEL expressions when you need dynamic logic that the static config cannot express:

  • Route conditions based on request headers, query params, or method.
  • Header transforms derived from request context.
  • Rate-limit key derivation (per-tenant, per-API-key).
  • Policy conditions (IP allowlists, method restrictions).

CEL replaces ad-hoc template strings with a typed, sandboxed expression language that fails fast on type errors at compile time.

Status

The CEL engine is a compile-time capability (cel, default OFF; see Editions) and is not included in the published OSS binaries. The engine and the shared RequestContext type are complete and test-covered as library components. The config wiring for the use sites below has not landed yet -- the keys shown (match.condition, CEL-valued transform values, rate_limit.key, policy condition) are the target surface and are not in the generated configuration schema. This page documents the expression language and that target wiring.

The request context

All CEL expressions evaluate against a request context with the following fields:

FieldTypeDescription
request.methodstringHTTP method (GET, POST, etc.)
request.pathstringRequest path (after rewrite)
request.hoststringHost header value
request.headersmap<string, string>Request headers (case-sensitive keys)
request.querymap<string, string>Query parameters

Use sites

The four use sites share one RequestContext and one expression language. The YAML below illustrates the target config surface.

Route conditions

A route condition is a CEL expression that evaluates to a boolean. The route matches only if the expression returns true:

yaml
routes:
  - name: api-v2
    service: api-v2-service
    match:
      path: { type: prefix, value: /api }
      condition: 'request.headers["x-version"] == "v2"'
    action: { type: proxy }

Common patterns:

cel
// Header check
request.headers["x-version"] == "v2"

// Query param check
request.query["debug"] == "true"

// Method restriction
request.method == "GET" || request.method == "HEAD"

// Host suffix
request.host.endsWith(".internal")

Header transforms

A header transform is a CEL expression that evaluates to a string. The result is used as the header value:

yaml
routes:
  - name: api
    service: api-service
    match:
      path: { type: prefix, value: /api }
    action: { type: proxy }
    transforms:
      request_headers:
        set:
          x-forwarded-for: 'request.headers["x-forwarded-for"]'
          x-request-id: 'request.method + "-" + request.path'

Rate-limit keys

A rate-limit key is a CEL expression that evaluates to a string. The result is used as the rate-limit bucket key:

yaml
routes:
  - name: api
    service: api-service
    match:
      path: { type: prefix, value: /api }
    action: { type: proxy }
    rate_limit:
      key: 'request.headers["x-api-key"] + ":" + request.path'
      requests_per_second: 100

Policy conditions

A policy condition is a CEL expression that evaluates to a boolean. The policy applies only if the expression returns true:

yaml
policies:
  - name: internal-only
    condition: 'request.headers["x-real-ip"] == "10.0.0.1"'
    # ... policy fields

Type checking

CEL expressions are type-checked at compile time. If an expression references a field that doesn't exist or has the wrong type, the config is rejected at load time:

Error: route condition compile: unknown field 'request.headerz'

This means typos and type mismatches are caught before the gateway starts, not at request time.

Performance

CEL expressions are compiled to an AOT bytecode representation at config load time. Evaluation at request time is a bytecode interpretation with a fuel budget -- no parsing, no compilation, no allocation beyond the result. Typical evaluation is sub-microsecond for simple expressions.

AOT compilation

The CEL engine compiles expressions ahead-of-time (AOT) into a bytecode program. This means:

  • Parse errors are caught at config load, not at request time.
  • The compiled program is reused across all requests.
  • No runtime parsing overhead.

Expression library

CEL supports the standard library: string methods (startsWith, endsWith, contains, matches), comparison operators, logical operators (&&, ||, !), arithmetic, and macros (all, exists, filter, map).

cel
// All headers start with "x-"
request.headers.all(k, k.startsWith("x-"))

// Exists a header with value "admin"
request.headers.exists(k, request.headers[k] == "admin")

// String concatenation
request.method + " " + request.path

See the CEL spec for the full language reference.

Runnable demo

Run the demo stack: demos/08-extensibility/ in the repository (test script: test-03-cel-expressions.sh). The cel cargo feature is off in the default demo image, so the script documents the CEL surface and verifies the proxy path; the README covers prerequisites, custom builds, and teardown.