115 lines
3.1 KiB
Go
115 lines
3.1 KiB
Go
package errors
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
// ErrorCode represents a specific error type
|
|
type ErrorCode string
|
|
|
|
const (
|
|
ErrCodeInternal ErrorCode = "INTERNAL_ERROR"
|
|
ErrCodeNotFound ErrorCode = "NOT_FOUND"
|
|
ErrCodeBadRequest ErrorCode = "BAD_REQUEST"
|
|
ErrCodeConflict ErrorCode = "CONFLICT"
|
|
ErrCodeUnauthorized ErrorCode = "UNAUTHORIZED"
|
|
ErrCodeForbidden ErrorCode = "FORBIDDEN"
|
|
ErrCodeServiceUnavailable ErrorCode = "SERVICE_UNAVAILABLE"
|
|
ErrCodeValidation ErrorCode = "VALIDATION_ERROR"
|
|
)
|
|
|
|
// APIError represents a structured API error
|
|
type APIError struct {
|
|
Code ErrorCode `json:"code"`
|
|
Message string `json:"message"`
|
|
Details string `json:"details,omitempty"`
|
|
HTTPStatus int `json:"-"`
|
|
}
|
|
|
|
func (e *APIError) Error() string {
|
|
if e.Details != "" {
|
|
return fmt.Sprintf("%s: %s (%s)", e.Code, e.Message, e.Details)
|
|
}
|
|
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
|
}
|
|
|
|
// NewAPIError creates a new API error
|
|
func NewAPIError(code ErrorCode, message string, httpStatus int) *APIError {
|
|
return &APIError{
|
|
Code: code,
|
|
Message: message,
|
|
HTTPStatus: httpStatus,
|
|
}
|
|
}
|
|
|
|
// WithDetails adds details to an error
|
|
func (e *APIError) WithDetails(details string) *APIError {
|
|
e.Details = details
|
|
return e
|
|
}
|
|
|
|
// Common error constructors
|
|
func ErrNotFound(resource string) *APIError {
|
|
return NewAPIError(ErrCodeNotFound, fmt.Sprintf("%s not found", resource), http.StatusNotFound)
|
|
}
|
|
|
|
func ErrBadRequest(message string) *APIError {
|
|
return NewAPIError(ErrCodeBadRequest, message, http.StatusBadRequest)
|
|
}
|
|
|
|
func ErrConflict(message string) *APIError {
|
|
return NewAPIError(ErrCodeConflict, message, http.StatusConflict)
|
|
}
|
|
|
|
func ErrInternal(message string) *APIError {
|
|
return NewAPIError(ErrCodeInternal, message, http.StatusInternalServerError)
|
|
}
|
|
|
|
func ErrServiceUnavailable(service string) *APIError {
|
|
return NewAPIError(ErrCodeServiceUnavailable, fmt.Sprintf("%s service is unavailable", service), http.StatusServiceUnavailable)
|
|
}
|
|
|
|
func ErrValidation(message string) *APIError {
|
|
return NewAPIError(ErrCodeValidation, message, http.StatusBadRequest)
|
|
}
|
|
|
|
// RetryConfig defines retry behavior
|
|
type RetryConfig struct {
|
|
MaxAttempts int
|
|
Backoff func(attempt int) error // Returns error if should stop retrying
|
|
}
|
|
|
|
// DefaultRetryConfig returns a default retry configuration
|
|
func DefaultRetryConfig() RetryConfig {
|
|
return RetryConfig{
|
|
MaxAttempts: 3,
|
|
Backoff: func(attempt int) error {
|
|
// Simple exponential backoff: 100ms, 200ms, 400ms
|
|
if attempt >= 3 {
|
|
return fmt.Errorf("max attempts reached")
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
// Retry executes a function with retry logic
|
|
func Retry(fn func() error, config RetryConfig) error {
|
|
var lastErr error
|
|
for attempt := 1; attempt <= config.MaxAttempts; attempt++ {
|
|
if err := fn(); err == nil {
|
|
return nil
|
|
} else {
|
|
lastErr = err
|
|
}
|
|
|
|
if attempt < config.MaxAttempts {
|
|
if err := config.Backoff(attempt); err != nil {
|
|
return fmt.Errorf("retry aborted: %w", err)
|
|
}
|
|
}
|
|
}
|
|
return fmt.Errorf("retry failed after %d attempts: %w", config.MaxAttempts, lastErr)
|
|
}
|