Files
httputil/response.go
Claude Code 8d34a0c715 feat: include platformCode in error responses (v0.10.0) (#1)
- Error(w, err) now extracts PlatformCode() from *xerrors.Err and
  includes "platformCode" in the JSON body when set; omitted otherwise
- errorBody updated to accept platformCode as an explicit parameter
- Upgraded code.nochebuena.dev/go/xerrors dependency to v0.10.0
- Added two new tests: platformCode included when set, omitted when absent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Reviewed-on: #1
Reviewed-by: Rene Nochebuena <rene@noreply.nochebuena.dev>
Co-authored-by: Claude Code <claude@nochebuena.dev>
Co-committed-by: Claude Code <claude@nochebuena.dev>
2026-03-25 16:57:49 -06:00

87 lines
2.4 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, PlatformCode, and Message.
// 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.PlatformCode(), xe.Message(), xe.Fields())
JSON(w, status, body)
return
}
JSON(w, http.StatusInternalServerError, errorBody("INTERNAL", "", "internal server error", nil))
}
func errorBody(code, platformCode, message string, fields map[string]any) map[string]any {
m := map[string]any{
"code": code,
"message": message,
}
if platformCode != "" {
m["platformCode"] = platformCode
}
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
}
}