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 }