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
- func ExtractMessageValue(data MessageFields, path string) (any, bool)
- type ConditionExpression
- type EvaluationError
- type Evaluator
- func (e *Evaluator) Distance(entity1, entity2 *gtypes.EntityState) (float64, error)
- func (e *Evaluator) Evaluate(entityState *gtypes.EntityState, expr LogicalExpression) (bool, error)
- func (e *Evaluator) EvaluateWithStateAndMessage(entityState *gtypes.EntityState, stateFields StateFields, ...) (bool, error)
- func (e *Evaluator) GetOutgoing(entity *gtypes.EntityState, predicate string) []string
- func (e *Evaluator) HasTriple(entity *gtypes.EntityState, predicate string) bool
- type FieldType
- type LogicalExpression
- type MessageFields
- type OperatorFunc
- type StateFields
- type TypeDetector
Constants ¶
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
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.
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 bool `json:"required"` // If false, missing field doesn't fail evaluation
From interface{} `json:"from,omitempty"` // For transition operator: allowed previous value(s)
}
ConditionExpression represents a single field/operator/value condition
type EvaluationError ¶
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:
- `$state.*` / `$prev.*` → stateFields map
- `$message.<dotted.path>` → messageFields via deep-walk
- 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.
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 )
type LogicalExpression ¶
type LogicalExpression struct {
Conditions []ConditionExpression `json:"conditions"`
Logic string `json:"logic"` // "and", "or"
}
LogicalExpression combines multiple conditions with logic operators
type MessageFields ¶
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 ¶
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)
DetectFieldType(value interface{}) FieldType
}
TypeDetector determines field type and extracts values from entity state