Files
atlas/internal/errors/errors.go
othman.suseno df475bc85e
Some checks failed
CI / test-build (push) Failing after 2m11s
logging and diagnostic features added
2025-12-15 00:45:14 +07:00

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