64 lines
2.0 KiB
Go
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)
|
|
}
|
|
}
|