Files
cv-site/internal/handlers/errors.go
T
juanatsap dab68f34f2 Initial commit: Go + HTMX CV Site
- Minimal, professional CV design with paper-on-gray layout
- Bilingual support (Spanish/English) with HTMX language switching
- JSON-based content management (cv-en.json, cv-es.json)
- Print-optimized CSS for PDF export
- Responsive design for all devices
- Go backend with stdlib net/http
- Clean, maintainable codebase

Features:
- 18+ years professional experience
- SAP CDC expertise
- Complete project history
- Education, certifications, awards
- Multi-language support

Tech stack: Go, HTMX, vanilla CSS
2025-10-20 08:54:21 +01:00

140 lines
3.2 KiB
Go

package handlers
import (
"encoding/json"
"log"
"net/http"
)
// ErrorResponse represents a structured error response
type ErrorResponse struct {
Error string `json:"error"`
Message string `json:"message,omitempty"`
Code int `json:"code"`
}
// AppError represents an application error with context
type AppError struct {
Err error
Message string
StatusCode int
Internal bool // If true, don't expose details to client
}
// Error implements the error interface
func (e *AppError) Error() string {
if e.Err != nil {
return e.Err.Error()
}
return e.Message
}
// NewAppError creates a new application error
func NewAppError(err error, message string, statusCode int, internal bool) *AppError {
return &AppError{
Err: err,
Message: message,
StatusCode: statusCode,
Internal: internal,
}
}
// HandleError handles errors consistently across the application
func HandleError(w http.ResponseWriter, r *http.Request, err error) {
var appErr *AppError
// Check if it's an AppError
switch e := err.(type) {
case *AppError:
appErr = e
default:
// Unknown error - treat as internal server error
appErr = NewAppError(err, "Internal Server Error", http.StatusInternalServerError, true)
}
// Log the error
if appErr.Internal {
log.Printf("ERROR [%s %s]: %v", r.Method, r.URL.Path, appErr.Err)
} else {
log.Printf("CLIENT ERROR [%s %s]: %s (status: %d)", r.Method, r.URL.Path, appErr.Message, appErr.StatusCode)
}
// Determine response based on Accept header
accept := r.Header.Get("Accept")
isJSON := accept == "application/json"
isHTMX := r.Header.Get("HX-Request") != ""
if isJSON {
// JSON response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(appErr.StatusCode)
response := ErrorResponse{
Error: http.StatusText(appErr.StatusCode),
Code: appErr.StatusCode,
}
// Only include message if not internal
if !appErr.Internal {
response.Message = appErr.Message
}
json.NewEncoder(w).Encode(response)
return
}
if isHTMX {
// HTMX response - return simple HTML fragment
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(appErr.StatusCode)
message := appErr.Message
if appErr.Internal {
message = "An error occurred. Please try again later."
}
w.Write([]byte("<div class='error'>" + message + "</div>"))
return
}
// Standard HTTP error response
message := appErr.Message
if appErr.Internal {
message = "Internal Server Error"
}
http.Error(w, message, appErr.StatusCode)
}
// Common error constructors
func NotFoundError(message string) *AppError {
return NewAppError(nil, message, http.StatusNotFound, false)
}
func BadRequestError(message string) *AppError {
return NewAppError(nil, message, http.StatusBadRequest, false)
}
func InternalError(err error) *AppError {
return NewAppError(err, "Internal Server Error", http.StatusInternalServerError, true)
}
func TemplateError(err error, templateName string) *AppError {
return NewAppError(
err,
"Error rendering template: "+templateName,
http.StatusInternalServerError,
true,
)
}
func DataLoadError(err error, dataType string) *AppError {
return NewAppError(
err,
"Error loading "+dataType+" data",
http.StatusInternalServerError,
true,
)
}