| |
| |
| |
| package errors |
|
|
| import ( |
| "errors" |
| "fmt" |
| ) |
|
|
| |
| type ErrorCode string |
|
|
| const ( |
| |
| NotFound ErrorCode = "NOT_FOUND" |
| |
| Unauthorized ErrorCode = "UNAUTHORIZED" |
| |
| Forbidden ErrorCode = "FORBIDDEN" |
| |
| InvalidInput ErrorCode = "INVALID_INPUT" |
| |
| Conflict ErrorCode = "CONFLICT" |
| |
| InternalError ErrorCode = "INTERNAL_ERROR" |
| |
| ServiceUnavailable ErrorCode = "SERVICE_UNAVAILABLE" |
| |
| Timeout ErrorCode = "TIMEOUT" |
| |
| ValidationFailed ErrorCode = "VALIDATION_FAILED" |
| |
| AlreadyExists ErrorCode = "ALREADY_EXISTS" |
|
|
| |
| |
| ErrCodeInvalidCredentials ErrorCode = "AUTH_INVALID_CREDENTIALS" |
| ErrCodeTokenExpired ErrorCode = "AUTH_TOKEN_EXPIRED" |
| ErrCodeTokenInvalid ErrorCode = "AUTH_TOKEN_INVALID" |
| ErrCodeUnauthorized ErrorCode = "AUTH_UNAUTHORIZED" |
|
|
| |
| ErrCodeConfigNotFound ErrorCode = "CONFIG_NOT_FOUND" |
| ErrCodeConfigInvalid ErrorCode = "CONFIG_INVALID" |
| ErrCodeConfigValidation ErrorCode = "CONFIG_VALIDATION_FAILED" |
|
|
| |
| ErrCodeProviderNotFound ErrorCode = "PROVIDER_NOT_FOUND" |
| ErrCodeProviderUnavailable ErrorCode = "PROVIDER_UNAVAILABLE" |
| ErrCodeProviderRateLimited ErrorCode = "PROVIDER_RATE_LIMITED" |
|
|
| |
| ErrCodeInvalidRequest ErrorCode = "REQUEST_INVALID" |
| ErrCodeMissingField ErrorCode = "REQUEST_MISSING_FIELD" |
| ErrCodeInvalidFormat ErrorCode = "REQUEST_INVALID_FORMAT" |
|
|
| |
| ErrCodeStorageFailure ErrorCode = "STORAGE_FAILURE" |
| ErrCodeNotFound ErrorCode = "RESOURCE_NOT_FOUND" |
| ErrCodeConflict ErrorCode = "RESOURCE_CONFLICT" |
|
|
| |
| ErrCodeInternal ErrorCode = "INTERNAL_ERROR" |
| ErrCodeNotImplemented ErrorCode = "NOT_IMPLEMENTED" |
| ) |
|
|
| |
| |
| |
| type DomainError struct { |
| Code ErrorCode |
| Message string |
| Details map[string]interface{} |
| Cause error |
| HTTPStatus int |
| } |
|
|
| |
| 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) |
| } |
|
|
| |
| func (e *DomainError) Unwrap() error { |
| return e.Cause |
| } |
|
|
| |
| 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 |
| } |
|
|
| |
| func IsDomainError(err error) (*DomainError, bool) { |
| var domainErr *DomainError |
| if errors.As(err, &domainErr) { |
| return domainErr, true |
| } |
| return nil, false |
| } |
|
|
| |
| func New(code ErrorCode, message string) *DomainError { |
| return &DomainError{ |
| Code: code, |
| Message: message, |
| } |
| } |
|
|
| |
| func Wrap(code ErrorCode, message string, cause error) *DomainError { |
| return &DomainError{ |
| Code: code, |
| Message: message, |
| Cause: cause, |
| } |
| } |
|
|
| |
|
|
| |
| func NewNotFoundError(resource string, identifier string) *DomainError { |
| return &DomainError{ |
| Code: NotFound, |
| Message: fmt.Sprintf("%s not found: %s", resource, identifier), |
| } |
| } |
|
|
| |
| func NewUnauthorizedError(message string) *DomainError { |
| return &DomainError{ |
| Code: Unauthorized, |
| Message: message, |
| } |
| } |
|
|
| |
| func NewForbiddenError(message string) *DomainError { |
| return &DomainError{ |
| Code: Forbidden, |
| Message: message, |
| } |
| } |
|
|
| |
| func NewInvalidInputError(message string) *DomainError { |
| return &DomainError{ |
| Code: InvalidInput, |
| Message: message, |
| } |
| } |
|
|
| |
| 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 |
| } |
|
|
| |
| func NewConflictError(message string) *DomainError { |
| return &DomainError{ |
| Code: Conflict, |
| Message: message, |
| } |
| } |
|
|
| |
| func NewAlreadyExistsError(resource string, identifier string) *DomainError { |
| return &DomainError{ |
| Code: AlreadyExists, |
| Message: fmt.Sprintf("%s already exists: %s", resource, identifier), |
| } |
| } |
|
|
| |
| func NewInternalError(message string, cause error) *DomainError { |
| return &DomainError{ |
| Code: InternalError, |
| Message: message, |
| Cause: cause, |
| } |
| } |
|
|
| |
| func NewServiceUnavailableError(service string) *DomainError { |
| return &DomainError{ |
| Code: ServiceUnavailable, |
| Message: fmt.Sprintf("Service unavailable: %s", service), |
| } |
| } |
|
|
| |
| func NewTimeoutError(operation string) *DomainError { |
| return &DomainError{ |
| Code: Timeout, |
| Message: fmt.Sprintf("Operation timed out: %s", operation), |
| } |
| } |
|
|
| |
| 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 |
| } |
| } |
|
|
| |
| 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 |
| } |
|
|
| |
| func NewInvalidCredentials(msg string) *DomainError { |
| return &DomainError{ |
| Code: ErrCodeInvalidCredentials, |
| Message: msg, |
| HTTPStatus: 401, |
| } |
| } |
|
|
| |
| 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}, |
| } |
| } |
|
|
| |
| 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}, |
| } |
| } |
|
|
| |
| 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}, |
| } |
| } |
|
|
| |
| func NewInternalErrorWrapped(cause error) *DomainError { |
| return &DomainError{ |
| Code: ErrCodeInternal, |
| Message: "an internal error occurred", |
| HTTPStatus: 500, |
| Cause: cause, |
| } |
| } |
|
|
| |
| var ( |
| |
| ErrConfigNotFound = New(NotFound, "configuration not found") |
|
|
| |
| ErrAuthFileNotFound = New(NotFound, "auth file not found") |
|
|
| |
| ErrInvalidConfig = New(ValidationFailed, "invalid configuration") |
|
|
| |
| ErrAuthManagerUnavailable = New(ServiceUnavailable, "auth manager unavailable") |
|
|
| |
| ErrTokenStoreUnavailable = New(ServiceUnavailable, "token store unavailable") |
|
|
| |
| ErrLogDirectoryNotConfigured = New(InternalError, "log directory not configured") |
|
|
| |
| ErrLoggingDisabled = New(ServiceUnavailable, "logging to file disabled") |
| ) |
|
|