Ctrl K
ComunidadGuías…

internal/gateway/failover_policy.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package gateway

import (
	"errors"
	"strings"

	"github.com/enterpilot/gomodel/config"
	"github.com/enterpilot/gomodel/internal/core"
)

// FailoverPolicy decides which failed attempts trigger a failover sweep and
// how many targets the sweep may try. Build one with NewFailoverPolicy; a nil
// policy, or one with no matchers, retries on the documented defaults.
type FailoverPolicy struct {
	// MaxAttempts caps the failover targets tried per request; zero sweeps
	// every remaining target.
	MaxAttempts int
	statuses    map[int]bool
	phrases     []config.FailoverErrorPhrase
}

// NewFailoverPolicy compiles the failover section of the configuration. A
// config that was not loaded through config.Load (so its parsed fields are
// empty) falls back to the documented defaults.
func NewFailoverPolicy(cfg config.FailoverConfig) *FailoverPolicy {
	policy := &FailoverPolicy{
		MaxAttempts: max(cfg.MaxAttempts, 0),
		statuses:    cfg.RetryStatuses,
		phrases:     cfg.RetryErrors,
	}
	if len(policy.statuses) == 0 || len(policy.phrases) == 0 {
		defaults := config.FailoverConfig{}
		if err := config.LoadFailoverPolicy(&defaults); err != nil {
			panic("gateway: default failover policy must parse: " + err.Error())
		}
		if len(policy.statuses) == 0 {
			policy.statuses = defaults.RetryStatuses
		}
		if len(policy.phrases) == 0 {
			policy.phrases = defaults.RetryErrors
		}
	}
	return policy
}

var defaultFailoverPolicy = NewFailoverPolicy(config.FailoverConfig{})

// ShouldRetry reports whether err should trigger a failover sweep: its HTTP
// status is in retry_on_statuses, or its code and message contain every word
// of a retry_on_errors phrase (with that phrase's status constraint, if any).
func (p *FailoverPolicy) ShouldRetry(err error) bool {
	if p == nil {
		p = defaultFailoverPolicy
	}
	statuses, phrases := p.statuses, p.phrases
	if len(statuses) == 0 && len(phrases) == 0 {
		statuses, phrases = defaultFailoverPolicy.statuses, defaultFailoverPolicy.phrases
	}
	var gatewayErr *core.GatewayError
	if !errors.As(err, &gatewayErr) || gatewayErr == nil {
		return false
	}

	status := gatewayErr.HTTPStatusCode()
	if statuses[status] {
		return true
	}

	text := strings.ToLower(gatewayErr.Message)
	if gatewayErr.Code != nil {
		text = strings.ToLower(*gatewayErr.Code) + " " + text
	}
	for _, phrase := range phrases {
		if len(phrase.Statuses) > 0 && !phrase.Statuses[status] {
			continue
		}
		if containsAll(text, phrase.Words) {
			return true
		}
	}
	return false
}

// attemptsExhausted reports whether attempts has reached the policy cap.
func (p *FailoverPolicy) attemptsExhausted(attempts int) bool {
	return p != nil && p.MaxAttempts > 0 && attempts >= p.MaxAttempts
}

func containsAll(text string, words []string) bool {
	for _, word := range words {
		if !strings.Contains(text, word) {
			return false
		}
	}
	return true
}

// ShouldAttemptFailover reports whether err should trigger translated failover
// under the default policy.
func ShouldAttemptFailover(err error) bool {
	return defaultFailoverPolicy.ShouldRetry(err)
}