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 } }