Files
hammy-backend/internal/api/errors.go
T
2026-09-06 19:14:59 +02:00

64 lines
2.0 KiB
Go

package api
import (
"encoding/json"
"log/slog"
"net/http"
)
// Error is the single shape every failure takes. Clients get a stable machine
// readable code plus a human message; a self-hosted bot instance can branch on
// the code without parsing prose.
type Error struct {
Code string `json:"code"`
Message string `json:"message"`
}
type errorEnvelope struct {
Error Error `json:"error"`
}
const (
CodeMissingKey = "missing_key"
CodeInvalidKey = "invalid_key"
CodeKeyRevoked = "key_revoked"
CodeKeySuspended = "key_suspended"
CodeKeyExpired = "key_expired"
CodeOwnerBlocked = "owner_blocked"
CodeForbidden = "insufficient_scope"
CodeRateLimited = "rate_limited"
CodeInternal = "internal_error"
)
// WriteError sends a JSON error. Deliberately specific about WHY authentication
// failed rather than a uniform "unauthorized".
//
// The usual argument for being vague is to prevent enumeration, but a key is
// 256 bits from crypto/rand - there is nothing to enumerate. Telling a
// developer their key expired, rather than making them guess, is worth far more
// than the non-existent secrecy gained.
func WriteError(w http.ResponseWriter, status int, code, message string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(errorEnvelope{Error{Code: code, Message: message}}); err != nil {
slog.Error("writing error response", "err", err)
}
}
// writeErrorBody writes just the envelope, for callers that have already sent
// their own status code and headers.
func writeErrorBody(w http.ResponseWriter, code, message string) error {
return json.NewEncoder(w).Encode(errorEnvelope{Error{Code: code, Message: message}})
}
// WriteJSON sends a success payload.
func WriteJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(v); err != nil {
slog.Error("writing response", "err", err)
}
}