Skip to main content

CEL EXPRESSIONS

Swamp uses CEL (Common Expression Language) for inline expressions in model definitions, workflow configurations, and data queries.

Expression Syntax

Embed CEL expressions in YAML values with the ${{ }} wrapper:

globalArguments:
  greeting: "${{ 'Hello, ' + self.name }}"
  vpc_id: "${{ model.my_vpc.resource.result.result.attributes.vpcId }}"

When the entire YAML value is a single ${{ }} expression, the evaluated result preserves its type (number, boolean, list, map). When mixed with surrounding text, the result is coerced to a string.

Where Expressions Are Evaluated

Each location has access to a different set of context variables:

Location Available Context
Model globalArguments model, self, env, vault, data, file
Model inputs defaults model, self, env, vault, data, file
Workflow step task.inputs model, self, inputs, env, vault, data, file
Workflow step forEach.in model, self, inputs, env, vault, data, file
Data output overrides model, self, env, vault, data, file
swamp data query predicate DataRecord fields only (see Query Fields)
swamp data query --select DataRecord fields only

Context Variables

self

The current model definition being evaluated.

Field Type Description
self.id string Definition UUID
self.name string Model name
self.version int Definition version number
self.tags map<string, string> Definition tags
self.globalArguments map<string, dyn> Evaluated global arguments

When forEach is active, the iteration variable is available as a field on self. For example, forEach: { item: "env", in: [...] } adds self.env.

model

A map of all models in the repository, indexed by name and UUID.

model.<name>.input

Field Type Description
id string Definition UUID
name string Model name
version int Definition version
tags map<string, string> Definition tags
globalArguments map<string, dyn> Global arguments

model.<name>.definition

Field Type Description
id string Definition UUID
name string Model name
version int Definition version
tags map<string, string> Definition tags
globalArguments map<string, dyn> Global arguments
inputs map<string, dyn> Inputs schema

model.<name>.resource.<specName>.<instanceName>

Field Type Description
attributes map<string, dyn> Parsed JSON content
tags map<string, string> Metadata tags
version int Version number

model.<name>.file.<specName>.<instanceName>

Field Type Description
path string File path on disk
size int File size in bytes
contentType string MIME type

model.<name>.execution

Field Type Description
id string Execution ID
methodName string Method that was executed
status string succeeded, failed, etc.
startedAt string ISO 8601 timestamp
completedAt string ISO 8601 timestamp (if done)
durationMs int Execution duration
error map Error details (if failed)

Hyphenated Model Names

CEL interprets hyphens as subtraction. Swamp automatically transforms model.deploy-vpc.resource to model["deploy-vpc"].resource. Both forms work in expressions. Use bracket notation explicitly when needed:

model["deploy-vpc"].resource.result.result.attributes.vpcId

inputs

Input values provided when instantiating a definition. Available in workflow step task inputs.

inputs.vpc_id
inputs["hyphenated-name"]

env

The full process environment as a flat string map.

env.API_ENDPOINT
env.HOME

Caution

Values accessed via env are persisted to .swamp/data/ on disk. Use vault.get() for sensitive values.

vault

Secure secret access. Secrets are fetched at runtime and never written to model output data. Vault expressions are resolved before CEL evaluation — the secret value is substituted into the expression before it runs.

vault.get("my-vault", "api-key")
vault.get("infra", "Client ID")

data

Namespace for querying versioned data artifacts. See Data Functions below.

file

Namespace for reading file contents. See File Functions below.

workflow

Reserved for workflow context data. Not yet populated in the current version of swamp.


DataRecord Fields

Data functions and query predicates operate on records with these fields:

Field Type Description
id string Record UUID
name string Data item name
version int Version number
createdAt string ISO 8601 timestamp
attributes map<string, dyn> Parsed JSON content (resource data)
tags map<string, string> Metadata tags
modelName string Owning model name
modelType string Model type (e.g., command/shell)
specName string Output spec name
dataType string resource or file
contentType string MIME type
lifetime string Data lifetime
ownerType string Owner type
streaming bool Whether data was streamed
size int Content size in bytes
content string Raw text content

Data Functions

All data functions are methods on the data namespace. They return DataRecord objects or arrays of them.

data.latest(modelName, dataName)

Returns the latest version of a named data item, or null.

data.latest("scanner", "result")
data.latest("scanner", "result").attributes.findings

data.latest(modelName, dataName, varyValues)

Returns the latest version for a specific vary dimension.

data.latest("scanner", "result", ["dev"])
data.latest("scanner", "result", ["us-east-1", "prod"])

data.version(modelName, dataName, version)

Returns a specific version of a data item, or null.

data.version("scanner", "result", 1)
data.version("scanner", "result", 3).attributes.status

data.version(modelName, dataName, varyValues, version)

Returns a specific version for a vary dimension.

data.version("scanner", "result", ["dev"], 2)

data.listVersions(modelName, dataName)

Returns all version numbers for a data item, in ascending order.

data.listVersions("scanner", "result")

With vary values:

data.listVersions("scanner", "result", ["prod"])

data.findByTag(tagKey, tagValue)

Returns all data records (latest version) matching a tag.

data.findByTag("env", "production")

data.findBySpec(modelName, specName)

Returns all data records for a model matching a given output spec name. Inside a workflow run, results are scoped to data produced during that run.

data.findBySpec("scanner", "result")

data.query(predicate)

Runs a CEL predicate against all data artifacts. Returns matching records.

data.query("modelName == \"scanner\" && size > 1000")

data.query(predicate, select)

Runs a predicate with a projection expression that transforms each matching row into the specified shape.

data.query("modelName == \"scanner\"", "attributes.status")
data.query("size > 1000", "{\"name\": name, \"size\": size}")

File Functions

file.contents(modelName, specName)

Returns the text content of a file data item, or null.

file.contents("config-gen", "output")

Data Query Predicates

The data.query() function and swamp data query command evaluate CEL predicates against data artifacts. Each row exposes the DataRecord fields as top-level variables.

Available Query Fields

id, name, version, createdAt, attributes, tags, modelName, modelType, specName, dataType, contentType, lifetime, ownerType, streaming, size, content

When using data.query(), the attributes and content fields are lazy-loaded only when referenced in the predicate or projection. Other data functions (data.latest(), data.version(), etc.) load these fields eagerly.

Projections

A projection transforms each matching row into the shape specified by the expression. The second argument to data.query() (or --select flag on the CLI) is evaluated against each matching row, and the result replaces the full DataRecord in the output.

data.query("modelName == \"hello-world\"", "attributes.stdout")
data.query("size > 1000", "{\"name\": name, \"size\": size}")

Types

Primitives

Type CEL Literal Description
int 42, 0xFF 64-bit signed integer
uint 42u 64-bit unsigned integer
double 3.14, 3e2 64-bit floating-point
string "hello", 'hello' Unicode text
bool true, false Boolean
bytes b"data" Binary data
null null Null value

Aggregates

Type Literal Description
list<T> [1, 2, 3] Ordered collection
map<K, V> {"key": value} Key-value mapping

Strings

Triple-quoted strings span multiple lines. Raw strings (r"...") disable escape processing.

"""multi
line"""
r"no\escape"

Operators

Arithmetic

Operator Types Result
+ int, double, string, list, bytes Addition, concatenation
- int, double Subtraction
* int, double Multiplication
/ int, double Division
% int, uint Remainder
- int, double (unary) Negation

Arithmetic operators work across int and double without explicit conversion. This covers +, -, *, /, and %.

Comparison

Operator Description
== Equal
!= Not equal
< Less than
<= Less than or equal
> Greater than
>= Greater than or equal

Logical

Operator Description
&& Logical AND (short-circuit)
|| Logical OR (short-circuit)
! Logical NOT

Membership

Expression Description
value in list true if list contains value
key in map true if map has key

For string containment, use the .contains() method:

"hello world".contains("world")   // true

Access

Syntax Description
obj.field Field access
obj[key] Index / bracket access
cond ? a : b Conditional (ternary)

Precedence (Highest to Lowest)

  1. (), ., [] -- grouping, field access, indexing
  2. - (unary), ! -- negation
  3. *, /, % -- multiplicative
  4. +, - -- additive
  5. ==, !=, <, >, <=, >=, in -- comparison
  6. && -- logical AND
  7. || -- logical OR
  8. ? : -- conditional

Built-in Functions

Type Conversion

Function Description
int(x) Convert to int from double, string
uint(x) Convert to uint
double(x) Convert to double from int, string
string(x) Convert to string from int, double, bool, bytes
bool(x) Parse boolean from string
bytes(x) UTF-8 encode string
type(x) Returns type of value
dyn(x) Wrap as dynamic type

Size

size("hello")        // 5
size([1, 2, 3])      // 3
size({"a": 1})       // 1
"hello".size()       // 5 (method form)

String Methods

Method Returns Description
s.contains(sub) bool Substring check
s.startsWith(prefix) bool Prefix check
s.endsWith(suffix) bool Suffix check
s.matches(regex) bool Regex match
s.lowerAscii() string Lowercase
s.upperAscii() string Uppercase
s.trim() string Trim whitespace
s.indexOf(sub) int First occurrence (-1 if absent)
s.indexOf(sub, offset) int From offset
s.lastIndexOf(sub) int Last occurrence
s.lastIndexOf(sub, offset) int Backward from offset
s.substring(start) string From start to end
s.substring(start, end) string From start to end index
s.split(sep) list Split by separator
s.split(sep, limit) list Split with max parts

List/String Join

["a", "b", "c"].join()       // "abc"
["a", "b", "c"].join(", ")   // "a, b, c"

Timestamp and Duration

Constructors

Function Returns Description
timestamp(string) Timestamp Parse ISO 8601 string
duration(string) Duration Parse Go-style duration (e.g., "1h30m", "300ms")

Valid duration units: ns, us, ms, s, m, h.

Timestamp Methods

All accept an optional timezone string (e.g., "America/New_York"). Default is UTC.

Method Returns Description
t.getFullYear() int Four-digit year
t.getMonth() int Month (0-based, 0 = January)
t.getDate() int Day of month (1-based)
t.getDayOfWeek() int Day of week (0 = Sunday)
t.getDayOfYear() int Day of year (0-based)
t.getHours() int Hour (0-23)
t.getMinutes() int Minute (0-59)
t.getSeconds() int Second (0-59)
t.getMilliseconds() int Millisecond (0-999)
timestamp("2024-01-15T10:30:00Z").getHours("America/New_York")  // 5

Duration Methods

Method Returns Description
d.getHours() int Total hours
d.getMinutes() int Total minutes
d.getSeconds() int Total seconds
d.getMilliseconds() int Total milliseconds

Arithmetic

timestamp("2024-01-15T10:00:00Z") + duration("1h")    // Timestamp
timestamp("2024-01-16T00:00:00Z") - timestamp("2024-01-15T00:00:00Z")  // Duration
duration("1h") + duration("30m")   // Duration

Macros

has()

Tests whether a field exists without throwing on missing properties.

has(model.scanner.resource)
has(attributes.optional_field)

all()

True if all elements satisfy the predicate. True for empty lists.

[1, 2, 3].all(x, x > 0)           // true

exists()

True if any element satisfies the predicate.

[1, 2, 3].exists(x, x > 2)        // true

exists_one()

True if exactly one element satisfies the predicate.

[1, 2, 3].exists_one(x, x > 2)    // true

filter()

Returns elements that satisfy the predicate.

[1, 2, 3, 4].filter(x, x > 2)     // [3, 4]

map()

Transforms each element. With three arguments, filters then transforms.

[1, 2, 3].map(x, x * 2)           // [2, 4, 6]
[1, 2, 3, 4].map(x, x > 2, x * 10)  // [30, 40]