package httputil import ( "encoding/json" "errors" "net/http" "code.nochebuena.dev/go/xerrors" ) // JSON encodes v as JSON and writes it with the given status code. // Sets Content-Type: application/json. func JSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } // NoContent writes a 204 No Content response. func NoContent(w http.ResponseWriter) { w.WriteHeader(http.StatusNoContent) } // Error maps err to the appropriate HTTP status code and writes a JSON error body. // Understands *xerrors.Err — extracts Code and Message; fields are included if present. // Falls back to 500 for unknown errors. func Error(w http.ResponseWriter, err error) { if err == nil { JSON(w, http.StatusInternalServerError, errorBody("INTERNAL", "internal server error", nil)) return } var xe *xerrors.Err if errors.As(err, &xe) { status := errorCodeToStatus(xe.Code()) body := errorBody(string(xe.Code()), xe.Message(), xe.Fields()) JSON(w, status, body) return } JSON(w, http.StatusInternalServerError, errorBody("INTERNAL", "internal server error", nil)) } func errorBody(code, message string, fields map[string]any) map[string]any { m := map[string]any{ "code": code, "message": message, } for k, v := range fields { m[k] = v } return m } // errorCodeToStatus maps a xerrors.Code to an HTTP status code. func errorCodeToStatus(code xerrors.Code) int { switch code { case xerrors.ErrInvalidInput: return http.StatusBadRequest case xerrors.ErrUnauthorized: return http.StatusUnauthorized case xerrors.ErrPermissionDenied: return http.StatusForbidden case xerrors.ErrNotFound: return http.StatusNotFound case xerrors.ErrAlreadyExists: return http.StatusConflict case xerrors.ErrGone: return http.StatusGone case xerrors.ErrPreconditionFailed: return http.StatusPreconditionFailed case xerrors.ErrRateLimited: return http.StatusTooManyRequests case xerrors.ErrInternal: return http.StatusInternalServerError case xerrors.ErrNotImplemented: return http.StatusNotImplemented case xerrors.ErrUnavailable: return http.StatusServiceUnavailable case xerrors.ErrDeadlineExceeded: return http.StatusGatewayTimeout default: return http.StatusInternalServerError } }