persona-community-2/pkg/laozhang/errors.go
jordan cb3d4d5786
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:53:55 +00:00

200 lines
4.9 KiB
Go

package laozhang
import (
"errors"
"fmt"
)
// Sentinel errors for common error types
var (
// ErrInvalidConfig indicates configuration validation failed
ErrInvalidConfig = errors.New("invalid configuration")
// ErrRateLimit indicates rate limit exceeded (HTTP 429)
ErrRateLimit = errors.New("rate limit exceeded")
// ErrServerError indicates server-side error (HTTP 5xx)
ErrServerError = errors.New("server error")
// ErrInvalidRequest indicates client error (HTTP 4xx except 429, 401)
ErrInvalidRequest = errors.New("invalid request")
// ErrTimeout indicates request timeout
ErrTimeout = errors.New("request timeout")
// ErrUnauthorized indicates authentication failed (HTTP 401)
ErrUnauthorized = errors.New("unauthorized")
)
// APIError represents an error returned by the LaoZhang API
type APIError struct {
StatusCode int // HTTP status code
Message string // Human-readable error message
Type string // Error type from API response
Code string // Error code from API response
err error // Underlying error for wrapping
Details map[string]string // Additional error details
}
// Error implements the error interface
func (e *APIError) Error() string {
if e.Code != "" {
return fmt.Sprintf("laozhang api error (status %d): [%s] %s", e.StatusCode, e.Code, e.Message)
}
return fmt.Sprintf("laozhang api error (status %d): %s", e.StatusCode, e.Message)
}
// Unwrap implements the errors.Unwrap interface for error chains
func (e *APIError) Unwrap() error {
return e.err
}
// NewAPIError creates a new APIError with the given parameters
func NewAPIError(statusCode int, message, errorType, code string, underlying error) *APIError {
return &APIError{
StatusCode: statusCode,
Message: message,
Type: errorType,
Code: code,
err: underlying,
Details: make(map[string]string),
}
}
// WithDetails adds additional details to the error
func (e *APIError) WithDetails(key, value string) *APIError {
if e.Details == nil {
e.Details = make(map[string]string)
}
e.Details[key] = value
return e
}
// IsRateLimitError checks if the error is a rate limit error
func IsRateLimitError(err error) bool {
if err == nil {
return false
}
// Check for sentinel error
if errors.Is(err, ErrRateLimit) {
return true
}
// Check for APIError with 429 status
var apiErr *APIError
if errors.As(err, &apiErr) {
return apiErr.StatusCode == 429
}
return false
}
// IsServerError checks if the error is a server error (5xx)
func IsServerError(err error) bool {
if err == nil {
return false
}
// Check for sentinel error
if errors.Is(err, ErrServerError) {
return true
}
// Check for APIError with 5xx status
var apiErr *APIError
if errors.As(err, &apiErr) {
return apiErr.StatusCode >= 500 && apiErr.StatusCode < 600
}
return false
}
// IsRetryableError checks if the error should trigger a retry
// Retryable errors include: rate limits (429), server errors (5xx), and timeouts
func IsRetryableError(err error) bool {
if err == nil {
return false
}
// Check sentinel errors
if errors.Is(err, ErrRateLimit) || errors.Is(err, ErrServerError) || errors.Is(err, ErrTimeout) {
return true
}
// Check APIError status codes
var apiErr *APIError
if errors.As(err, &apiErr) {
// Retry on rate limits and server errors
return apiErr.StatusCode == 429 || (apiErr.StatusCode >= 500 && apiErr.StatusCode < 600)
}
return false
}
// IsUnauthorizedError checks if the error is an unauthorized error (401)
func IsUnauthorizedError(err error) bool {
if err == nil {
return false
}
// Check for sentinel error
if errors.Is(err, ErrUnauthorized) {
return true
}
// Check for APIError with 401 status
var apiErr *APIError
if errors.As(err, &apiErr) {
return apiErr.StatusCode == 401
}
return false
}
// IsTimeoutError checks if the error is a timeout error
func IsTimeoutError(err error) bool {
if err == nil {
return false
}
return errors.Is(err, ErrTimeout)
}
// IsInvalidRequestError checks if the error is a client error (4xx except 429, 401)
func IsInvalidRequestError(err error) bool {
if err == nil {
return false
}
// Check for sentinel error
if errors.Is(err, ErrInvalidRequest) {
return true
}
// Check for APIError with 4xx status (excluding 429 and 401)
var apiErr *APIError
if errors.As(err, &apiErr) {
return apiErr.StatusCode >= 400 && apiErr.StatusCode < 500 &&
apiErr.StatusCode != 429 && apiErr.StatusCode != 401
}
return false
}
// classifyHTTPError classifies an HTTP status code into a domain error type
func classifyHTTPError(statusCode int) error {
switch {
case statusCode == 401:
return ErrUnauthorized
case statusCode == 429:
return ErrRateLimit
case statusCode >= 400 && statusCode < 500:
return ErrInvalidRequest
case statusCode >= 500 && statusCode < 600:
return ErrServerError
default:
return nil
}
}