115 lines
3.0 KiB
Go
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
|
|
}
|