httputil depends on xerrors (Tier 0) and valid (Tier 1), placing it at Tier 2. No infrastructure or lifecycle dependencies exist in this module.
84 lines
2.3 KiB
Go
84 lines
2.3 KiB
Go
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
|
|
}
|
|
}
|