Address second review findings F1-F3 for 404, public URL and listen host
Independent review Approved — 0 high findings (#2)
Independent review Approved — 0 high findings (#2)
- F1: return the JSON API error shape for unknown paths (404 not_found) - F2: require a non-empty hostname in MEALQUEST_PUBLIC_URL - F3: validate the MEALQUEST_HTTP_ADDR host as an IP address or hostname Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,8 @@ import (
|
||||
func NewRouter(logger *slog.Logger, version string) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/health", health(logger, version))
|
||||
r.MethodNotAllowed(methodNotAllowed(logger))
|
||||
r.MethodNotAllowed(apiErrorHandler(logger, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed"))
|
||||
r.NotFound(apiErrorHandler(logger, http.StatusNotFound, "not_found", "not found"))
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -28,19 +29,20 @@ type apiError struct {
|
||||
Retryable bool `json:"retryable"`
|
||||
}
|
||||
|
||||
func methodNotAllowed(logger *slog.Logger) http.HandlerFunc {
|
||||
// apiErrorHandler responds with status and a fixed, non-retryable API error.
|
||||
func apiErrorHandler(logger *slog.Logger, status int, code, message string) http.HandlerFunc {
|
||||
body, err := json.Marshal(errorBody{Error: apiError{
|
||||
Code: "method_not_allowed",
|
||||
Message: "method not allowed",
|
||||
Code: code,
|
||||
Message: message,
|
||||
}})
|
||||
if err != nil {
|
||||
panic(err) // fixed string and bool fields cannot fail to marshal
|
||||
}
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
w.WriteHeader(status)
|
||||
if _, err := w.Write(body); err != nil {
|
||||
logger.WarnContext(r.Context(), "write method not allowed response", "error", err)
|
||||
logger.WarnContext(r.Context(), "write error response", "code", code, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,3 +67,22 @@ func TestHealthRejectsOtherMethods(t *testing.T) {
|
||||
t.Errorf("error body = %v, want %v", got.Error, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownPathNotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
rec := httptest.NewRecorder()
|
||||
NewRouter(logger, "dev").ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/unknown", nil))
|
||||
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Errorf("status = %d, want 404", rec.Code)
|
||||
}
|
||||
if ct := rec.Header().Get("Content-Type"); ct != "application/json" {
|
||||
t.Errorf("Content-Type = %q, want application/json", ct)
|
||||
}
|
||||
const want = `{"error":{"code":"not_found","message":"not found","retryable":false}}`
|
||||
if got := rec.Body.String(); got != want {
|
||||
t.Errorf("body = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user