From 1ef27698b93338b2c364b608556df2f59657811d Mon Sep 17 00:00:00 2001 From: Denis Mishankov Date: Mon, 2 Mar 2026 16:23:58 +0300 Subject: [PATCH 1/2] Fix #67: Add WriteJSON helper to httpserver package --- httpserver/response.go | 21 +++++++++ httpserver/response_test.go | 94 +++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 httpserver/response.go create mode 100644 httpserver/response_test.go diff --git a/httpserver/response.go b/httpserver/response.go new file mode 100644 index 0000000..03f8dcc --- /dev/null +++ b/httpserver/response.go @@ -0,0 +1,21 @@ +package httpserver + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// WriteJSON writes a JSON response with the specified status code. +// It sets the Content-Type header to application/json and encodes the data as JSON. +// Returns an error if encoding fails. +func WriteJSON(w http.ResponseWriter, statusCode int, data any) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + if err := json.NewEncoder(w).Encode(data); err != nil { + return fmt.Errorf("failed to encode response to json: %w", err) + } + + return nil +} diff --git a/httpserver/response_test.go b/httpserver/response_test.go new file mode 100644 index 0000000..b78ace1 --- /dev/null +++ b/httpserver/response_test.go @@ -0,0 +1,94 @@ +package httpserver_test + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/platforma-dev/platforma/httpserver" +) + +func TestWriteJSON(t *testing.T) { + t.Parallel() + + t.Run("writes json response with status code", func(t *testing.T) { + t.Parallel() + + data := map[string]string{"message": "hello"} + + w := httptest.NewRecorder() + + err := httpserver.WriteJSON(w, http.StatusCreated, data) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + resp := w.Result() + if resp.StatusCode != http.StatusCreated { + t.Fatalf("expected status code %d, got %d", http.StatusCreated, resp.StatusCode) + } + + contentType := resp.Header.Get("Content-Type") + if contentType != "application/json" { + t.Fatalf("expected Content-Type 'application/json', got %s", contentType) + } + + body, _ := io.ReadAll(resp.Body) + var result map[string]string + if err := json.Unmarshal(body, &result); err != nil { + t.Fatalf("failed to unmarshal response body: %v", err) + } + + if result["message"] != "hello" { + t.Fatalf("expected message 'hello', got %s", result["message"]) + } + }) + + t.Run("writes struct as json", func(t *testing.T) { + t.Parallel() + + type TestStruct struct { + ID int `json:"id"` + Name string `json:"name"` + } + + data := TestStruct{ID: 1, Name: "test"} + + w := httptest.NewRecorder() + + err := httpserver.WriteJSON(w, http.StatusOK, data) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + resp := w.Result() + body, _ := io.ReadAll(resp.Body) + var result TestStruct + if err := json.Unmarshal(body, &result); err != nil { + t.Fatalf("failed to unmarshal response body: %v", err) + } + + if result.ID != 1 || result.Name != "test" { + t.Fatalf("unexpected result: %+v", result) + } + }) + + t.Run("returns error on unencodable data", func(t *testing.T) { + t.Parallel() + + w := httptest.NewRecorder() + + unencodable := make(chan int) + + err := httpserver.WriteJSON(w, http.StatusOK, unencodable) + if err == nil { + t.Fatal("expected error for unencodable data") + } + + if err.Error() == "" { + t.Fatal("expected non-empty error message") + } + }) +} From 8c549152ee3d4246361fcf82027394d85609b894 Mon Sep 17 00:00:00 2001 From: Denis Mishankov Date: Mon, 2 Mar 2026 16:45:02 +0300 Subject: [PATCH 2/2] refactor: use WriteJSON helper in auth and healthcheck handlers Replace manual JSON encoding with httpserver.WriteJSON() helper: - application/healthcheck.go: simplify health response - auth/handler_delete.go: fix error log message (was "decode") - auth/handler_get.go: remove redundant Content-Type header Reduces code duplication and ensures consistent response handling. --- application/healthcheck.go | 10 +++------- auth/handler_delete.go | 7 +++---- auth/handler_get.go | 11 ++++------- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/application/healthcheck.go b/application/healthcheck.go index 0b37659..2a60fba 100644 --- a/application/healthcheck.go +++ b/application/healthcheck.go @@ -2,9 +2,9 @@ package application import ( "context" - "encoding/json" "net/http" + "github.com/platforma-dev/platforma/httpserver" "github.com/platforma-dev/platforma/log" ) @@ -25,11 +25,7 @@ func NewHealthCheckHandler(app healther) *HealthCheckHandler { func (h *HealthCheckHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { health := h.app.Health(r.Context()) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - err := json.NewEncoder(w).Encode(health) - if err != nil { - log.ErrorContext(r.Context(), "failed to encode response to json", "error", err) + if err := httpserver.WriteJSON(w, http.StatusOK, health); err != nil { + log.ErrorContext(r.Context(), "failed to write health response", "error", err) } } diff --git a/auth/handler_delete.go b/auth/handler_delete.go index 2bb53a5..d72082e 100644 --- a/auth/handler_delete.go +++ b/auth/handler_delete.go @@ -2,10 +2,10 @@ package auth import ( "context" - "encoding/json" "errors" "net/http" + "github.com/platforma-dev/platforma/httpserver" "github.com/platforma-dev/platforma/log" ) @@ -41,8 +41,7 @@ func (h *DeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode("User deleted successfully"); err != nil { - log.ErrorContext(ctx, "failed to decode response to json", "error", err) + if err := httpserver.WriteJSON(w, http.StatusOK, "User deleted successfully"); err != nil { + log.ErrorContext(ctx, "failed to write delete response", "error", err) } } diff --git a/auth/handler_get.go b/auth/handler_get.go index 1a742b4..eb6b991 100644 --- a/auth/handler_get.go +++ b/auth/handler_get.go @@ -1,9 +1,9 @@ package auth import ( - "encoding/json" "net/http" + "github.com/platforma-dev/platforma/httpserver" "github.com/platforma-dev/platforma/log" ) @@ -42,16 +42,13 @@ func (h *GetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Content-Type", "application/json") - - var resp = struct { + resp := struct { Username string `json:"username"` }{ Username: user.Username, } - err = json.NewEncoder(w).Encode(resp) - if err != nil { - log.ErrorContext(ctx, "failed to decode response to json", "error", err) + if err := httpserver.WriteJSON(w, http.StatusOK, resp); err != nil { + log.ErrorContext(ctx, "failed to write user response", "error", err) } }