ConditionCommand

ConditionCommand evaluates Tera expressions to select between multiple branches. It provides if/then branching logic for pipelines, producing a result based on the first matching condition.

When to Use

Use ConditionCommand when you need to:

  • Choose between different values based on runtime conditions
  • Implement feature flags or configuration-based branching
  • Generate different outputs based on data characteristics
  • Create conditional messages or labels

Attributes

AttributeTypeRequiredDescription
branchesArray of objectsYesArray of condition branches evaluated in order
defaultStringNoDefault value if no branch matches (supports Tera substitution)

Branch Object Fields

Each object in the branches array defines one condition:

FieldTypeRequiredDescription
nameStringYesUnique identifier for this branch
ifStringYesTera expression to evaluate as the condition
thenStringYesValue if condition is true (supports Tera substitution)

Results

Data Results (Fixed)

ResultTypeDescription
resultStringThe value from the matched branch or default
matchedBooleanWhether a branch condition matched (false if default was used)
branch_indexNumberIndex of the matched branch (0-based), or -1 if default was used

Data Results (Per Branch)

For each branch in the branches array, an object is stored:

ResultTypeDescription
{name}ObjectContains matched (bool) and value (string) for this branch

Condition Evaluation

The if expression is wrapped in {{ }} and evaluated as a Tera expression. The result is considered truthy if it is:

  • A non-empty string (except "false", "0", "null", "undefined")
  • A non-zero number
  • Boolean true

Branches are evaluated in order. The first truthy branch wins, and subsequent branches are not evaluated.

Examples

Basic Conditional

#![allow(unused)]
fn main() {
use panopticon_core::prelude::*;

// Set up inputs
pipeline
    .add_namespace(
        NamespaceBuilder::new("inputs")
            .static_ns()
            .insert("user_role", ScalarValue::String("admin".to_string())),
    )
    .await?;

let attrs = ObjectBuilder::new()
    .insert(
        "branches",
        ScalarValue::Array(vec![
            ObjectBuilder::new()
                .insert("name", "admin_greeting")
                .insert("if", "inputs.user_role == 'admin'")
                .insert("then", "Welcome, Administrator!")
                .build_scalar(),
            ObjectBuilder::new()
                .insert("name", "user_greeting")
                .insert("if", "inputs.user_role == 'user'")
                .insert("then", "Welcome, User!")
                .build_scalar(),
        ]),
    )
    .insert("default", "Welcome, Guest!")
    .build_hashmap();

pipeline
    .add_namespace(NamespaceBuilder::new("greeting"))
    .await?
    .add_command::<ConditionCommand>("message", &attrs)
    .await?;

// Result: "Welcome, Administrator!"
}

Feature Flag Pattern

#![allow(unused)]
fn main() {
// Static namespace with feature flags
pipeline
    .add_namespace(
        NamespaceBuilder::new("features")
            .static_ns()
            .insert("new_dashboard", ScalarValue::Bool(true))
            .insert("beta_api", ScalarValue::Bool(false)),
    )
    .await?;

let attrs = ObjectBuilder::new()
    .insert(
        "branches",
        ScalarValue::Array(vec![
            ObjectBuilder::new()
                .insert("name", "new_dash")
                .insert("if", "features.new_dashboard")
                .insert("then", "/v2/dashboard")
                .build_scalar(),
        ]),
    )
    .insert("default", "/v1/dashboard")
    .build_hashmap();

pipeline
    .add_namespace(NamespaceBuilder::new("routing"))
    .await?
    .add_command::<ConditionCommand>("dashboard_path", &attrs)
    .await?;
}

Data-Driven Conditions

Use values from earlier pipeline stages (like aggregations):

#![allow(unused)]
fn main() {
// Aggregate order data
let agg_attrs = ObjectBuilder::new()
    .insert("source", "data.load.orders.data")
    .insert("aggregations", ScalarValue::Array(vec![
        ObjectBuilder::new()
            .insert("name", "order_count")
            .insert("op", "count")
            .build_scalar(),
        ObjectBuilder::new()
            .insert("name", "total_revenue")
            .insert("column", "total")
            .insert("op", "sum")
            .build_scalar(),
    ]))
    .build_hashmap();

pipeline
    .add_namespace(NamespaceBuilder::new("metrics"))
    .await?
    .add_command::<AggregateCommand>("orders", &agg_attrs)
    .await?;

// Branch based on metrics
let condition_attrs = ObjectBuilder::new()
    .insert(
        "branches",
        ScalarValue::Array(vec![
            ObjectBuilder::new()
                .insert("name", "high_volume")
                .insert("if", "metrics.orders.order_count > 1000")
                .insert("then", "HIGH_VOLUME")
                .build_scalar(),
            ObjectBuilder::new()
                .insert("name", "high_revenue")
                .insert("if", "metrics.orders.total_revenue > 50000")
                .insert("then", "HIGH_REVENUE")
                .build_scalar(),
            ObjectBuilder::new()
                .insert("name", "normal")
                .insert("if", "true")  // Always matches as fallback
                .insert("then", "NORMAL")
                .build_scalar(),
        ]),
    )
    .build_hashmap();

pipeline
    .add_namespace(NamespaceBuilder::new("classification"))
    .await?
    .add_command::<ConditionCommand>("tier", &condition_attrs)
    .await?;
}

Using Tera Filters and Functions

The if expression supports full Tera syntax:

#![allow(unused)]
fn main() {
let attrs = ObjectBuilder::new()
    .insert(
        "branches",
        ScalarValue::Array(vec![
            ObjectBuilder::new()
                .insert("name", "long_name")
                .insert("if", "inputs.name | length > 10")
                .insert("then", "Name is long")
                .build_scalar(),
            ObjectBuilder::new()
                .insert("name", "uppercase")
                .insert("if", "inputs.name == inputs.name | upper")
                .insert("then", "Name is all uppercase")
                .build_scalar(),
        ]),
    )
    .insert("default", "Name is normal")
    .build_hashmap();
}

Accessing Results

#![allow(unused)]
fn main() {
let completed = pipeline.compile().await?.execute().await?;
let results = completed.results(ResultSettings::default()).await?;

let source = StorePath::from_segments(["greeting", "message"]);
let cmd_results = results.get_by_source(&source).expect("Expected results");

// Main result
let result = cmd_results
    .data_get(&source.with_segment("result"))
    .and_then(|r| r.as_scalar())
    .expect("Expected result");
println!("Result: {}", result.1);

// Did any branch match?
let matched = cmd_results
    .data_get(&source.with_segment("matched"))
    .and_then(|r| r.as_scalar())
    .expect("Expected matched");
println!("Matched: {}", matched.1);

// Which branch matched?
let index = cmd_results
    .data_get(&source.with_segment("branch_index"))
    .and_then(|r| r.as_scalar())
    .expect("Expected branch_index");
println!("Branch index: {}", index.1);  // 0, 1, 2... or -1 for default
}

Common Patterns

Conditional with when Attribute

Combine ConditionCommand with the when attribute to skip the entire command:

#![allow(unused)]
fn main() {
let attrs = ObjectBuilder::new()
    .insert("when", "inputs.feature_enabled")  // Skip if false
    .insert(
        "branches",
        ScalarValue::Array(vec![
            ObjectBuilder::new()
                .insert("name", "greeting")
                .insert("if", "true")
                .insert("then", "Hello, {{ inputs.user_name }}! Feature is active.")
                .build_scalar(),
        ]),
    )
    .insert("default", "Fallback message")
    .build_hashmap();
}

When when is false:

  • The command status is "skipped"
  • No data results are produced
  • result, matched, and branch_index are absent

Multiple Independent Conditions

Evaluate multiple conditions that don't depend on each other:

#![allow(unused)]
fn main() {
let attrs = ObjectBuilder::new()
    .insert(
        "branches",
        ScalarValue::Array(vec![
            ObjectBuilder::new()
                .insert("name", "is_admin")
                .insert("if", "inputs.role == 'admin'")
                .insert("then", "true")
                .build_scalar(),
            ObjectBuilder::new()
                .insert("name", "is_premium")
                .insert("if", "inputs.subscription == 'premium'")
                .insert("then", "true")
                .build_scalar(),
            ObjectBuilder::new()
                .insert("name", "has_access")
                .insert("if", "inputs.access_granted")
                .insert("then", "true")
                .build_scalar(),
        ]),
    )
    .build_hashmap();

// Access individual branch results:
// condition.check.is_admin.matched
// condition.check.is_premium.matched
// condition.check.has_access.matched
}

Cascading If/Else

Use true as the final condition for an else clause:

#![allow(unused)]
fn main() {
let attrs = ObjectBuilder::new()
    .insert(
        "branches",
        ScalarValue::Array(vec![
            ObjectBuilder::new()
                .insert("name", "premium")
                .insert("if", "metrics.score > 90")
                .insert("then", "PREMIUM")
                .build_scalar(),
            ObjectBuilder::new()
                .insert("name", "standard")
                .insert("if", "metrics.score > 50")
                .insert("then", "STANDARD")
                .build_scalar(),
            ObjectBuilder::new()
                .insert("name", "basic")
                .insert("if", "true")  // Else clause
                .insert("then", "BASIC")
                .build_scalar(),
        ]),
    )
    .build_hashmap();
}

Using Results in Templates

Condition results are stored in the scalar store and can be used in templates:

#![allow(unused)]
fn main() {
// Condition command
let condition_attrs = ObjectBuilder::new()
    .insert("branches", ScalarValue::Array(vec![
        ObjectBuilder::new()
            .insert("name", "status")
            .insert("if", "metrics.health > 80")
            .insert("then", "healthy")
            .build_scalar(),
    ]))
    .insert("default", "degraded")
    .build_hashmap();

pipeline
    .add_namespace(NamespaceBuilder::new("check"))
    .await?
    .add_command::<ConditionCommand>("health", &condition_attrs)
    .await?;

// Template using the result
let template_attrs = ObjectBuilder::new()
    .insert("templates", ScalarValue::Array(vec![
        ObjectBuilder::new()
            .insert("name", "report")
            .insert("content", "System status: {{ check.health.result }}")
            .build_scalar(),
    ]))
    .insert("render", "report")
    .insert("output", "/tmp/status.txt")
    .build_hashmap();
}

Error Handling

ConditionCommand will return an error if:

  • A branch is missing required fields (name, if, or then)
  • A Tera expression in if or then cannot be evaluated
  • Referenced variables in expressions do not exist in the scalar store