// Package errors provides centralized error handling for the API package errors import ( "encoding/json" "fmt" "net/http" "github.com/gorilla/websocket" ) // Error codes - centralized to ensure consistency across all API handlers const ( CodeUnknownError = "UNKNOWN_ERROR" CodeInvalidRequest = "INVALID_REQUEST" CodeAuthenticationFailed = "AUTHENTICATION_FAILED" CodePermissionDenied = "PERMISSION_DENIED" CodeResourceNotFound = "RESOURCE_NOT_FOUND" CodeResourceAlreadyExists = "RESOURCE_ALREADY_EXISTS" CodeServerOverloaded = "SERVER_OVERLOADED" CodeDatabaseError = "DATABASE_ERROR" CodeNetworkError = "NETWORK_ERROR" CodeStorageError = "STORAGE_ERROR" CodeTimeout = "TIMEOUT" CodeJobNotFound = "JOB_NOT_FOUND" CodeJobAlreadyRunning = "JOB_ALREADY_RUNNING" CodeJobFailedToStart = "JOB_FAILED_TO_START" CodeJobExecutionFailed = "JOB_EXECUTION_FAILED" CodeJobCancelled = "JOB_CANCELLED" CodeOutOfMemory = "OUT_OF_MEMORY" CodeDiskFull = "DISK_FULL" CodeInvalidConfiguration = "INVALID_CONFIGURATION" CodeServiceUnavailable = "SERVICE_UNAVAILABLE" CodeBadRequest = "BAD_REQUEST" CodeForbidden = "FORBIDDEN" CodeNotFound = "NOT_FOUND" ) // ErrorResponse represents a standardized error response type ErrorResponse struct { ErrorMsg string `json:"error"` Code string `json:"code"` Message string `json:"message,omitempty"` Details string `json:"details,omitempty"` } // NewErrorResponse creates a new error response func NewErrorResponse(code, message, details string) ErrorResponse { return ErrorResponse{ ErrorMsg: "true", Code: code, Message: message, Details: details, } } // Error implements the error interface func (e ErrorResponse) 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) } // WriteHTTPError writes an error response to an HTTP ResponseWriter func WriteHTTPError(w http.ResponseWriter, statusCode int, errCode, message, details string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) resp := NewErrorResponse(errCode, message, details) _ = json.NewEncoder(w).Encode(resp) } // WriteHTTPErrorFromError writes an error response from an error func WriteHTTPErrorFromError(w http.ResponseWriter, statusCode int, err error) { if errResp, ok := err.(ErrorResponse); ok { WriteHTTPError(w, statusCode, errResp.Code, errResp.Message, errResp.Details) return } WriteHTTPError(w, statusCode, CodeUnknownError, err.Error(), "") } // SendErrorPacket sends an error packet over WebSocket func SendErrorPacket(conn *websocket.Conn, code, message, details string) error { resp := NewErrorResponse(code, message, details) return conn.WriteJSON(resp) } // SendSuccessPacket sends a success packet over WebSocket // Automatically adds "success": true to the response func SendSuccessPacket(conn *websocket.Conn, data map[string]any) error { response := make(map[string]any, len(data)+1) response["success"] = true for k, v := range data { response[k] = v } return conn.WriteJSON(response) } // Common error responses as typed errors for use with errors.Is/errors.As var ( ErrNotFound = NewErrorResponse(CodeNotFound, "resource not found", "") ErrPermissionDenied = NewErrorResponse(CodePermissionDenied, "permission denied", "") ErrInvalidRequest = NewErrorResponse(CodeInvalidRequest, "invalid request", "") ErrServiceUnavailable = NewErrorResponse(CodeServiceUnavailable, "service unavailable", "") ErrServerOverloaded = NewErrorResponse(CodeServerOverloaded, "server overloaded", "") ) // HTTPStatusFromCode maps error codes to HTTP status codes func HTTPStatusFromCode(code string) int { switch code { case CodeInvalidRequest, CodeBadRequest: return http.StatusBadRequest case CodeAuthenticationFailed: return http.StatusUnauthorized case CodePermissionDenied, CodeForbidden: return http.StatusForbidden case CodeResourceNotFound, CodeNotFound, CodeJobNotFound: return http.StatusNotFound case CodeResourceAlreadyExists: return http.StatusConflict case CodeServerOverloaded, CodeServiceUnavailable: return http.StatusServiceUnavailable case CodeTimeout: return http.StatusRequestTimeout default: return http.StatusInternalServerError } }