package handlers import ( "encoding/json" "fmt" "net/http" "git.haelnorr.com/h/golib/hws" "github.com/pkg/errors" ) // throwError is a generic helper that all throw* functions use internally func throwError( s *hws.Server, w http.ResponseWriter, r *http.Request, statusCode int, msg string, err error, level string, ) { err = s.ThrowError(w, r, hws.HWSError{ StatusCode: statusCode, Message: msg, Error: err, Level: hws.ErrorLevel(level), RenderErrorPage: true, // throw* family always renders error pages }) if err != nil { s.ThrowFatal(w, err) } } // throwInternalServiceError handles 500 errors (server failures) func throwInternalServiceError( s *hws.Server, w http.ResponseWriter, r *http.Request, msg string, err error, ) { throwError(s, w, r, http.StatusInternalServerError, msg, err, "error") } // throwBadRequest handles 400 errors (malformed requests) func throwBadRequest( s *hws.Server, w http.ResponseWriter, r *http.Request, msg string, err error, ) { throwError(s, w, r, http.StatusBadRequest, msg, err, "debug") } // throwForbidden handles 403 errors (normal permission denials) func throwForbidden( s *hws.Server, w http.ResponseWriter, r *http.Request, msg string, err error, ) { throwError(s, w, r, http.StatusForbidden, msg, err, "debug") } // throwForbiddenSecurity handles 403 errors for security events (uses WARN level) func throwForbiddenSecurity( s *hws.Server, w http.ResponseWriter, r *http.Request, msg string, err error, ) { throwError(s, w, r, http.StatusForbidden, msg, err, "warn") } // throwUnauthorized handles 401 errors (not authenticated) func throwUnauthorized( s *hws.Server, w http.ResponseWriter, r *http.Request, msg string, err error, ) { throwError(s, w, r, http.StatusUnauthorized, msg, err, "debug") } // throwUnauthorizedSecurity handles 401 errors for security events (uses WARN level) func throwUnauthorizedSecurity( s *hws.Server, w http.ResponseWriter, r *http.Request, msg string, err error, ) { throwError(s, w, r, http.StatusUnauthorized, msg, err, "warn") } // throwNotFound handles 404 errors func throwNotFound( s *hws.Server, w http.ResponseWriter, r *http.Request, path string, ) { msg := fmt.Sprintf("The requested resource was not found: %s", path) err := errors.New("Resource not found") throwError(s, w, r, http.StatusNotFound, msg, err, "debug") } // ErrorDetails contains structured error information for WebSocket error modals type ErrorDetails struct { Code int `json:"code"` Stacktrace string `json:"stacktrace"` } // formatErrorDetails extracts and formats error details from wrapped errors func formatErrorDetails(err error) string { if err == nil { return "" } // Use %+v format to get stack trace from github.com/pkg/errors return fmt.Sprintf("%+v", err) } // SerializeErrorDetails creates a JSON string with code and stacktrace // This is exported so it can be used when creating error notifications func SerializeErrorDetails(code int, err error) string { details := ErrorDetails{ Code: code, Stacktrace: formatErrorDetails(err), } jsonData, jsonErr := json.Marshal(details) if jsonErr != nil { // Fallback if JSON encoding fails return fmt.Sprintf(`{"code":%d,"stacktrace":"Failed to serialize error"}`, code) } return string(jsonData) } // parseErrorDetails extracts code and stacktrace from JSON Details field // Returns (code, stacktrace). If parsing fails, returns (500, original details string) func parseErrorDetails(details string) (int, string) { if details == "" { return 500, "" } var errDetails ErrorDetails err := json.Unmarshal([]byte(details), &errDetails) if err != nil { // Not JSON or malformed - treat as plain stacktrace with default code return 500, details } return errDetails.Code, errDetails.Stacktrace }