Conditions

Conditions #

The INFINI Framework provides a powerful conditions system for evaluating events against configurable rules. Conditions are used throughout the framework — most notably in pipeline if/then/else branching — to make runtime decisions based on field values, patterns, numeric ranges, network membership, and logical combinations.

The conditions package is located at core/conditions/.

Condition Interface #

Every condition implements the Condition interface:

type Condition interface {
    Check(event ValuesMap) bool
    String() string
}
MethodDescription
Check(event ValuesMap) boolEvaluates the condition against an event. Returns true if the event matches.
String() stringReturns a human-readable representation of the condition for logging and debugging.

Events are read through the ValuesMap interface, which provides dot-notation field access:

type ValuesMap interface {
    GetValue(string) (interface{}, error)
}

Fields are referenced using dot notation (e.g., _ctx.request.method, http.response.status_code). If a field does not exist, GetValue returns an error and the condition typically evaluates to false.

Config Struct #

Conditions are declared in YAML configuration and deserialized into the Config struct:

type Config struct {
    Equals           *Fields                `config:"equals"`
    Contains         *Fields                `config:"contains"`
    Prefix           map[string]interface{} `config:"prefix"`
    Suffix           map[string]interface{} `config:"suffix"`
    Regexp           *Fields                `config:"regexp"`
    Range            *Fields                `config:"range"`
    Network          map[string]interface{} `config:"network"`
    Exists           []string               `config:"exists"`
    IN               map[string]interface{} `config:"in"`
    LengthEquals     *Fields                `config:"length"`
    OR               []Config               `config:"or"`
    AND              []Config               `config:"and"`
    NOT              *Config                `config:"not"`
    QueueHasLag      []string               `config:"queue_has_lag"`
    ConsumerHasLag   *Fields                `config:"consumer_has_lag"`
    ClusterAvailable []string               `config:"cluster_available"`
}

A Config is converted into a live Condition using the factory functions:

func NewCondition(config *Config) (Condition, error)
func NewConditionList(config []Config) ([]Condition, error)

Each Config must contain exactly one top-level operator. To combine multiple operators, use the logical operators and, or, or not.

Value Operators #

equals #

Tests whether a field value is exactly equal to an expected value. Supports string, int, float, and bool types. When multiple fields are specified, all must match (implicit AND).

equals:
  _ctx.request.method: "GET"
# Multiple fields — all must match
equals:
  type: "process"
  proc.pid: 305

contains #

Tests whether a string field contains a given substring. Also works on arrays of strings — returns true if any element contains the substring.

contains:
  _ctx.request.uri: "/api"

regexp #

Tests whether a string field matches a regular expression pattern. Also supports matching against arrays of strings.

regexp:
  source: "apache2/error.*"
regexp:
  message: "[Ee]rror|[Ff]ailed"

prefix #

Tests whether a string field starts with a given prefix. Accepts exactly one field.

prefix:
  hostname: "prod-"

suffix #

Tests whether a string field ends with a given suffix. Accepts exactly one field.

suffix:
  filename: ".log"

in #

Tests whether a field value is contained in a list of allowed values. Supports both string and integer values.

in:
  _ctx.response.status_code: [200, 201, 204]
in:
  env: ["production", "staging"]

range #

Tests whether a numeric field falls within a specified range. Supports the following comparison operators:

OperatorMeaning
gteGreater than or equal to (>=)
gtGreater than (>)
lteLess than or equal to (<=)
ltLess than (<)

Range operators are appended to the field name with a dot separator:

# 200 <= status_code < 300
range:
  _ctx.response.status_code:
    gte: 200
    lt: 300
# CPU usage above 90%
range:
  proc.cpu.total_p:
    gt: 0.9

Supports int, uint, and float numeric types.

exists #

Tests whether one or more fields exist and are non-empty. Accepts a list of field names. All fields must exist for the condition to match.

exists:
  - username
  - email
  - session_id

length #

Tests whether the length of a field’s value equals an expected integer. Works with slices, arrays, strings, maps, and channels.

length:
  tags: 3

network #

Tests whether an IP address field belongs to a specific network. Accepts named network identifiers or CIDR notation.

Named networks:

NameDescription
loopbackLoopback addresses (e.g., 127.0.0.1, ::1)
privateRFC 1918 (IPv4) and RFC 4193 (IPv6) private addresses
publicAny address that is not local or private
global_unicastGlobal unicast addresses
unicastAlias for global_unicast
link_local_unicastLink-local unicast addresses
multicastMulticast addresses
link_local_multicastLink-local multicast addresses
interface_local_multicastInterface-local multicast addresses
unspecifiedThe unspecified address (0.0.0.0 or ::)
# Named network
network:
  client_ip: private
# CIDR notation
network:
  source.ip: "192.168.1.0/24"

Multiple networks can be specified as a list — the field matches if it belongs to any of them:

network:
  client_ip: ["private", "loopback"]

Logical Operators #

Logical operators combine or negate conditions to build complex expressions.

and #

Evaluates to true only when all inner conditions are true. Uses short-circuit evaluation — stops checking on the first false.

and:
  - equals:
      _ctx.request.method: "POST"
  - contains:
      _ctx.request.uri: "/api"

or #

Evaluates to true when any inner condition is true. Uses short-circuit evaluation — stops checking on the first true.

or:
  - equals:
      _ctx.response.status_code: 401
  - equals:
      _ctx.response.status_code: 403

not #

Negates a single inner condition. Evaluates to true when the inner condition is false.

not:
  contains:
    _ctx.request.uri: "/health"

Nesting Logical Operators #

Logical operators can be nested to any depth:

and:
  - equals:
      _ctx.request.method: "GET"
  - not:
      contains:
        _ctx.request.uri: "/internal"
  - or:
      - prefix:
          _ctx.request.uri: "/api/v1"
      - prefix:
          _ctx.request.uri: "/api/v2"

Domain-Specific Conditions #

queue_has_lag #

Tests whether a message queue has unconsumed messages. Accepts a list of queue specifiers. An optional > max_depth threshold can be appended:

queue_has_lag:
  - "my_queue"
  - "my_queue > 1000"

consumer_has_lag #

Tests whether a consumer group has fallen behind the producer on a queue:

consumer_has_lag:
  queue: "my_queue"
  group: "consumer_group"
  name: "consumer_1"

cluster_available #

Tests whether one or more Elasticsearch clusters are available:

cluster_available:
  - "primary_cluster"
  - "backup_cluster"

Using Conditions in Pipelines #

Conditions power the if/then/else branching in pipeline processor definitions. The if block takes a single condition configuration. When it evaluates to true, the then processors execute; otherwise, the else processors run (if provided).

Basic Branching #

pipeline:
  - name: my_pipeline
    auto_start: true
    keep_running: true
    processor:
      - if:
          equals:
            _ctx.request.method: "POST"
        then:
          - echo:
              message: "POST request received"
        else:
          - echo:
              message: "Non-POST request"

Range-Based Routing #

pipeline:
  - name: error_handler
    auto_start: true
    keep_running: true
    processor:
      - if:
          range:
            _ctx.response.status_code:
              gte: 400
              lt: 500
        then:
          - echo:
              message: "Client error (4xx)"
      - if:
          range:
            _ctx.response.status_code:
              gte: 500
        then:
          - echo:
              message: "Server error (5xx)"

Complex Conditions #

pipeline:
  - name: api_filter
    auto_start: true
    keep_running: true
    processor:
      - if:
          and:
            - equals:
                _ctx.request.method: "GET"
            - not:
                contains:
                  _ctx.request.uri: "/health"
            - exists:
                - _ctx.request.header.Authorization
        then:
          - echo:
              message: "Authenticated GET request (non-health)"

Nested If/Then/Else #

pipeline:
  - name: nested_routing
    auto_start: true
    keep_running: true
    processor:
      - if:
          equals:
            _ctx.request.method: "POST"
        then:
          - if:
              prefix:
                _ctx.request.uri: "/api/"
            then:
              - echo:
                  message: "POST to API"
            else:
              - echo:
                  message: "POST to non-API"
        else:
          - echo:
              message: "Non-POST request"

Using Conditions Programmatically #

You can create and evaluate conditions directly from Go code:

Creating a Condition from Config #

import "infini.sh/framework/core/conditions"

cfg := &conditions.Config{
    Equals: &conditions.Fields{},
}
// Typically populated by config deserialization, or manually:
cfg.Equals = conditions.MustNewFields(map[string]interface{}{
    "type": "process",
    "proc.pid": 305,
})

cond, err := conditions.NewCondition(cfg)
if err != nil {
    log.Fatal(err)
}

// Check against an event that implements ValuesMap
if cond.Check(event) {
    fmt.Println("Condition matched")
}

Building Compound Conditions #

import "infini.sh/framework/core/conditions"

cfg := &conditions.Config{
    AND: []conditions.Config{
        {
            Equals: conditions.MustNewFields(map[string]interface{}{
                "status": "active",
            }),
        },
        {
            Range: conditions.MustNewFields(map[string]interface{}{
                "age.gte": 18,
            }),
        },
    },
}

cond, err := conditions.NewCondition(cfg)
if err != nil {
    log.Fatal(err)
}

Using the Context Helper #

The Context type aggregates multiple ValuesMap sources, so a condition can read fields from several data providers. It also supports variable templates with $[[variable]] syntax:

ctx := &conditions.Context{}
ctx.AddContext(primaryData)
ctx.AddContext(fallbackData)

if cond.Check(ctx) {
    // Fields are looked up across all added contexts in order
}

Complete Example #

The following YAML shows a pipeline that uses multiple condition types together:

pipeline:
  - name: request_classifier
    auto_start: true
    keep_running: true
    processor:
      # Block requests from private networks to admin endpoints
      - if:
          and:
            - network:
                client_ip: private
            - prefix:
                _ctx.request.uri: "/admin"
        then:
          - echo:
              message: "Blocked private network access to admin"

      # Route API errors
      - if:
          and:
            - prefix:
                _ctx.request.uri: "/api/"
            - range:
                _ctx.response.status_code:
                  gte: 400
        then:
          - if:
              range:
                _ctx.response.status_code:
                  lt: 500
            then:
              - echo:
                  message: "API client error"
            else:
              - echo:
                  message: "API server error"

      # Match specific status codes
      - if:
          in:
            _ctx.response.status_code: [301, 302, 307, 308]
        then:
          - echo:
              message: "Redirect detected"

      # Check required fields exist
      - if:
          not:
            exists:
              - _ctx.request.header.X-Request-ID
        then:
          - echo:
              message: "Missing request ID header"

      # Pattern matching on log sources
      - if:
          or:
            - regexp:
                source: "nginx/access.*"
            - regexp:
                source: "apache2/access.*"
        then:
          - echo:
              message: "Web server access log"

      # Queue-based routing
      - if:
          queue_has_lag:
            - "indexing_queue > 5000"
        then:
          - echo:
              message: "Indexing queue has significant lag"

Operator Summary #

OperatorYAML KeyDescription
EqualsequalsExact value match (string, int, float, bool)
ContainscontainsSubstring match on strings or string arrays
RegexpregexpRegular expression match
PrefixprefixString starts-with check
SuffixsuffixString ends-with check
IninValue membership in a list
RangerangeNumeric range comparison (gt, gte, lt, lte)
ExistsexistsField existence and non-empty check
LengthlengthCollection/string length equality
NetworknetworkIP address network membership
ANDandLogical AND (all must match)
ORorLogical OR (any must match)
NOTnotLogical negation
Queue Has Lagqueue_has_lagMessage queue lag detection
Consumer Has Lagconsumer_has_lagConsumer group lag detection
Cluster Availablecluster_availableElasticsearch cluster availability
Edit Edit this page