persona-community-1/pkg/routing/failure.go
jordan 4004f88f4a
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/manual/woodpecker Pipeline was successful
Initialize project from skeleton template
2026-02-23 10:20:59 +00:00

115 lines
3.0 KiB
Go

package routing
import (
"errors"
"strings"
)
// FailureType categorizes errors for cooldown duration selection.
type FailureType int
const (
// FailureTypeNone indicates the error doesn't warrant a cooldown.
// Examples: content blocked, invalid request, timeout.
FailureTypeNone FailureType = iota
// FailureTypeRateLimit indicates rate limiting or quota exhaustion.
// Uses long cooldown (DefaultCooldownPeriod).
// Examples: HTTP 429, quota_exceeded, resource_exhausted.
FailureTypeRateLimit
// FailureTypeTransient indicates transient server errors.
// Uses short cooldown (TransientCooldownPeriod).
// Examples: HTTP 500, 503, server unavailable, overloaded.
FailureTypeTransient
)
// String returns a human-readable name for the failure type.
func (f FailureType) String() string {
switch f {
case FailureTypeNone:
return "none"
case FailureTypeRateLimit:
return "rate-limit"
case FailureTypeTransient:
return "transient"
default:
return "unknown"
}
}
// ClassifyError determines the failure type for cooldown selection.
//
// Returns:
// - FailureTypeRateLimit for 429/quota errors (long cooldown)
// - FailureTypeTransient for 5xx errors (short cooldown)
// - FailureTypeNone for errors that don't warrant cooldown
//
// The function checks wrapped sentinel errors first (using errors.Is),
// then falls back to error message pattern matching for untyped errors.
func ClassifyError(err error) FailureType {
if err == nil {
return FailureTypeNone
}
// Check wrapped sentinel errors first (most specific)
if errors.Is(err, ErrRateLimit) || errors.Is(err, ErrQuotaExceeded) {
return FailureTypeRateLimit
}
if errors.Is(err, ErrServerUnavailable) {
return FailureTypeTransient
}
// Pattern matching on error message for untyped errors
errStr := strings.ToLower(err.Error())
// Rate limit patterns (long cooldown)
rateLimitPatterns := []string{
"rate limit",
"ratelimit",
"too many requests",
"429",
"quota",
"resource_exhausted",
}
for _, pattern := range rateLimitPatterns {
if strings.Contains(errStr, pattern) {
return FailureTypeRateLimit
}
}
// Transient server error patterns (short cooldown)
transientPatterns := []string{
"503",
"500",
"unavailable",
"overloaded",
"service unavailable",
"temporarily unavailable",
"server error",
"internal server error",
}
for _, pattern := range transientPatterns {
if strings.Contains(errStr, pattern) {
return FailureTypeTransient
}
}
return FailureTypeNone
}
// IsRateLimitError returns true if the error indicates rate limiting.
func IsRateLimitError(err error) bool {
return ClassifyError(err) == FailureTypeRateLimit
}
// IsTransientError returns true if the error is transient (quick recovery expected).
func IsTransientError(err error) bool {
return ClassifyError(err) == FailureTypeTransient
}
// IsCooldownTriggeringError returns true if the error should trigger cooldown.
func IsCooldownTriggeringError(err error) bool {
return ClassifyError(err) != FailureTypeNone
}