// 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") )