Probe Expression Engine Syntax for Templating: A Complete Guide

Probe uses the Expr-Lang expression engine with double-curly-brace syntax {{ <expression> }} to evaluate dynamic values, supporting type preservation for single expressions and string concatenation for mixed content.

The Probe monitoring tool (linyows/probe) leverages a powerful expression engine to enable dynamic templating within configuration files, step definitions, and action payloads. Understanding the Probe expression engine syntax for templating is essential for writing flexible, data-driven health checks and automation workflows.

Core Templating Syntax in Probe

The Double-Curly-Brace Pattern

All template expressions in Probe follow a consistent delimiter pattern defined in expr.go:

{{ <expression> }}

This syntax wraps valid Expr-Lang expressions that the engine evaluates against a provided environment map. The implementation in expr.go registers safe functions and validates expressions before execution to prevent security vulnerabilities.

Type Preservation vs String Concatenation

Probe distinguishes between two evaluation modes based on template structure:

  • Single expression templates: When the entire string consists of one {{ … }} block, EvalTemplateWithTypePreservation returns the raw evaluated value (e.g., integers remain int, booleans remain bool).
  • Mixed content templates: When text contains one or more {{ … }} fragments, the engine evaluates each fragment and concatenates results into a string.

This behavior is extensively tested in expr_test.go, which defines the supported syntax and type-handling expectations.

Supported Operators and Functions

Standard Operators and Variables

Probe's expression engine supports Go-like operators and variable references from the evaluation environment. Expressions can access nested maps using dot notation:

env := map[string]any{
    "vars": map[string]any{"count": 42},
}
// Access via: {{ vars.count }}

Null-Coalescing and Ternary Operators

The engine implements advanced control flow operators demonstrated in expr_test.go:

  • Null-coalescing (??): Returns the right-hand operand if the left is null or empty
  • Ternary (cond ? a : b): Evaluates conditions and returns the appropriate branch
tmpl := "{{ env.URL ?? 'http://localhost' }} is {{ status == 200 ? 'OK' : 'FAIL' }}"

Built-in Custom Functions

expr.go registers several custom functions for dynamic data generation:

  • random_int(max) – Returns random integer (0 ≤ n < max)
  • random_str(length) – Generates random alphanumeric string
  • unixtime() – Returns current Unix timestamp
  • encode_base64(string) / decode_base64(string) – Base64 encoding operations

Implementation in expr.go

The core templating logic resides in expr.go, which provides the primary API methods:

  • Eval(expression, env) – Evaluates raw expressions without templating
  • EvalTemplate(template, env) – Processes mixed-content templates with string output
  • EvalTemplateWithTypePreservation(template, env) – Preserves types for single-expression templates
  • EvalTemplateMap(input, env) – Recursively evaluates templates within map structures

Security validation occurs within these methods to prevent code injection, as verified by the comprehensive test suite in expr_test.go.

Practical Code Examples

Simple Variable Substitution

env := map[string]any{
    "name": "Alice",
}
result, _ := (&probe.Expr{}).EvalTemplate("Hello {{ name }}!", env)
// → "Hello Alice!"

Type-Preserved Evaluation

env := map[string]any{
    "vars": map[string]any{"count": 42},
}
val, _ := (&probe.Expr{}).EvalTemplateWithTypePreservation("{{ vars.count }}", env)
// → 42 (int, not "42")

Using Advanced Operators

env := map[string]any{
    "status": 200,
    "env":    map[string]any{"URL": ""},
}
tmpl := "{{ env.URL ?? 'http://localhost' }} is {{ status == 200 ? 'OK' : 'FAIL' }}"
out, _ := (&probe.Expr{}).EvalTemplate(tmpl, env)
// → "http://localhost is OK"

Built-in Function Usage

env := map[string]any{}
expr := &probe.Expr{}

// Random integer (0 ≤ n < 100)
randInt, _ := expr.Eval("random_int(100)", env)

// Random alphanumeric string of length 16
randStr, _ := expr.Eval("random_str(16)", env)

// Current Unix timestamp
ts, _ := expr.Eval("unixtime()", env)

// Base64 encode/decode
enc, _ := expr.Eval("encode_base64('user:pass')", env) // → "dXNlcjpwYXNz"
dec, _ := expr.Eval("decode_base64(enc)", map[string]any{"enc": enc}) // → "user:pass"

Evaluating Nested Map Templates

input := map[string]any{
    "url":       "{{ env.URL }}",
    "auth":      "Bearer {{ env.TOKEN }}",
    "nested": map[string]any{
        "msg": "Hello {{ name }}",
    },
}
env := map[string]any{
    "env":  map[string]any{"URL": "https://api.example.com", "TOKEN": "s3cr3t"},
    "name": "Bob",
}
out := (&probe.Expr{}).EvalTemplateMap(input, env)
// out["url"]        → "https://api.example.com"
// out["auth"]       → "Bearer s3cr3t"
// out["nested"].(map[string]any)["msg"] → "Hello Bob"

Summary

  • Probe uses double-curly-brace syntax ({{ expression }}) for all templating operations via the Expr-Lang engine.
  • Type preservation occurs when templates contain only a single expression block, while mixed content produces string concatenation.
  • The engine supports Go-like operators, null-coalescing (??), ternary conditionals (? :), and custom functions like random_int and encode_base64.
  • Core implementation resides in expr.go with comprehensive validation and security checks defined in expr_test.go.

Frequently Asked Questions

What is the basic syntax for Probe's expression templates?

Probe templates use double-curly-brace delimiters: {{ <expression> }}. Any valid Expr-Lang expression can be placed inside the braces, including variable references, arithmetic operations, and function calls. This syntax is processed by the EvalTemplate method in expr.go.

How does Probe handle type conversion in templates?

Probe distinguishes between single-expression and mixed-content templates. When a template consists solely of one {{ … }} block, EvalTemplateWithTypePreservation returns the raw evaluated type (integer, boolean, etc.). When text contains multiple expressions or literal strings, the engine concatenates everything into a final string result.

What built-in functions are available in Probe's expression engine?

Probe registers several custom functions in expr.go for dynamic data generation: random_int(max) for random integers, random_str(length) for alphanumeric strings, unixtime() for timestamps, and encode_base64/decode_base64 for Base64 operations. These functions are validated and sandboxed to prevent security vulnerabilities.

Can Probe templates reference nested variables and use advanced operators?

Yes, Probe supports dot-notation for nested map access (e.g., {{ vars.count }}) and implements advanced operators including null-coalescing (??) for default values and ternary conditionals (cond ? a : b) for inline branching. These features are extensively tested in expr_test.go to ensure consistent evaluation behavior.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →