expression

package
v1.0.0-beta.86 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 29, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package expression - Expression evaluator implementation

Package expression — Dotted-path access into MessageFields payloads.

Both `$message.*` substitution (in processor/rule/message_substitution.go) and `$message.*` condition evaluation (Evaluator.EvaluateWithStateAndMessage) walk the same map shape via the same dotted-path rules. This helper is the single source of truth — shared via the expression package so the rule package can import it without introducing a cycle.

Package expression - Regex pattern caching using framework's cache package

Package expression - Simple DSL for rule condition evaluation

Index

Constants

View Source
const (
	// Numeric operators
	OpEqual            = "eq"
	OpNotEqual         = "ne"
	OpLessThan         = "lt"
	OpLessThanEqual    = "lte"
	OpGreaterThan      = "gt"
	OpGreaterThanEqual = "gte"
	OpBetween          = "between"

	// String operators
	OpContains   = "contains"
	OpStartsWith = "starts_with"
	OpEndsWith   = "ends_with"
	OpRegexMatch = "regex"

	// Array operators
	OpIn            = "in"
	OpNotIn         = "not_in"
	OpLengthEq      = "length_eq"
	OpLengthGt      = "length_gt"
	OpLengthLt      = "length_lt"
	OpArrayContains = "array_contains"

	// State transition operator
	OpTransition = "transition"
)

Supported operators by field type

View Source
const (
	LogicAnd = "and"
	LogicOr  = "or"
)

Logic operators

Variables

This section is empty.

Functions

func ExtractMessageValue

func ExtractMessageValue(data MessageFields, path string) (any, bool)

ExtractMessageValue walks data along a dotted path. Returns the terminal value and true on success; the second return is false (and the caller must treat the result as "field not present") when:

  • any non-terminal segment is missing from the current map, OR
  • any non-terminal segment resolves to a non-map value (can't descend further), OR
  • the terminal segment is missing.

A nil terminal value is considered a successful resolution (the JSON `null` distinct from "missing"). Callers can render `<nil>` via fmt; the surfacing safety net is the regex-leftover warning at the substitution layer, or the `Required` flag at the condition layer.

The path is the bare dotted form (no `$message.` prefix). Callers trim the prefix before calling.

func RegisteredOperators

func RegisteredOperators() map[string]struct{}

RegisteredOperators returns the set of operator names a freshly- constructed Evaluator knows how to evaluate, INCLUDING the special-cased OpTransition (which evaluateConditionWithStateAndMessage dispatches outside the regular operator map). Intended for the rule-config validator at load time so the validator's "is this operator name supported" check derives from the evaluator's actual wiring rather than a hardcoded list — #147 surfaced the split-brain class where length_eq / length_gt / length_lt / array_contains were validator-listed but evaluator-unregistered, causing rules to pass config validation and fail at fire time with "unsupported operator". Deriving the validator's set from the same construction path the evaluator uses prevents the class structurally.

The set is computed by spinning up a throwaway evaluator and reading its operator map. Cheap (no I/O); typical validator paths call this at boot, not per-rule.

Types

type ConditionExpression

type ConditionExpression struct {
	Field    string      `json:"field"`    // Predicate field (e.g., "robotics.battery.level")
	Operator string      `json:"operator"` // Comparison operator (e.g., "lte", "eq", "contains")
	Value    interface{} `json:"value"`    // Comparison value (20.0, "active", true)
	// Required: when true, a missing field causes evaluation to error
	// rather than silently match false. Scalar operators (eq, lt,
	// contains, ...) honour this strictly: missing predicate +
	// Required=true → EvaluationError.
	//
	// Array operators (length_eq, length_gt, length_lt, array_contains
	// — see isArrayOperator) DELIBERATELY DIVERGE: a missing predicate
	// resolves to an empty array (`[]interface{}{}`) and the operator
	// runs against it. length_eq with value=0 then matches; length_gt
	// with value=N>0 doesn't; array_contains returns false. This is
	// the intended semantic for the #147 / ADR-046 Phase 1 counter
	// pattern, where a join rule wanting "fire before any children
	// complete" is a legitimate authoring intent. Authors who want
	// "this predicate MUST exist on the entity" should pair the array
	// operator with a separate scalar eq/exists condition over the
	// same field.
	Required bool        `json:"required"`
	From     interface{} `json:"from,omitempty"` // For transition operator: allowed previous value(s)
}

ConditionExpression represents a single field/operator/value condition

type EvaluationError

type EvaluationError struct {
	Field    string
	Operator string
	Message  string
	Err      error
}

EvaluationError represents an error during expression evaluation

func (*EvaluationError) Error

func (e *EvaluationError) Error() string

func (*EvaluationError) Unwrap

func (e *EvaluationError) Unwrap() error

type Evaluator

type Evaluator struct {
	// contains filtered or unexported fields
}

Evaluator processes expressions against entity state

func NewExpressionEvaluator

func NewExpressionEvaluator() *Evaluator

NewExpressionEvaluator creates a new expression evaluator with all supported operators

func (*Evaluator) Distance

func (e *Evaluator) Distance(entity1, entity2 *gtypes.EntityState) (float64, error)

Distance calculates the great-circle distance between two entities in meters using the Haversine formula. Returns error if either entity lacks position data. Position is now extracted from geo.location.* triples (single source of truth).

func (*Evaluator) Evaluate

func (e *Evaluator) Evaluate(entityState *gtypes.EntityState, expr LogicalExpression) (bool, error)

Evaluate evaluates a logical expression against an entity state. Equivalent to EvaluateWithStateAndMessage with nil stateFields and nil messageFields — entity triples are the only resolution source.

func (*Evaluator) EvaluateWithStateAndMessage

func (e *Evaluator) EvaluateWithStateAndMessage(entityState *gtypes.EntityState, stateFields StateFields, messageFields MessageFields, expr LogicalExpression) (bool, error)

EvaluateWithStateAndMessage evaluates a logical expression against the full field-resolution surface: entity triples, rule match-state (`$state.*`/`$prev.*`), and the inbound message payload (`$message.*`).

Field resolution precedence per condition:

  1. `$state.*` / `$prev.*` → stateFields map
  2. `$message.<dotted.path>` → messageFields via deep-walk
  3. Bare field name → entity triples first (when entity is non-nil); falls through to messageFields if not present on the entity. When entity is nil (message-path rules), bare names resolve from messageFields directly.

This is the single condition evaluator for both rule-level conditions (where the rule fires on message-path or entity-state events) and action-level `when` guards. See ADR-041 for the unification rationale.

`$message.command` is the recommended form when both rule-level and when-clause use the same field, because it makes the resolution source explicit. Bare `command` works equivalently on message-path rules but implicitly prefers entity triples on entity-path rules.

func (*Evaluator) GetOutgoing

func (e *Evaluator) GetOutgoing(entity *gtypes.EntityState, predicate string) []string

GetOutgoing returns a list of entity IDs for outgoing relationships with the given predicate. Only returns valid entity IDs (4-part dotted notation), not literal values. Returns empty slice if entity is nil or no relationships found.

func (*Evaluator) HasTriple

func (e *Evaluator) HasTriple(entity *gtypes.EntityState, predicate string) bool

HasTriple checks if an entity has a triple with the given predicate. Returns false if entity is nil or predicate not found.

type FieldType

type FieldType int

FieldType represents the detected type of a field

const (
	// FieldTypeUnknown represents an unknown or unsupported field type
	FieldTypeUnknown FieldType = iota
	// FieldTypeFloat64 represents a floating point number field
	FieldTypeFloat64
	// FieldTypeString represents a string field
	FieldTypeString
	// FieldTypeBool represents a boolean field
	FieldTypeBool
	// FieldTypeArray represents an array field
	FieldTypeArray
)

func (FieldType) String

func (f FieldType) String() string

type LogicalExpression

type LogicalExpression struct {
	Conditions []ConditionExpression `json:"conditions"`
	Logic      string                `json:"logic"` // "and", "or"
}

LogicalExpression combines multiple conditions with logic operators

type MessageFields

type MessageFields map[string]any

MessageFields carries the inbound NATS message payload for `$message.*` pseudo-field resolution in condition expressions. The map shape is `map[string]any` (matching `GenericJSONPayload.Data` and what `ExecutionContext.MessageData` carries through the rule pipeline).

Deep-path access is supported: `$message.tool_args.command` walks the map along the dotted path. Bare field names (e.g. `command`) also resolve here when entity state is nil or when the field isn't present as an entity triple — see Evaluator.EvaluateWithStateAndMessage for the full precedence rule.

Empty or nil maps cause `$message.*` and bare-name message lookups to return "not found" — same surfacing behaviour as missing entity triples (condition fails when Required=false, errors when Required=true).

type OperatorFunc

type OperatorFunc func(fieldValue, compareValue interface{}) (bool, error)

OperatorFunc defines the signature for operator implementations

type StateFields

type StateFields map[string]interface{}

StateFields provides rule match state values for $state.* pseudo-field resolution in condition expressions. Keys are the full field names (e.g., "$state.iteration"). This avoids circular dependencies between the expression and rule packages.

type TypeDetector

type TypeDetector interface {
	GetFieldValue(entityState *gtypes.EntityState, field string) (value interface{}, exists bool, err error)
	// GetFieldValuesAll returns every triple Object matching the given
	// predicate as a []interface{}. Used by array operators (length_eq,
	// length_gt, length_lt, array_contains — see isArrayOperator) where
	// the natural semantics are "how many triples carry this predicate"
	// rather than "what is the first triple's Object". Returns
	// (nil, false) when no triple matches. #147 / ADR-046 Phase 1 join
	// gap: the counter pattern stamps N triples with the same
	// predicate keyed by child ID; the join rule counts them via
	// length_eq on this multi-valued path.
	GetFieldValuesAll(entityState *gtypes.EntityState, field string) (values []interface{}, exists bool, err error)
	DetectFieldType(value interface{}) FieldType
}

TypeDetector determines field type and extracts values from entity state

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL