first commit
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user