API / internal /domain /errors /errors.go
sshinmen's picture
Update HF Spaces deployment with latest changes
2e6b65c
Raw
History Blame Contribute Delete
9.66 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"
// New Error Codes (Standardization Plan)
// Authentication errors
ErrCodeInvalidCredentials ErrorCode = "AUTH_INVALID_CREDENTIALS"
ErrCodeTokenExpired ErrorCode = "AUTH_TOKEN_EXPIRED"
ErrCodeTokenInvalid ErrorCode = "AUTH_TOKEN_INVALID"
ErrCodeUnauthorized ErrorCode = "AUTH_UNAUTHORIZED"
// Configuration errors
ErrCodeConfigNotFound ErrorCode = "CONFIG_NOT_FOUND"
ErrCodeConfigInvalid ErrorCode = "CONFIG_INVALID"
ErrCodeConfigValidation ErrorCode = "CONFIG_VALIDATION_FAILED"
// Provider errors
ErrCodeProviderNotFound ErrorCode = "PROVIDER_NOT_FOUND"
ErrCodeProviderUnavailable ErrorCode = "PROVIDER_UNAVAILABLE"
ErrCodeProviderRateLimited ErrorCode = "PROVIDER_RATE_LIMITED"
// Request errors
ErrCodeInvalidRequest ErrorCode = "REQUEST_INVALID"
ErrCodeMissingField ErrorCode = "REQUEST_MISSING_FIELD"
ErrCodeInvalidFormat ErrorCode = "REQUEST_INVALID_FORMAT"
// Storage errors
ErrCodeStorageFailure ErrorCode = "STORAGE_FAILURE"
ErrCodeNotFound ErrorCode = "RESOURCE_NOT_FOUND"
ErrCodeConflict ErrorCode = "RESOURCE_CONFLICT"
// Internal errors
ErrCodeInternal ErrorCode = "INTERNAL_ERROR"
ErrCodeNotImplemented ErrorCode = "NOT_IMPLEMENTED"
)
// 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
Details map[string]interface{}
Cause error
HTTPStatus int
}
// 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 {
if e.HTTPStatus > 0 {
return e.HTTPStatus
}
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
}
// NewInvalidCredentials creates an AUTH_INVALID_CREDENTIALS error
func NewInvalidCredentials(msg string) *DomainError {
return &DomainError{
Code: ErrCodeInvalidCredentials,
Message: msg,
HTTPStatus: 401,
}
}
// NewConfigNotFound creates a CONFIG_NOT_FOUND error
func NewConfigNotFound(resource string) *DomainError {
return &DomainError{
Code: ErrCodeConfigNotFound,
Message: fmt.Sprintf("configuration not found: %s", resource),
HTTPStatus: 404,
Details: map[string]interface{}{"resource": resource},
}
}
// NewProviderUnavailable creates a PROVIDER_UNAVAILABLE error
func NewProviderUnavailable(provider string, cause error) *DomainError {
return &DomainError{
Code: ErrCodeProviderUnavailable,
Message: fmt.Sprintf("provider %s is unavailable", provider),
HTTPStatus: 503,
Cause: cause,
Details: map[string]interface{}{"provider": provider},
}
}
// NewConfigValidationError creates a CONFIG_VALIDATION_FAILED error
func NewConfigValidationError(field string, msg string) *DomainError {
return &DomainError{
Code: ErrCodeConfigValidation,
Message: fmt.Sprintf("validation failed for %s: %s", field, msg),
HTTPStatus: 400,
Details: map[string]interface{}{"field": field},
}
}
// NewInternalErrorWrapped creates an INTERNAL_ERROR
func NewInternalErrorWrapped(cause error) *DomainError {
return &DomainError{
Code: ErrCodeInternal,
Message: "an internal error occurred",
HTTPStatus: 500,
Cause: cause,
}
}
// 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")
)