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