package routing import ( "context" "time" "go.uber.org/zap" ) // Engine evaluates routing rules from the RuleCache against a RoutingContext. // It is the central component of the intelligent routing system. type Engine struct { cache *RuleCache logger *zap.Logger } // New creates an Engine backed by store with the given cache TTL. func New(store RuleStore, ttl time.Duration, logger *zap.Logger) *Engine { return &Engine{ cache: NewRuleCache(store, ttl, logger), logger: logger, } } // Start launches the cache background refresh goroutine. func (e *Engine) Start() { e.cache.Start() } // Stop shuts down the cache refresh goroutine. func (e *Engine) Stop() { e.cache.Stop() } // Cache returns the underlying RuleCache so callers (e.g. admin API) can // invalidate entries after mutations. func (e *Engine) Cache() *RuleCache { return e.cache } // Evaluate finds the first matching rule for rctx and returns its Action. // Rules are evaluated in priority order (ASC). All conditions within a rule // must match (AND logic). An empty Conditions slice matches everything (catch-all). // // Returns (action, true, nil) on match. // Returns (Action{}, false, nil) when no rule matches. // Returns (Action{}, false, err) on cache/store errors. func (e *Engine) Evaluate(ctx context.Context, rctx *RoutingContext) (Action, bool, error) { rules, err := e.cache.Get(ctx, rctx.TenantID) if err != nil { return Action{}, false, err } for _, rule := range rules { // already sorted ASC by priority if matchesAll(rule.Conditions, rctx) { e.logger.Debug("routing rule matched", zap.String("rule_id", rule.ID), zap.String("rule_name", rule.Name), zap.String("provider", rule.Action.Provider), zap.String("tenant_id", rctx.TenantID), ) return rule.Action, true, nil } } return Action{}, false, nil } // matchesAll returns true if every condition in the slice holds for rctx. // An empty slice is a catch-all — it always matches. func matchesAll(conditions []Condition, rctx *RoutingContext) bool { for _, c := range conditions { if !c.Evaluate(rctx) { return false } } return true }