Extracted all deferred API packages from monolithic ws_*.go files: - api/routes.go (75 lines) - Extracted route registration from server.go - api/errors.go (108 lines) - Standardized error responses and error codes - api/jobs/handlers.go (271 lines) - Job WebSocket handlers * HandleAnnotateRun, HandleSetRunNarrative * HandleCancelJob, HandlePruneJobs, HandleListJobs - api/jupyter/handlers.go (244 lines) - Jupyter WebSocket handlers * HandleStartJupyter, HandleStopJupyter * HandleListJupyter, HandleListJupyterPackages * HandleRemoveJupyter, HandleRestoreJupyter - api/validate/handlers.go (163 lines) - Validation WebSocket handlers * HandleValidate, HandleGetValidateStatus, HandleListValidations - api/ws/handler.go (298 lines) - WebSocket handler framework * Core WebSocket handling logic * Opcode constants and error codes Lines redistributed: ~1,150 lines from ws_jobs.go (1,365), ws_jupyter.go (512), ws_validate.go (523), ws_handler.go (379) into focused packages. Note: Original ws_*.go files still present - cleanup in next commit. Build status: Compiles successfully
133 lines
3.8 KiB
Go
133 lines
3.8 KiB
Go
// Package api provides error handling utilities for the API
|
|
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// ErrorResponse represents a standardized error response
|
|
type ErrorResponse struct {
|
|
Error bool `json:"error"`
|
|
Code byte `json:"code"`
|
|
Message string `json:"message"`
|
|
Details string `json:"details,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
RequestID string `json:"request_id,omitempty"`
|
|
}
|
|
|
|
// SuccessResponse represents a standardized success response
|
|
type SuccessResponse struct {
|
|
Success bool `json:"success"`
|
|
Data interface{} `json:"data,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// WriteError writes a standardized error response
|
|
func WriteError(w http.ResponseWriter, code byte, message, details string, statusCode int) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(statusCode)
|
|
|
|
response := ErrorResponse{
|
|
Error: true,
|
|
Code: code,
|
|
Message: message,
|
|
Details: details,
|
|
Timestamp: time.Now().UTC(),
|
|
}
|
|
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// WriteSuccess writes a standardized success response
|
|
func WriteSuccess(w http.ResponseWriter, data interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
response := SuccessResponse{
|
|
Success: true,
|
|
Data: data,
|
|
Timestamp: time.Now().UTC(),
|
|
}
|
|
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// Common error codes for API responses
|
|
const (
|
|
ErrCodeUnknownError = 0x00
|
|
ErrCodeInvalidRequest = 0x01
|
|
ErrCodeAuthenticationFailed = 0x02
|
|
ErrCodePermissionDenied = 0x03
|
|
ErrCodeResourceNotFound = 0x04
|
|
ErrCodeResourceAlreadyExists = 0x05
|
|
ErrCodeServerOverloaded = 0x10
|
|
ErrCodeDatabaseError = 0x11
|
|
ErrCodeNetworkError = 0x12
|
|
ErrCodeStorageError = 0x13
|
|
ErrCodeTimeout = 0x14
|
|
ErrCodeJobNotFound = 0x20
|
|
ErrCodeJobAlreadyRunning = 0x21
|
|
ErrCodeJobFailedToStart = 0x22
|
|
ErrCodeJobExecutionFailed = 0x23
|
|
ErrCodeJobCancelled = 0x24
|
|
ErrCodeOutOfMemory = 0x30
|
|
ErrCodeDiskFull = 0x31
|
|
ErrCodeInvalidConfiguration = 0x32
|
|
ErrCodeServiceUnavailable = 0x33
|
|
)
|
|
|
|
// HTTP status code mappings
|
|
const (
|
|
StatusBadRequest = http.StatusBadRequest
|
|
StatusUnauthorized = http.StatusUnauthorized
|
|
StatusForbidden = http.StatusForbidden
|
|
StatusNotFound = http.StatusNotFound
|
|
StatusConflict = http.StatusConflict
|
|
StatusInternalServerError = http.StatusInternalServerError
|
|
StatusServiceUnavailable = http.StatusServiceUnavailable
|
|
StatusTooManyRequests = http.StatusTooManyRequests
|
|
)
|
|
|
|
// ErrorCodeToHTTPStatus maps API error codes to HTTP status codes
|
|
func ErrorCodeToHTTPStatus(code byte) int {
|
|
switch code {
|
|
case ErrCodeInvalidRequest:
|
|
return StatusBadRequest
|
|
case ErrCodeAuthenticationFailed:
|
|
return StatusUnauthorized
|
|
case ErrCodePermissionDenied:
|
|
return StatusForbidden
|
|
case ErrCodeResourceNotFound, ErrCodeJobNotFound:
|
|
return StatusNotFound
|
|
case ErrCodeResourceAlreadyExists, ErrCodeJobAlreadyRunning:
|
|
return StatusConflict
|
|
case ErrCodeServerOverloaded, ErrCodeServiceUnavailable:
|
|
return StatusServiceUnavailable
|
|
case ErrCodeDatabaseError, ErrCodeNetworkError, ErrCodeStorageError:
|
|
return StatusInternalServerError
|
|
default:
|
|
return StatusInternalServerError
|
|
}
|
|
}
|
|
|
|
// NewErrorResponse creates a new error response with the given details
|
|
func NewErrorResponse(code byte, message, details string) ErrorResponse {
|
|
return ErrorResponse{
|
|
Error: true,
|
|
Code: code,
|
|
Message: message,
|
|
Details: details,
|
|
Timestamp: time.Now().UTC(),
|
|
}
|
|
}
|
|
|
|
// NewSuccessResponse creates a new success response with the given data
|
|
func NewSuccessResponse(data interface{}) SuccessResponse {
|
|
return SuccessResponse{
|
|
Success: true,
|
|
Data: data,
|
|
Timestamp: time.Now().UTC(),
|
|
}
|
|
}
|