Tratamento de Erros
A diferença mais visível entre um handler Kori e um handler Chi puro é o tipo de retorno.
// Chi puro
func handler(w http.ResponseWriter, r *http.Request) { ... }
// Kori
func handler(w http.ResponseWriter, r *http.Request) error { ... }Retornando erros
Retorne qualquer error de um handler. O Kori o intercepta e escreve a resposta.
func getUser(w http.ResponseWriter, r *http.Request) error {
user, err := db.Find(chi.URLParam(r, "id"))
if err != nil {
return kori.NotFound("user not found")
}
return kori.JSON(w, http.StatusOK, user)
}As regras são:
| Valor retornado | Comportamento |
|---|---|
nil | Nada — você já escreveu a resposta |
*kori.HTTPError | Resposta JSON com o status code do erro |
Qualquer outro error | 500 Internal Server Error |
Erros HTTP
O Kori fornece construtores para os status codes mais comuns:
return kori.BadRequest("invalid input") // 400
return kori.Unauthorized("missing token") // 401
return kori.Forbidden("insufficient permissions") // 403
return kori.NotFound("user not found") // 404
return kori.Conflict("email already in use") // 409
return kori.UnprocessableEntity("invalid data") // 422
return kori.InternalServerError("unexpected err") // 500Todos aceitam um segundo argumento opcional para detalhes estruturados:
return kori.NotFound("user not found", map[string]string{
"id": chi.URLParam(r, "id"),
})
return kori.NewError(http.StatusTooManyRequests, "rate limit exceeded", map[string]int{
"retry_after": 60,
})Respostas de erro automáticas
Quando um handler retorna um *kori.HTTPError, o handler de erro padrão escreve:
{
"message": "user not found"
}Com detalhes:
{
"message": "rate limit exceeded",
"details": { "retry_after": 60 }
}Erros desconhecidos (qualquer coisa que não seja *kori.HTTPError) produzem:
{
"message": "internal server error"
}Handler de erro customizado
Substitua o handler padrão com kori.SetErrorHandler. Chame uma vez na inicialização, antes de qualquer requisição ser tratada.
kori.SetErrorHandler(func(w http.ResponseWriter, r *http.Request, err error) {
var he *kori.HTTPError
if !errors.As(err, &he) {
slog.Error("unhandled error", "err", err, "path", r.URL.Path)
he = kori.InternalServerError("internal server error")
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(he.Status)
json.NewEncoder(w).Encode(he)
})Um caso de uso comum é o RFC 7807 Problem Details:
kori.SetErrorHandler(func(w http.ResponseWriter, r *http.Request, err error) {
var he *kori.HTTPError
if !errors.As(err, &he) {
he = kori.InternalServerError("internal server error")
}
w.Header().Set("Content-Type", "application/problem+json")
w.WriteHeader(he.Status)
json.NewEncoder(w).Encode(map[string]any{
"type": "https://api.example.com/errors",
"status": he.Status,
"detail": he.Message,
})
})Envolvendo o handler atual
kori.GetErrorHandler retorna o handler em uso no momento. Use-o para adicionar comportamento ao redor do existente — logging, métricas, tracing — em vez de reimplementar o formato da resposta:
base := kori.GetErrorHandler()
kori.SetErrorHandler(func(w http.ResponseWriter, r *http.Request, err error) {
slog.Error("request failed", "err", err, "path", r.URL.Path)
base(w, r, err)
})Capture base antes de chamar SetErrorHandler, não dentro do novo handler — caso contrário ele chamaria a si mesmo.
Tipo HTTPError
type HTTPError struct {
Status int `json:"-"`
Message string `json:"message"`
Details any `json:"details,omitempty"`
}Você pode construir um diretamente para status codes customizados:
return kori.NewError(http.StatusPaymentRequired, "payment required")
return kori.NewError(http.StatusServiceUnavailable, "service unavailable", map[string]string{
"reason": "database unreachable",
})