Skip to content

Variable Parsing

Variable parsing in Clarive allows you to dynamically insert values and perform operations on data within rules. This powerful feature enables you to create flexible and reusable rule configurations by referencing variables, performing transformations, and accessing nested data structures.

Basic Variable Syntax

Variables in Clarive rules use the ${variable_name} syntax. When a rule is processed, these placeholders are replaced with their corresponding values.

Simple Variable Substitution

The most basic form of variable parsing is direct substitution:

# Rule configuration
name: "Deploy to ${environment}"
description: "Deploying application ${app_name} to ${target_server}"

If the variables are defined as:

environment: "production"
app_name: "my-web-app"
target_server: "web-01.example.com"

The result would be:

name: "Deploy to production"
description: "Deploying application my-web-app to web-01.example.com"

Multiple Variables in One String

You can use multiple variables in a single string:

# Multiple variables
message: "${foo} ${bar}"
# If foo = "hello" and bar = "world"
# Result: "hello world"

# Variables with surrounding text
path: "before${foo}after"
# If foo = "|"
# Result: "before|after"

Variable Types and Data Structures

Variables can contain various data types, and the parser handles them appropriately:

String Variables

# Simple string substitution
message: "Hello ${user_name}!"
# Result: "Hello John!"

Array Variables

# Array substitution
files: ${file_list}
# If file_list = ["config.yml", "app.js", "style.css"]
# Result: ["config.yml", "app.js", "style.css"]

Hash/Dictionary Variables

# Hash substitution
config: ${app_config}
# If app_config = {port: 8080, host: "localhost"}
# Result: {port: 8080, host: "localhost"}

Reference Variables

# Scalar references
value: ${scalar_ref}
# If scalar_ref = \'bar'
# Result: "bar"

# MongoDB ObjectID references
oid_value: ${mongodb_oid}
# If mongodb_oid = MongoDB::OID->new(value => '012345678901234567890123')
# Result: "012345678901234567890123"

Nested Variable Access

You can access nested properties using dot notation:

# Access nested properties
server_host: ${server.host}
server_port: ${server.port}
database_url: ${database.connection.url}

If the variables contain:

server:
  host: "app.example.com"
  port: 443
database:
  connection:
    url: "postgresql://localhost:5432/mydb"

The result would be:

server_host: "app.example.com"
server_port: 443
database_url: "postgresql://localhost:5432/mydb"

Field Access Behavior

When accessing fields of non-hash values, the variable remains unparsed:

# If foo is an empty string
field_access: ${foo.bar}
# Result: "${foo.bar}" (remains unparsed)

Array Index Access

Access array elements using bracket notation:

# Access array elements
first_item: ${items[0]}
second_item: ${items[1]}
nested_access: ${items[0].bar}

Array Access Examples

# If items = [{bar: "baz"}]
nested_value: ${items[0].bar}
# Result: "baz"

# With recursive parsing
nested_recursive: ${items[0].bar}
# If items = [{bar: "${deep}"}] and deep = "hello"
# Result: "hello"

Built-in Functions

Clarive provides several built-in functions for variable transformation:

String Case Functions

Upper Case (uc())

# Convert to uppercase
environment: ${uc(foo)}
# If foo = "bar"
# Result: "BAR"

Lower Case (lc())

# Convert to lowercase
hostname: ${lc(foo)}
# If foo = "BAR"
# Result: "bar"

ID Conversion (to_id())

# Convert name to ID format
project_id: ${to_id(this is 123 and #more... !stuff)}
# Result: "this_is_123_and_more_stuff"

Padding Function (pad())

# Pad with zeros
version: ${pad(0, 15, x)}
# If x = 1234
# Result: "000000000001234"

# Pad with custom character
label: ${pad("x", 15, x)}
# If x = "asdf"
# Result: "xxxxxxxxxxxasdf"

# Extra spaces in function are ignored
padded: ${pad(0,  15,   x)}
# If x = "asdf"
# Result: "00000000000asdf"

Quote List Function (quote_list())

# Quote single value
quoted: ${quote_list(foo)}
# If foo = "bar"
# Result: '"bar"'

JSON and YAML Functions

# Convert to JSON
json_config: ${json(foo)}
# If foo = {aa: 100}
# Result: '{"aa":100}'

# Convert to YAML
yaml_config: ${yaml(foo)}
# If foo = {aa: 200}
# Result: '{"aa":200}'

CI Object Access

You can access CI (Configuration Item) properties directly:

# Access CI properties
ci_name: ${ci(server_id).name}
ci_status: ${ci(server_id).status}
ci_ip: ${ci(server_id).ip_address}

Or get the entire CI object:

# Get full CI object
server_ci: ${ci(server_id)}

Required Variables

Use the + prefix to mark variables as required. If a required variable is not found, the rule will fail:

# Required variable - will fail if not defined
critical_path: ${+deployment_path}
api_key: ${+secret_key}

# Required nested access
required_field: ${+foo.bar}

Non-Recursive Variables

Use ${{variable}} syntax to prevent recursive parsing:

# Non-recursive - won't parse variables inside the result
template: ${{email_template}}
# If email_template = "Hello ${user_name}!"
# Result: "Hello ${user_name}!" (not parsed further)

# Mixing recursive and non-recursive
mixed: "${foo} ${{bar}}"
# If foo = "${baz}", bar = "${baz}", baz = "123"
# Result: "123 ${baz}"

Whitespace Handling

The parser ignores whitespace around variable names and function parameters:

# Whitespace is ignored
spaced_var: ${ foo }
# If foo = "bar"
# Result: "bar"

# Tab characters are also ignored
tabbed_var: ${    foo }
# If foo = "bar"
# Result: "bar"

# Function parameters with extra spaces
spaced_func: ${ to_id(  foo bar  ) }
# Result: "foo_bar"

Escaping Variables

To include literal variable syntax in output, escape with double dollar signs:

# Escape dollar signs
escaped: "$${foo}"
# If foo = 123
# Result: "${foo}"

# Escape in multiline strings
multiline_escape: |
  hello
  $${foo}
  world
# If foo = 123
# Result: "hello\n${foo}\nworld"

Error Handling

Missing Variables

When variables are not found, behavior depends on configuration:

# Default behavior - variables remain unparsed
missing_var: ${foo}
# If foo is not defined
# Result: "${foo}"

# With cleanup enabled - variables are removed
# cleanup = true
cleaned_var: ${foo}
# If foo is not defined
# Result: ""

# Multiple unresolved variables
multiple_missing: "${foo} ${bar}"
# If both are undefined
# Result: " " (space between them)

Required Variables

Required variables cause errors when missing:

# This will throw an error if foo.bar is not defined
required_error: ${+foo.bar}

Recursion Detection

The parser detects infinite recursion and fails gracefully:

# Direct recursion - will fail
circular: ${foo}
# If foo = "${foo}"

# Indirect recursion - will fail
indirect: ${foo}
# If foo = "${bar}" and bar = "${foo}"

Reference Errors

When using references in string context:

# Single variable with reference - works
single_ref: ${foo}
# If foo = {foo: "bar"}
# Result: {foo: "bar"}

# Reference in string context - fails
string_ref: "Hello, ${foo}!"
# If foo = {foo: "bar"}
# Error: "Unexpected reference found in ${foo}"

Advanced Examples

Complex Rule Configuration

# Rule with multiple variable types
name: "Deploy ${app_name} to ${environment}"
description: "Deploying version ${version} to ${ci(server_id).name}"
parameters:
  host: ${ci(server_id).ip_address}
  port: ${nvl(custom_port, 8080)}
  environment: ${uc(environment)}
  config_file: "${app_name}-${environment}.yml"
  backup_path: "/backups/${to_id(app_name)}/${date}"

Data Structure Parsing

# Parse complex nested structures
config:
  foo: "${foo.bar}"
  bar: ["${server}"]
  deep:
    nested: ["${path}"]

# If variables are:
# foo = {bar: "value"}
# server = "web-01"
# path = "/var/log"

# Result:
config:
  foo: "value"
  bar: ["web-01"]
  deep:
    nested: ["/var/log"]

Reference Consistency

# Multiple references to the same object
server_config:
  var1: "${server}"
  var2: "${server}"

# If server = {name: "foo", hostname: "foo"}
# Both var1 and var2 will reference the same object

Performance Considerations

  • Variable parsing is performed during rule execution
  • Complex nested structures may impact performance
  • Use non-recursive parsing (${{}}) when possible to avoid unnecessary processing
  • The parser tracks variable usage for optimization

Debugging Variable Parsing

To debug variable parsing issues:

  1. Check that all required variables are defined
  2. Verify variable names match exactly (case-sensitive)
  3. Use the REPL to test variable expressions
  4. Check for recursion in variable definitions
  5. Ensure proper escaping of special characters
  6. Monitor the vars property to see which variables were accessed