Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions application/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package application

import (
"context"
"encoding/json"
"net/http"

"github.com/platforma-dev/platforma/httpserver"
"github.com/platforma-dev/platforma/log"
)

Expand All @@ -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)
}
}
7 changes: 3 additions & 4 deletions auth/handler_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
}
}
11 changes: 4 additions & 7 deletions auth/handler_get.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package auth

import (
"encoding/json"
"net/http"

"github.com/platforma-dev/platforma/httpserver"
"github.com/platforma-dev/platforma/log"
)

Expand Down Expand Up @@ -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)
}
}
21 changes: 21 additions & 0 deletions httpserver/response.go
Original file line number Diff line number Diff line change
@@ -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
}
94 changes: 94 additions & 0 deletions httpserver/response_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
})
}