feat(rest,config): wire CORS, real readiness, TLS options, error hygiene
- CORS config is now actually applied to the router (the middleware existed but was never wired); preflight returns 204 for allowed origins and 403 with no CORS headers for disallowed ones. - /readyz runs real dependency checks (queue, auth database) and returns 503 with per-component detail when not ready; exported handlers support dedicated health listeners. - Optional server.tls (cert_file/key_file) for REST and gRPC, validated at config load. - 5xx responses no longer echo internal error details; not-found and already-sent map to 404/409 on cancel/retry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+20
-4
@@ -2,6 +2,7 @@ package rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -160,7 +161,7 @@ func (h *Handler) CancelNotification(w http.ResponseWriter, r *http.Request) {
|
||||
id := vars["id"]
|
||||
|
||||
if err := h.service.CancelNotification(r.Context(), id); err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to cancel notification", err)
|
||||
respondError(w, statusForServiceError(err), "failed to cancel notification", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -177,7 +178,7 @@ func (h *Handler) RetryNotification(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
result, err := h.service.RetryNotification(r.Context(), id)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "failed to retry notification", err)
|
||||
respondError(w, statusForServiceError(err), "failed to retry notification", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -186,6 +187,19 @@ func (h *Handler) RetryNotification(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// statusForServiceError maps service-layer sentinel errors to HTTP status
|
||||
// codes; anything unrecognized is an internal error.
|
||||
func statusForServiceError(err error) int {
|
||||
switch {
|
||||
case errors.Is(err, domain.ErrNotificationNotFound):
|
||||
return http.StatusNotFound
|
||||
case errors.Is(err, domain.ErrNotificationAlreadySent):
|
||||
return http.StatusConflict
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
// GetStats handles GET /api/v1/stats
|
||||
func (h *Handler) GetStats(w http.ResponseWriter, r *http.Request) {
|
||||
stats, err := h.service.GetStats(r.Context())
|
||||
@@ -270,10 +284,12 @@ func respondJSON(w http.ResponseWriter, status int, data interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// respondError sends an error response
|
||||
// respondError sends an error response. Client errors (4xx) include the
|
||||
// underlying detail to help callers fix their request; server errors (5xx)
|
||||
// deliberately do not echo internals — those belong in the server log.
|
||||
func respondError(w http.ResponseWriter, status int, message string, err error) {
|
||||
errMsg := message
|
||||
if err != nil {
|
||||
if err != nil && status < http.StatusInternalServerError {
|
||||
errMsg = message + ": " + err.Error()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user