From 6fa693156daeeeb5af096cc400689f0c4d1fe816 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Wed, 20 May 2026 14:48:57 +0700 Subject: [PATCH] fix(httpapi): stable problem details for client errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Стабильные detail для 4xx (store, CDN preview, CSV); логирование на сервере. Расширен lint-httpapi для ERR-01 в 4xx. Co-authored-by: Cursor --- internal/httpapi/problem.go | 8 ++++++-- internal/httpapi/routes_crud.go | 15 ++++++++++----- scripts/lint-httpapi.sh | 8 ++++++++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/internal/httpapi/problem.go b/internal/httpapi/problem.go index 50f07cc..12a39e1 100644 --- a/internal/httpapi/problem.go +++ b/internal/httpapi/problem.go @@ -7,8 +7,12 @@ import ( ) const ( - internalErrorDetail = "an internal error occurred" - badGatewayDetail = "upstream request failed" + internalErrorDetail = "an internal error occurred" + badGatewayDetail = "upstream request failed" + notFoundDetail = "resource not found" + invalidInputDetail = "invalid request data" + cdnExtractDetail = "could not extract prefixes from source" + csvInvalidRowDetail = "invalid row in csv file" ) // Problem is RFC 9457 application/problem+json. diff --git a/internal/httpapi/routes_crud.go b/internal/httpapi/routes_crud.go index d304d81..bf911e3 100644 --- a/internal/httpapi/routes_crud.go +++ b/internal/httpapi/routes_crud.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "log" "net/http" "strconv" "strings" @@ -172,12 +173,15 @@ func (s *Server) handleDeleteModule(w http.ResponseWriter, r *http.Request) { } func writeStoreErr(w http.ResponseWriter, err error) { + if err != nil { + log.Printf("httpapi: store: %v", err) + } if err == store.ErrNotFound || err == store.ErrTenantScope { - writeProblem(w, http.StatusNotFound, "Not Found", err.Error()) + writeProblem(w, http.StatusNotFound, "Not Found", notFoundDetail) return } if err == store.ErrInvalidInput { - writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", err.Error()) + writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", invalidInputDetail) return } writeInternalError(w, "store", err) @@ -267,7 +271,8 @@ func (s *Server) handlePreviewCDNSource(w http.ResponseWriter, r *http.Request) } pfxs, err := pipeline.ExtractCIDRs(string(raw), body.SourceKind, body.PrefixPath) if err != nil { - writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", err.Error()) + log.Printf("httpapi: cdn preview extract: %v", err) + writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", cdnExtractDetail) return } items := make([]string, 0, len(pfxs)) @@ -686,8 +691,8 @@ func (s *Server) handleImportModuleEntriesCSV(w http.ResponseWriter, r *http.Req return } if strings.Contains(err.Error(), "importer: line") { - detail := strings.TrimPrefix(err.Error(), "importer: ") - writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", detail) + log.Printf("httpapi: csv import: %v", err) + writeProblem(w, http.StatusUnprocessableEntity, "Unprocessable Entity", csvInvalidRowDetail) return } if strings.Contains(err.Error(), "importer: csv import/export") { diff --git a/scripts/lint-httpapi.sh b/scripts/lint-httpapi.sh index 2ddcef9..dd930cc 100644 --- a/scripts/lint-httpapi.sh +++ b/scripts/lint-httpapi.sh @@ -16,6 +16,14 @@ if grep -rE 'writeProblem\(w, http\.StatusBadGateway.*err\.Error\(\)' "$HTTPAPI" FAIL=1 fi +echo "==> ERR-01: no err.Error() in 4xx writeProblem (store/cdn/csv)" +if grep -rE 'writeProblem\(w, http\.Status(NotFound|UnprocessableEntity|BadRequest).*, err\.Error\(\)' "$HTTPAPI" 2>/dev/null; then + FAIL=1 +fi +if grep -rE 'writeStoreErr.*err\.Error|writeProblem.*Unprocessable.*err\.Error' "$HTTPAPI" 2>/dev/null; then + FAIL=1 +fi + echo "==> ARCH-01: no SQL/pgx queries in httpapi" if grep -rE 'pool\.(Query|Exec|QueryRow)|SELECT |INSERT INTO |UPDATE .* SET |DELETE FROM ' "$HTTPAPI" 2>/dev/null; then FAIL=1