295 lines
11 KiB
Go
295 lines
11 KiB
Go
package errors
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
)
|
|
|
|
// Error categories
|
|
const (
|
|
CategoryValidation = "validation"
|
|
CategoryNotFound = "not_found"
|
|
CategoryUnauthorized = "unauthorized"
|
|
CategoryForbidden = "forbidden"
|
|
CategoryConflict = "conflict"
|
|
CategoryRateLimit = "rate_limit"
|
|
CategoryInternal = "internal"
|
|
CategoryExternal = "external"
|
|
CategoryBusiness = "business"
|
|
CategoryTimeout = "timeout"
|
|
CategoryNetwork = "network"
|
|
CategoryDatabase = "database"
|
|
)
|
|
|
|
// Standard error codes
|
|
const (
|
|
// Validation errors
|
|
ErrCodeValidationFailed = "VALIDATION_FAILED"
|
|
ErrCodeInvalidInput = "INVALID_INPUT"
|
|
ErrCodeMissingField = "MISSING_FIELD"
|
|
ErrCodeInvalidFormat = "INVALID_FORMAT"
|
|
ErrCodeInvalidLength = "INVALID_LENGTH"
|
|
ErrCodeInvalidRange = "INVALID_RANGE"
|
|
|
|
// Not found errors
|
|
ErrCodeNotFound = "NOT_FOUND"
|
|
ErrCodeUserNotFound = "USER_NOT_FOUND"
|
|
ErrCodeResourceNotFound = "RESOURCE_NOT_FOUND"
|
|
ErrCodeDataNotFound = "DATA_NOT_FOUND"
|
|
|
|
// Authorization errors
|
|
ErrCodeUnauthorized = "UNAUTHORIZED"
|
|
ErrCodeInvalidToken = "INVALID_TOKEN"
|
|
ErrCodeTokenExpired = "TOKEN_EXPIRED"
|
|
ErrCodeInvalidCredentials = "INVALID_CREDENTIALS"
|
|
|
|
// Forbidden errors
|
|
ErrCodeForbidden = "FORBIDDEN"
|
|
ErrCodeInsufficientRights = "INSUFFICIENT_RIGHTS"
|
|
ErrCodeAccessDenied = "ACCESS_DENIED"
|
|
|
|
// Conflict errors
|
|
ErrCodeConflict = "CONFLICT"
|
|
ErrCodeDuplicateEntry = "DUPLICATE_ENTRY"
|
|
ErrCodeResourceLocked = "RESOURCE_LOCKED"
|
|
ErrCodeConcurrentUpdate = "CONCURRENT_UPDATE"
|
|
|
|
// Rate limit errors
|
|
ErrCodeRateLimitExceeded = "RATE_LIMIT_EXCEEDED"
|
|
ErrCodeTooManyRequests = "TOO_MANY_REQUESTS"
|
|
ErrCodeQuotaExceeded = "QUOTA_EXCEEDED"
|
|
|
|
// Internal errors
|
|
ErrCodeInternalError = "INTERNAL_ERROR"
|
|
ErrCodeUnexpectedError = "UNEXPECTED_ERROR"
|
|
ErrCodeServiceUnavailable = "SERVICE_UNAVAILABLE"
|
|
ErrCodeConfigurationError = "CONFIGURATION_ERROR"
|
|
|
|
// External errors
|
|
ErrCodeExternalError = "EXTERNAL_ERROR"
|
|
ErrCodeThirdPartyError = "THIRD_PARTY_ERROR"
|
|
ErrCodeAPIError = "API_ERROR"
|
|
|
|
// Business errors
|
|
ErrCodeBusinessRule = "BUSINESS_RULE"
|
|
ErrCodeInsufficientBalance = "INSUFFICIENT_BALANCE"
|
|
ErrCodeAccountSuspended = "ACCOUNT_SUSPENDED"
|
|
|
|
// Timeout errors
|
|
ErrCodeTimeout = "TIMEOUT"
|
|
ErrCodeRequestTimeout = "REQUEST_TIMEOUT"
|
|
ErrCodeConnectionTimeout = "CONNECTION_TIMEOUT"
|
|
|
|
// Network errors
|
|
ErrCodeNetworkError = "NETWORK_ERROR"
|
|
ErrCodeConnectionFailed = "CONNECTION_FAILED"
|
|
ErrCodeConnectionLost = "CONNECTION_LOST"
|
|
|
|
// Database errors
|
|
ErrCodeDatabaseError = "DATABASE_ERROR"
|
|
ErrCodeQueryFailed = "QUERY_FAILED"
|
|
ErrCodeTransactionFailed = "TRANSACTION_FAILED"
|
|
)
|
|
|
|
// ErrorInfo contains error metadata
|
|
type ErrorInfo struct {
|
|
Code string
|
|
Category string
|
|
HTTPStatus int
|
|
GRPCCode codes.Code
|
|
}
|
|
|
|
// ErrorTemplate for custom error types
|
|
type ErrorTemplate struct {
|
|
Code string
|
|
Message string
|
|
Category string
|
|
HTTPStatus int
|
|
GRPCCode codes.Code
|
|
Retryable bool
|
|
}
|
|
|
|
var (
|
|
errorCodes = make(map[string]ErrorInfo)
|
|
errorCodesMu sync.RWMutex
|
|
errorTemplates = make(map[string]*ErrorTemplate)
|
|
errorTemplatesMu sync.RWMutex
|
|
)
|
|
|
|
func init() {
|
|
// Initialize standard error codes
|
|
initializeErrorCodes()
|
|
}
|
|
|
|
// initializeErrorCodes sets up standard error codes
|
|
func initializeErrorCodes() {
|
|
// Validation errors
|
|
registerErrorCode(ErrCodeValidationFailed, CategoryValidation, http.StatusBadRequest, codes.InvalidArgument)
|
|
registerErrorCode(ErrCodeInvalidInput, CategoryValidation, http.StatusBadRequest, codes.InvalidArgument)
|
|
registerErrorCode(ErrCodeMissingField, CategoryValidation, http.StatusBadRequest, codes.InvalidArgument)
|
|
registerErrorCode(ErrCodeInvalidFormat, CategoryValidation, http.StatusBadRequest, codes.InvalidArgument)
|
|
registerErrorCode(ErrCodeInvalidLength, CategoryValidation, http.StatusBadRequest, codes.InvalidArgument)
|
|
registerErrorCode(ErrCodeInvalidRange, CategoryValidation, http.StatusBadRequest, codes.InvalidArgument)
|
|
|
|
// Not found errors
|
|
registerErrorCode(ErrCodeNotFound, CategoryNotFound, http.StatusNotFound, codes.NotFound)
|
|
registerErrorCode(ErrCodeUserNotFound, CategoryNotFound, http.StatusNotFound, codes.NotFound)
|
|
registerErrorCode(ErrCodeResourceNotFound, CategoryNotFound, http.StatusNotFound, codes.NotFound)
|
|
registerErrorCode(ErrCodeDataNotFound, CategoryNotFound, http.StatusNotFound, codes.NotFound)
|
|
|
|
// Authorization errors
|
|
registerErrorCode(ErrCodeUnauthorized, CategoryUnauthorized, http.StatusUnauthorized, codes.Unauthenticated)
|
|
registerErrorCode(ErrCodeInvalidToken, CategoryUnauthorized, http.StatusUnauthorized, codes.Unauthenticated)
|
|
registerErrorCode(ErrCodeTokenExpired, CategoryUnauthorized, http.StatusUnauthorized, codes.Unauthenticated)
|
|
registerErrorCode(ErrCodeInvalidCredentials, CategoryUnauthorized, http.StatusUnauthorized, codes.Unauthenticated)
|
|
|
|
// Forbidden errors
|
|
registerErrorCode(ErrCodeForbidden, CategoryForbidden, http.StatusForbidden, codes.PermissionDenied)
|
|
registerErrorCode(ErrCodeInsufficientRights, CategoryForbidden, http.StatusForbidden, codes.PermissionDenied)
|
|
registerErrorCode(ErrCodeAccessDenied, CategoryForbidden, http.StatusForbidden, codes.PermissionDenied)
|
|
|
|
// Conflict errors
|
|
registerErrorCode(ErrCodeConflict, CategoryConflict, http.StatusConflict, codes.AlreadyExists)
|
|
registerErrorCode(ErrCodeDuplicateEntry, CategoryConflict, http.StatusConflict, codes.AlreadyExists)
|
|
registerErrorCode(ErrCodeResourceLocked, CategoryConflict, http.StatusLocked, codes.Aborted)
|
|
registerErrorCode(ErrCodeConcurrentUpdate, CategoryConflict, http.StatusConflict, codes.Aborted)
|
|
|
|
// Rate limit errors
|
|
registerErrorCode(ErrCodeRateLimitExceeded, CategoryRateLimit, http.StatusTooManyRequests, codes.ResourceExhausted)
|
|
registerErrorCode(ErrCodeTooManyRequests, CategoryRateLimit, http.StatusTooManyRequests, codes.ResourceExhausted)
|
|
registerErrorCode(ErrCodeQuotaExceeded, CategoryRateLimit, http.StatusTooManyRequests, codes.ResourceExhausted)
|
|
|
|
// Internal errors
|
|
registerErrorCode(ErrCodeInternalError, CategoryInternal, http.StatusInternalServerError, codes.Internal)
|
|
registerErrorCode(ErrCodeUnexpectedError, CategoryInternal, http.StatusInternalServerError, codes.Internal)
|
|
registerErrorCode(ErrCodeServiceUnavailable, CategoryInternal, http.StatusServiceUnavailable, codes.Unavailable)
|
|
registerErrorCode(ErrCodeConfigurationError, CategoryInternal, http.StatusInternalServerError, codes.Internal)
|
|
|
|
// External errors
|
|
registerErrorCode(ErrCodeExternalError, CategoryExternal, http.StatusBadGateway, codes.Unavailable)
|
|
registerErrorCode(ErrCodeThirdPartyError, CategoryExternal, http.StatusBadGateway, codes.Unavailable)
|
|
registerErrorCode(ErrCodeAPIError, CategoryExternal, http.StatusBadGateway, codes.Unavailable)
|
|
|
|
// Business errors
|
|
registerErrorCode(ErrCodeBusinessRule, CategoryBusiness, http.StatusBadRequest, codes.FailedPrecondition)
|
|
registerErrorCode(ErrCodeInsufficientBalance, CategoryBusiness, http.StatusBadRequest, codes.FailedPrecondition)
|
|
registerErrorCode(ErrCodeAccountSuspended, CategoryBusiness, http.StatusForbidden, codes.PermissionDenied)
|
|
|
|
// Timeout errors
|
|
registerErrorCode(ErrCodeTimeout, CategoryTimeout, http.StatusRequestTimeout, codes.DeadlineExceeded)
|
|
registerErrorCode(ErrCodeRequestTimeout, CategoryTimeout, http.StatusRequestTimeout, codes.DeadlineExceeded)
|
|
registerErrorCode(ErrCodeConnectionTimeout, CategoryTimeout, http.StatusRequestTimeout, codes.DeadlineExceeded)
|
|
|
|
// Network errors
|
|
registerErrorCode(ErrCodeNetworkError, CategoryNetwork, http.StatusBadGateway, codes.Unavailable)
|
|
registerErrorCode(ErrCodeConnectionFailed, CategoryNetwork, http.StatusBadGateway, codes.Unavailable)
|
|
registerErrorCode(ErrCodeConnectionLost, CategoryNetwork, http.StatusBadGateway, codes.Unavailable)
|
|
|
|
// Database errors
|
|
registerErrorCode(ErrCodeDatabaseError, CategoryDatabase, http.StatusInternalServerError, codes.Internal)
|
|
registerErrorCode(ErrCodeQueryFailed, CategoryDatabase, http.StatusInternalServerError, codes.Internal)
|
|
registerErrorCode(ErrCodeTransactionFailed, CategoryDatabase, http.StatusInternalServerError, codes.Aborted)
|
|
}
|
|
|
|
// registerErrorCode registers an error code
|
|
func registerErrorCode(code, category string, httpStatus int, grpcCode codes.Code) {
|
|
errorCodesMu.Lock()
|
|
defer errorCodesMu.Unlock()
|
|
errorCodes[code] = ErrorInfo{
|
|
Code: code,
|
|
Category: category,
|
|
HTTPStatus: httpStatus,
|
|
GRPCCode: grpcCode,
|
|
}
|
|
}
|
|
|
|
// RegisterErrorCode registers a custom error code
|
|
func RegisterErrorCode(code, category string, httpStatus int, grpcCode codes.Code) {
|
|
registerErrorCode(code, category, httpStatus, grpcCode)
|
|
}
|
|
|
|
// GetErrorInfo returns error information for a code
|
|
func GetErrorInfo(code string) ErrorInfo {
|
|
errorCodesMu.RLock()
|
|
defer errorCodesMu.RUnlock()
|
|
|
|
if info, exists := errorCodes[code]; exists {
|
|
return info
|
|
}
|
|
|
|
return ErrorInfo{
|
|
Code: code,
|
|
Category: CategoryInternal,
|
|
HTTPStatus: http.StatusInternalServerError,
|
|
GRPCCode: codes.Internal,
|
|
}
|
|
}
|
|
|
|
// RegisterErrorTemplate registers a custom error template
|
|
func RegisterErrorTemplate(name string, template *ErrorTemplate) {
|
|
errorTemplatesMu.Lock()
|
|
defer errorTemplatesMu.Unlock()
|
|
errorTemplates[name] = template
|
|
}
|
|
|
|
// GetErrorTemplate returns error template by name
|
|
func GetErrorTemplate(name string) *ErrorTemplate {
|
|
errorTemplatesMu.RLock()
|
|
defer errorTemplatesMu.RUnlock()
|
|
return errorTemplates[name]
|
|
}
|
|
|
|
// GetAllErrorCodes returns all registered error codes
|
|
func GetAllErrorCodes() map[string]ErrorInfo {
|
|
errorCodesMu.RLock()
|
|
defer errorCodesMu.RUnlock()
|
|
|
|
result := make(map[string]ErrorInfo)
|
|
for k, v := range errorCodes {
|
|
result[k] = v
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetErrorCodesByCategory returns error codes by category
|
|
func GetErrorCodesByCategory(category string) map[string]ErrorInfo {
|
|
errorCodesMu.RLock()
|
|
defer errorCodesMu.RUnlock()
|
|
|
|
result := make(map[string]ErrorInfo)
|
|
for k, v := range errorCodes {
|
|
if v.Category == category {
|
|
result[k] = v
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// IsRetryable checks if error is retryable
|
|
func IsRetryable(code string) bool {
|
|
info := GetErrorInfo(code)
|
|
switch info.Category {
|
|
case CategoryNetwork, CategoryTimeout, CategoryExternal:
|
|
return true
|
|
case CategoryDatabase:
|
|
return strings.Contains(code, "TIMEOUT") || strings.Contains(code, "CONNECTION")
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// IsClientError checks if error is client-side (4xx)
|
|
func IsClientError(code string) bool {
|
|
info := GetErrorInfo(code)
|
|
return info.HTTPStatus >= 400 && info.HTTPStatus < 500
|
|
}
|
|
|
|
// IsServerError checks if error is server-side (5xx)
|
|
func IsServerError(code string) bool {
|
|
info := GetErrorInfo(code)
|
|
return info.HTTPStatus >= 500
|
|
}
|