When Blocks
Multi-condition when blocks enable complex state transitions with rich boolean expressions for advanced routing logic.
Overview
When blocks replace simple status-based transitions with complex decision logic supporting:
HTTP status code matching (exact, range, multiple)
Response body content matching
Header checks and comparisons
Extracted variable conditions using Jinja2
Response time analysis
AND/OR logic for complex routing
Basic Syntax
states:
check_auth:
request: |
GET /api/user/profile HTTP/1.1
extract:
role:
type: jpath
pattern: "$.user.role"
next:
- when:
- status: 200
- condition: "{{ role == 'admin' }}"
goto: admin_panel
- otherwise:
goto: normal_flow
Condition Types
Status Code Matching
when:
- status: 200 # Exact match
- status_in: [200, 201, 202] # Multiple statuses
- status_range: [200, 299] # Range (inclusive)
Examples:
# Success codes
- when:
- status_range: [200, 299]
goto: success
# Client errors
- when:
- status_range: [400, 499]
goto: client_error
# Specific codes
- when:
- status_in: [401, 403]
goto: unauthorized
Jinja2 Expressions
Evaluate complex boolean expressions using extracted variables:
when:
- condition: "{{ role == 'admin' }}"
- condition: "{{ balance > 1000 }}"
- condition: "{{ age >= 18 and age <= 65 }}"
- condition: "{{ 'premium' in user.features }}"
Supported operators:
Comparison:
==,!=,>,<,>=,<=Membership:
in,not inBoolean:
and,or,notString operations via Jinja2 filters
Examples:
# Numeric comparisons
- condition: "{{ balance | int > 1000 }}"
# String operations
- condition: "{{ username | lower == 'admin' }}"
# List operations
- condition: "{{ user_id in [1, 2, 3, 4, 5] }}"
# Complex logic
- condition: "{{ (balance > 100 and role == 'premium') or is_vip }}"
Body Content Matching
when:
- body_contains: "success" # Substring match
- body_not_contains: "error" # Negative match
- body_matches: "^HTTP/1\\.1 200" # Regex match
- body_equals: '{"status": "ok"}' # Exact match
Examples:
# Check for success message
- when:
- status: 200
- body_contains: "operation completed"
goto: success
# Detect errors in response
- when:
- body_contains: "error"
- body_not_contains: "no errors"
goto: error_handler
# Regex pattern matching
- when:
- body_matches: "user_id\":\\s*(\\d+)"
goto: user_found
Header Checks
when:
# Simple existence check
- header_exists: "X-Auth-Token"
- header_not_exists: "X-Debug"
# Value matching
- header_equals:
name: "Content-Type"
value: "application/json"
# Substring matching
- header_contains:
name: "Content-Type"
value: "json"
# Numeric comparison
- header_gt:
name: "Content-Length"
value: 1000
Available header conditions:
header_exists- Check if header is presentheader_not_exists- Check if header is absentheader_equals- Exact value matchheader_contains- Substring matchheader_gt- Greater than (numeric)header_lt- Less than (numeric)header_gte- Greater than or equal (numeric)header_lte- Less than or equal (numeric)
Examples:
# Rate limiting detection
- when:
- header_exists: "X-RateLimit-Remaining"
- header_lt:
name: "X-RateLimit-Remaining"
value: 10
goto: slow_down
# Content type validation
- when:
- header_equals:
name: "Content-Type"
value: "application/json"
goto: parse_json
# Check response size
- when:
- header_gt:
name: "Content-Length"
value: 10000
goto: large_response
Response Time Analysis
when:
- response_time_lt: 100 # Less than 100ms
- response_time_gt: 1000 # Greater than 1000ms
- response_time_between:
min: 100
max: 500
Examples:
# Fast response detection
- when:
- response_time_lt: 50
goto: cache_hit
# Slow response detection
- when:
- response_time_gt: 2000
goto: timeout_handling
# Acceptable range
- when:
- response_time_between:
min: 100
max: 500
goto: normal_flow
Combining Conditions
Multiple conditions in a when block are implicitly AND’ed:
# All conditions must be true
- when:
- status: 200
- body_contains: "success"
- header_exists: "X-Auth-Token"
- condition: "{{ balance > 100 }}"
goto: all_conditions_met
For OR logic, use separate when blocks:
# If status is 200 OR 201
- when:
- status: 200
goto: success
- when:
- status: 201
goto: success
Or use Jinja2 conditions:
- when:
- condition: "{{ status == 200 or status == 201 }}"
goto: success
Complex Examples
Role-Based Routing
states:
check_user:
request: |
GET /api/user/profile HTTP/1.1
Authorization: Bearer {{ token }}
extract:
role:
type: jpath
pattern: "$.role"
permissions:
type: jpath
pattern: "$.permissions"
next:
# Admin with full access
- when:
- status: 200
- condition: "{{ role == 'admin' }}"
- condition: "{{ 'full_access' in permissions }}"
goto: admin_panel
# Moderator
- when:
- status: 200
- condition: "{{ role == 'moderator' }}"
goto: moderator_panel
# Regular user
- when:
- status: 200
goto: user_dashboard
# Unauthorized
- when:
- status_in: [401, 403]
goto: login
Error Detection
states:
api_call:
request: |
POST /api/process HTTP/1.1
next:
# Success
- when:
- status_range: [200, 299]
- body_contains: "success"
- body_not_contains: "error"
goto: success
# Rate limited
- when:
- status: 429
- header_exists: "Retry-After"
goto: rate_limited
# Server error
- when:
- status_range: [500, 599]
goto: server_error
# Validation error
- when:
- status: 400
- body_contains: "validation"
goto: validation_error
# Unknown error
- otherwise:
goto: unknown_error
Race Condition Detection
states:
race_attack:
race:
threads: 20
request: |
POST /api/redeem HTTP/1.1
extract:
remaining:
type: jpath
pattern: "$.vouchers_remaining"
next:
# Multiple successes = vulnerability
- when:
- status: 200
- body_contains: "redeemed"
- condition: "{{ successful_requests | int > 1 }}"
goto: vulnerability_found
# Single success = expected
- when:
- status: 200
- condition: "{{ successful_requests | int == 1 }}"
goto: no_vulnerability
# All failed = race condition prevented
- when:
- condition: "{{ successful_requests | int == 0 }}"
goto: protected
Performance-Based Routing
states:
load_test:
request: |
GET /api/heavy-operation HTTP/1.1
next:
# Fast cache hit
- when:
- status: 200
- response_time_lt: 100
- header_exists: "X-Cache-Hit"
goto: cache_hit
# Slow but successful
- when:
- status: 200
- response_time_gt: 1000
goto: slow_response
# Timeout
- when:
- response_time_gt: 5000
goto: timeout
# Normal response
- when:
- status: 200
- response_time_between:
min: 100
max: 1000
goto: normal_response
Best Practices
Order Matters
When blocks are evaluated in order. Put more specific conditions first:
# Good - specific first - when: - status: 200 - condition: "{{ role == 'admin' }}" goto: admin - when: - status: 200 goto: user # Bad - general first (admin never reached) - when: - status: 200 goto: user - when: - status: 200 - condition: "{{ role == 'admin' }}" goto: admin
Always Provide Fallback
Use
otherwiseto handle unexpected cases:- when: - status: 200 goto: success - otherwise: goto: error_handler
Keep Conditions Simple
Break complex logic into multiple when blocks:
# Good - when: - status: 200 goto: check_role # In check_role state - when: - condition: "{{ role == 'admin' }}" goto: admin # Bad - too complex - when: - condition: "{{ (status == 200 and role == 'admin') or (status == 201 and role == 'moderator') }}" goto: privileged
Test Extracted Variables
Ensure variables exist before using in conditions:
extract: balance: type: jpath pattern: "$.balance" default: 0 # Provide default next: - when: - condition: "{{ balance | int > 100 }}" goto: high_balance
Use Descriptive State Names
Make flow easy to understand:
- when: - status: 429 goto: handle_rate_limit # Not goto: state_x
Common Patterns
Authentication Flow
next:
- when:
- status: 200
- body_contains: "token"
goto: authenticated
- when:
- status: 401
goto: login_failed
- when:
- status: 429
goto: rate_limited
- otherwise:
goto: error
API Error Handling
next:
- when:
- status_range: [200, 299]
goto: success
- when:
- status_range: [400, 499]
goto: client_error
- when:
- status_range: [500, 599]
goto: server_error
- otherwise:
goto: unknown_error
See Also
Configuration Reference - Complete YAML configuration reference
Template Engine - Jinja2 template syntax and filters
Data Extractors - Data extraction from responses
Attack Examples - Real-world examples