2026-03-19 13:09:32 +00:00
|
|
|
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.
|
2026-03-25 22:56:39 +00:00
|
|
|
// Understands *xerrors.Err — extracts Code, PlatformCode, and Message.
|
2026-03-19 13:09:32 +00:00
|
|
|
// Falls back to 500 for unknown errors.
|
|
|
|
|
func Error(w http.ResponseWriter, err error) {
|
|
|
|
|
if err == nil {
|
2026-03-25 22:56:39 +00:00
|
|
|
JSON(w, http.StatusInternalServerError, errorBody("INTERNAL", "", "internal server error", nil))
|
2026-03-19 13:09:32 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
var xe *xerrors.Err
|
|
|
|
|
if errors.As(err, &xe) {
|
|
|
|
|
status := errorCodeToStatus(xe.Code())
|
2026-03-25 22:56:39 +00:00
|
|
|
body := errorBody(string(xe.Code()), xe.PlatformCode(), xe.Message(), xe.Fields())
|
2026-03-19 13:09:32 +00:00
|
|
|
JSON(w, status, body)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-03-25 22:56:39 +00:00
|
|
|
JSON(w, http.StatusInternalServerError, errorBody("INTERNAL", "", "internal server error", nil))
|
2026-03-19 13:09:32 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-25 22:56:39 +00:00
|
|
|
func errorBody(code, platformCode, message string, fields map[string]any) map[string]any {
|
2026-03-19 13:09:32 +00:00
|
|
|
m := map[string]any{
|
|
|
|
|
"code": code,
|
|
|
|
|
"message": message,
|
|
|
|
|
}
|
2026-03-25 22:56:39 +00:00
|
|
|
if platformCode != "" {
|
|
|
|
|
m["platformCode"] = platformCode
|
|
|
|
|
}
|
2026-03-19 13:09:32 +00:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|