- Add new `app` package to manage application initialization and lifecycle - Refactor `main.go` to use new application management approach - Implement graceful shutdown with context timeout and signal handling - Add dependency injection container initialization - Enhance logging with configurable log levels and structured logging - Update configuration loading and server initialization process - Modify Jitsi configuration in `.env` for custom deployment - Improve error handling and logging throughout application startup - Centralize application startup and shutdown logic in single package Introduces a more robust and flexible application management system with improved initialization, logging, and shutdown capabilities.
236 lines
7.8 KiB
Go
236 lines
7.8 KiB
Go
package errors
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
// ErrorCode represents application-specific error codes
|
|
type ErrorCode string
|
|
|
|
const (
|
|
// Authentication errors
|
|
ErrCodeInvalidCredentials ErrorCode = "INVALID_CREDENTIALS"
|
|
ErrCodeTokenExpired ErrorCode = "TOKEN_EXPIRED"
|
|
ErrCodeTokenInvalid ErrorCode = "TOKEN_INVALID"
|
|
ErrCodeUnauthorized ErrorCode = "UNAUTHORIZED"
|
|
ErrCodeForbidden ErrorCode = "FORBIDDEN"
|
|
|
|
// Validation errors
|
|
ErrCodeValidationFailed ErrorCode = "VALIDATION_FAILED"
|
|
ErrCodeInvalidInput ErrorCode = "INVALID_INPUT"
|
|
ErrCodeMissingField ErrorCode = "MISSING_FIELD"
|
|
|
|
// Resource errors
|
|
ErrCodeNotFound ErrorCode = "NOT_FOUND"
|
|
ErrCodeAlreadyExists ErrorCode = "ALREADY_EXISTS"
|
|
ErrCodeConflict ErrorCode = "CONFLICT"
|
|
|
|
// Business logic errors
|
|
ErrCodeSlotUnavailable ErrorCode = "SLOT_UNAVAILABLE"
|
|
ErrCodeBookingNotFound ErrorCode = "BOOKING_NOT_FOUND"
|
|
ErrCodePaymentFailed ErrorCode = "PAYMENT_FAILED"
|
|
ErrCodePaymentRequired ErrorCode = "PAYMENT_REQUIRED"
|
|
ErrCodeMeetingCreateFailed ErrorCode = "MEETING_CREATE_FAILED"
|
|
|
|
// System errors
|
|
ErrCodeInternalServer ErrorCode = "INTERNAL_SERVER_ERROR"
|
|
ErrCodeDatabaseError ErrorCode = "DATABASE_ERROR"
|
|
ErrCodeExternalAPI ErrorCode = "EXTERNAL_API_ERROR"
|
|
ErrCodeServiceUnavailable ErrorCode = "SERVICE_UNAVAILABLE"
|
|
|
|
// Rate limiting errors
|
|
ErrCodeRateLimitExceeded ErrorCode = "RATE_LIMIT_EXCEEDED"
|
|
ErrCodeTooManyRequests ErrorCode = "TOO_MANY_REQUESTS"
|
|
)
|
|
|
|
// AppError represents an application error with structured information
|
|
type AppError struct {
|
|
Code ErrorCode `json:"code"`
|
|
Message string `json:"message"`
|
|
Details string `json:"details,omitempty"`
|
|
HTTPStatus int `json:"-"`
|
|
Fields map[string]interface{} `json:"fields,omitempty"`
|
|
Cause error `json:"-"`
|
|
}
|
|
|
|
// Error implements the error interface
|
|
func (e *AppError) Error() string {
|
|
if e.Details != "" {
|
|
return fmt.Sprintf("%s: %s - %s", e.Code, e.Message, e.Details)
|
|
}
|
|
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
|
}
|
|
|
|
// Unwrap returns the underlying cause error
|
|
func (e *AppError) Unwrap() error {
|
|
return e.Cause
|
|
}
|
|
|
|
// WithDetails adds details to the error
|
|
func (e *AppError) WithDetails(details string) *AppError {
|
|
e.Details = details
|
|
return e
|
|
}
|
|
|
|
// WithField adds a field to the error
|
|
func (e *AppError) WithField(key string, value interface{}) *AppError {
|
|
if e.Fields == nil {
|
|
e.Fields = make(map[string]interface{})
|
|
}
|
|
e.Fields[key] = value
|
|
return e
|
|
}
|
|
|
|
// WithFields adds multiple fields to the error
|
|
func (e *AppError) WithFields(fields map[string]interface{}) *AppError {
|
|
if e.Fields == nil {
|
|
e.Fields = make(map[string]interface{})
|
|
}
|
|
for k, v := range fields {
|
|
e.Fields[k] = v
|
|
}
|
|
return e
|
|
}
|
|
|
|
// WithCause adds a cause error
|
|
func (e *AppError) WithCause(cause error) *AppError {
|
|
e.Cause = cause
|
|
return e
|
|
}
|
|
|
|
// New creates a new AppError
|
|
func New(code ErrorCode, message string, httpStatus int) *AppError {
|
|
return &AppError{
|
|
Code: code,
|
|
Message: message,
|
|
HTTPStatus: httpStatus,
|
|
}
|
|
}
|
|
|
|
// Wrap wraps an existing error with application error information
|
|
func Wrap(err error, code ErrorCode, message string, httpStatus int) *AppError {
|
|
return &AppError{
|
|
Code: code,
|
|
Message: message,
|
|
HTTPStatus: httpStatus,
|
|
Cause: err,
|
|
}
|
|
}
|
|
|
|
// Predefined common errors
|
|
var (
|
|
// Authentication errors
|
|
ErrInvalidCredentials = New(ErrCodeInvalidCredentials, "Invalid email or password", http.StatusUnauthorized)
|
|
ErrTokenExpired = New(ErrCodeTokenExpired, "Authentication token has expired", http.StatusUnauthorized)
|
|
ErrTokenInvalid = New(ErrCodeTokenInvalid, "Invalid authentication token", http.StatusUnauthorized)
|
|
ErrUnauthorized = New(ErrCodeUnauthorized, "Authentication required", http.StatusUnauthorized)
|
|
ErrForbidden = New(ErrCodeForbidden, "Access denied", http.StatusForbidden)
|
|
|
|
// Validation errors
|
|
ErrValidationFailed = New(ErrCodeValidationFailed, "Request validation failed", http.StatusBadRequest)
|
|
ErrInvalidInput = New(ErrCodeInvalidInput, "Invalid input provided", http.StatusBadRequest)
|
|
ErrMissingField = New(ErrCodeMissingField, "Required field is missing", http.StatusBadRequest)
|
|
|
|
// Resource errors
|
|
ErrNotFound = New(ErrCodeNotFound, "Resource not found", http.StatusNotFound)
|
|
ErrAlreadyExists = New(ErrCodeAlreadyExists, "Resource already exists", http.StatusConflict)
|
|
ErrConflict = New(ErrCodeConflict, "Resource conflict", http.StatusConflict)
|
|
|
|
// Business logic errors
|
|
ErrSlotUnavailable = New(ErrCodeSlotUnavailable, "Selected time slot is not available", http.StatusConflict)
|
|
ErrBookingNotFound = New(ErrCodeBookingNotFound, "Booking not found", http.StatusNotFound)
|
|
ErrPaymentFailed = New(ErrCodePaymentFailed, "Payment processing failed", http.StatusPaymentRequired)
|
|
ErrPaymentRequired = New(ErrCodePaymentRequired, "Payment is required", http.StatusPaymentRequired)
|
|
ErrMeetingCreateFailed = New(ErrCodeMeetingCreateFailed, "Failed to create meeting room", http.StatusInternalServerError)
|
|
|
|
// System errors
|
|
ErrInternalServer = New(ErrCodeInternalServer, "Internal server error", http.StatusInternalServerError)
|
|
ErrDatabaseError = New(ErrCodeDatabaseError, "Database operation failed", http.StatusInternalServerError)
|
|
ErrExternalAPI = New(ErrCodeExternalAPI, "External API error", http.StatusBadGateway)
|
|
ErrServiceUnavailable = New(ErrCodeServiceUnavailable, "Service temporarily unavailable", http.StatusServiceUnavailable)
|
|
|
|
// Rate limiting errors
|
|
ErrRateLimitExceeded = New(ErrCodeRateLimitExceeded, "Rate limit exceeded", http.StatusTooManyRequests)
|
|
ErrTooManyRequests = New(ErrCodeTooManyRequests, "Too many requests", http.StatusTooManyRequests)
|
|
)
|
|
|
|
// IsAppError checks if an error is an AppError
|
|
func IsAppError(err error) bool {
|
|
_, ok := err.(*AppError)
|
|
return ok
|
|
}
|
|
|
|
// GetAppError extracts AppError from error, returns nil if not an AppError
|
|
func GetAppError(err error) *AppError {
|
|
if appErr, ok := err.(*AppError); ok {
|
|
return appErr
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ErrorResponse represents the JSON error response structure
|
|
type ErrorResponse struct {
|
|
Error ErrorInfo `json:"error"`
|
|
}
|
|
|
|
// ErrorInfo contains error information for API responses
|
|
type ErrorInfo struct {
|
|
Code ErrorCode `json:"code"`
|
|
Message string `json:"message"`
|
|
Details string `json:"details,omitempty"`
|
|
Fields map[string]interface{} `json:"fields,omitempty"`
|
|
}
|
|
|
|
// ToErrorResponse converts an AppError to an ErrorResponse
|
|
func (e *AppError) ToErrorResponse() ErrorResponse {
|
|
return ErrorResponse{
|
|
Error: ErrorInfo{
|
|
Code: e.Code,
|
|
Message: e.Message,
|
|
Details: e.Details,
|
|
Fields: e.Fields,
|
|
},
|
|
}
|
|
}
|
|
|
|
// ValidationError represents a field validation error
|
|
type ValidationError struct {
|
|
Field string `json:"field"`
|
|
Message string `json:"message"`
|
|
Value interface{} `json:"value,omitempty"`
|
|
}
|
|
|
|
// ValidationErrors represents multiple validation errors
|
|
type ValidationErrors []ValidationError
|
|
|
|
// Error implements the error interface for ValidationErrors
|
|
func (ve ValidationErrors) Error() string {
|
|
if len(ve) == 0 {
|
|
return "validation failed"
|
|
}
|
|
if len(ve) == 1 {
|
|
return fmt.Sprintf("validation failed: %s %s", ve[0].Field, ve[0].Message)
|
|
}
|
|
return fmt.Sprintf("validation failed: %d errors", len(ve))
|
|
}
|
|
|
|
// ToAppError converts ValidationErrors to AppError
|
|
func (ve ValidationErrors) ToAppError() *AppError {
|
|
fields := make(map[string]interface{})
|
|
for _, err := range ve {
|
|
fields[err.Field] = err.Message
|
|
}
|
|
|
|
return ErrValidationFailed.WithFields(fields).WithDetails(ve.Error())
|
|
}
|
|
|
|
// NewValidationError creates a new validation error
|
|
func NewValidationError(field, message string, value interface{}) ValidationError {
|
|
return ValidationError{
|
|
Field: field,
|
|
Message: message,
|
|
Value: value,
|
|
}
|
|
}
|