This commit is contained in:
114
internal/errors/errors.go
Normal file
114
internal/errors/errors.go
Normal file
@@ -0,0 +1,114 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user