File size: 6,851 Bytes
bf9e111 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | // Package errors provides standardized domain error types for the CLI Proxy API.
// These errors are used throughout the domain layer and are mapped to appropriate
// HTTP responses at the transport layer.
package errors
import (
"errors"
"fmt"
)
// ErrorCode represents a standardized error code for API responses
type ErrorCode string
const (
// NotFound indicates a requested resource was not found
NotFound ErrorCode = "NOT_FOUND"
// Unauthorized indicates authentication is required or failed
Unauthorized ErrorCode = "UNAUTHORIZED"
// Forbidden indicates the user lacks permission
Forbidden ErrorCode = "FORBIDDEN"
// InvalidInput indicates the request input is invalid
InvalidInput ErrorCode = "INVALID_INPUT"
// Conflict indicates a resource conflict (e.g., duplicate)
Conflict ErrorCode = "CONFLICT"
// InternalError indicates an unexpected internal error
InternalError ErrorCode = "INTERNAL_ERROR"
// ServiceUnavailable indicates a dependent service is unavailable
ServiceUnavailable ErrorCode = "SERVICE_UNAVAILABLE"
// Timeout indicates the operation timed out
Timeout ErrorCode = "TIMEOUT"
// ValidationFailed indicates validation of data failed
ValidationFailed ErrorCode = "VALIDATION_FAILED"
// AlreadyExists indicates a resource already exists
AlreadyExists ErrorCode = "ALREADY_EXISTS"
)
// DomainError is the base error type for all domain errors.
// It provides structured error information that can be consistently
// mapped to HTTP responses.
type DomainError struct {
Code ErrorCode
Message string
Cause error
Details map[string]interface{}
}
// Error implements the error interface
func (e *DomainError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("[%s] %s: %v", e.Code, e.Message, e.Cause)
}
return fmt.Sprintf("[%s] %s", e.Code, e.Message)
}
// Unwrap returns the underlying cause of the error
func (e *DomainError) Unwrap() error {
return e.Cause
}
// WithDetail adds a detail field to the error
func (e *DomainError) WithDetail(key string, value interface{}) *DomainError {
if e.Details == nil {
e.Details = make(map[string]interface{})
}
e.Details[key] = value
return e
}
// IsDomainError checks if an error is a DomainError
func IsDomainError(err error) (*DomainError, bool) {
var domainErr *DomainError
if errors.As(err, &domainErr) {
return domainErr, true
}
return nil, false
}
// New creates a new DomainError with the given code and message
func New(code ErrorCode, message string) *DomainError {
return &DomainError{
Code: code,
Message: message,
}
}
// Wrap wraps an existing error with a domain error
func Wrap(code ErrorCode, message string, cause error) *DomainError {
return &DomainError{
Code: code,
Message: message,
Cause: cause,
}
}
// Predefined error constructors for common cases
// NewNotFoundError creates a NOT_FOUND error
func NewNotFoundError(resource string, identifier string) *DomainError {
return &DomainError{
Code: NotFound,
Message: fmt.Sprintf("%s not found: %s", resource, identifier),
}
}
// NewUnauthorizedError creates an UNAUTHORIZED error
func NewUnauthorizedError(message string) *DomainError {
return &DomainError{
Code: Unauthorized,
Message: message,
}
}
// NewForbiddenError creates a FORBIDDEN error
func NewForbiddenError(message string) *DomainError {
return &DomainError{
Code: Forbidden,
Message: message,
}
}
// NewInvalidInputError creates an INVALID_INPUT error
func NewInvalidInputError(message string) *DomainError {
return &DomainError{
Code: InvalidInput,
Message: message,
}
}
// NewValidationError creates a VALIDATION_FAILED error with field details
func NewValidationError(message string, field string, reason string) *DomainError {
err := &DomainError{
Code: ValidationFailed,
Message: message,
}
if field != "" {
err.WithDetail("field", field)
}
if reason != "" {
err.WithDetail("reason", reason)
}
return err
}
// NewConflictError creates a CONFLICT error
func NewConflictError(message string) *DomainError {
return &DomainError{
Code: Conflict,
Message: message,
}
}
// NewAlreadyExistsError creates an ALREADY_EXISTS error
func NewAlreadyExistsError(resource string, identifier string) *DomainError {
return &DomainError{
Code: AlreadyExists,
Message: fmt.Sprintf("%s already exists: %s", resource, identifier),
}
}
// NewInternalError creates an INTERNAL_ERROR
func NewInternalError(message string, cause error) *DomainError {
return &DomainError{
Code: InternalError,
Message: message,
Cause: cause,
}
}
// NewServiceUnavailableError creates a SERVICE_UNAVAILABLE error
func NewServiceUnavailableError(service string) *DomainError {
return &DomainError{
Code: ServiceUnavailable,
Message: fmt.Sprintf("Service unavailable: %s", service),
}
}
// NewTimeoutError creates a TIMEOUT error
func NewTimeoutError(operation string) *DomainError {
return &DomainError{
Code: Timeout,
Message: fmt.Sprintf("Operation timed out: %s", operation),
}
}
// HTTPStatusCode returns the appropriate HTTP status code for the error
func (e *DomainError) HTTPStatusCode() int {
switch e.Code {
case NotFound:
return 404
case Unauthorized:
return 401
case Forbidden:
return 403
case InvalidInput, ValidationFailed:
return 400
case Conflict, AlreadyExists:
return 409
case ServiceUnavailable:
return 503
case Timeout:
return 504
default:
return 500
}
}
// ToResponse converts the error to a response map suitable for JSON serialization
func (e *DomainError) ToResponse() map[string]interface{} {
response := map[string]interface{}{
"error": string(e.Code),
"message": e.Message,
}
if len(e.Details) > 0 {
response["details"] = e.Details
}
return response
}
// Common error instances for reuse
var (
// ErrConfigNotFound is returned when configuration is not found
ErrConfigNotFound = New(NotFound, "configuration not found")
// ErrAuthFileNotFound is returned when an auth file is not found
ErrAuthFileNotFound = New(NotFound, "auth file not found")
// ErrInvalidConfig is returned when configuration is invalid
ErrInvalidConfig = New(ValidationFailed, "invalid configuration")
// ErrAuthManagerUnavailable is returned when the auth manager is not available
ErrAuthManagerUnavailable = New(ServiceUnavailable, "auth manager unavailable")
// ErrTokenStoreUnavailable is returned when the token store is not available
ErrTokenStoreUnavailable = New(ServiceUnavailable, "token store unavailable")
// ErrLogDirectoryNotConfigured is returned when log directory is not set
ErrLogDirectoryNotConfigured = New(InternalError, "log directory not configured")
// ErrLoggingDisabled is returned when logging to file is disabled
ErrLoggingDisabled = New(ServiceUnavailable, "logging to file disabled")
) |