API / internal /domain /errors /errors.go
sshinmen's picture
Clean deploy to HF Space
bf9e111
Raw
History Blame
6.85 kB
// 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")
)