diff --git a/response_builder.go b/response_builder.go index bf69a7a..6513e59 100644 --- a/response_builder.go +++ b/response_builder.go @@ -79,6 +79,18 @@ func (b *ReplyBuilder) Created(w http.ResponseWriter, data any, location string) builder.Write(w) } +// Accepted writes a 202 JSON response using the fluent helper configuration. +func (b *ReplyBuilder) Accepted(w http.ResponseWriter, data any) { + Respond(data).Status(http.StatusAccepted).Meta(b.meta).Headers(b.headers).Write(w) +} + +// NoContent writes a 204 response with no body using the fluent helper configuration. +// Metadata is ignored because a 204 response must not carry a body. +func (b *ReplyBuilder) NoContent(w http.ResponseWriter) { + applyHeaders(w, b.headers) + w.WriteHeader(http.StatusNoContent) +} + // Problem writes a problem response using the fluent helper configuration. func (b *ReplyBuilder) Problem(w http.ResponseWriter, problem *ProblemDetails) { RespondProblem(problem).Headers(b.headers).Write(w) diff --git a/response_helpers.go b/response_helpers.go index f7a3d8c..e6aa0bd 100644 --- a/response_helpers.go +++ b/response_helpers.go @@ -17,6 +17,16 @@ func Created[T any](w http.ResponseWriter, data T, location string) { Reply().Created(w, data, location) } +// Accepted writes a 202 JSON response without metadata. +func Accepted[T any](w http.ResponseWriter, data T) { + Reply().Accepted(w, data) +} + +// NoContent writes a 204 response with no body. +func NoContent(w http.ResponseWriter) { + Reply().NoContent(w) +} + // ProblemResponse writes a problem response using the problem's status. func ProblemResponse(w http.ResponseWriter, problem *ProblemDetails) { if problem == nil { diff --git a/response_helpers_test.go b/response_helpers_test.go index 27d62c2..0cd6610 100644 --- a/response_helpers_test.go +++ b/response_helpers_test.go @@ -36,6 +36,48 @@ func TestResponseHelpers(t *testing.T) { } }) + t.Run("accepted helper", func(t *testing.T) { + w := httptest.NewRecorder() + Accepted(w, testResponse{Key: "value"}) + if w.Code != http.StatusAccepted { + t.Fatalf("expected status %d, got %d", http.StatusAccepted, w.Code) + } + if w.Body.Len() == 0 { + t.Fatal("expected response body") + } + }) + + t.Run("no content helper", func(t *testing.T) { + w := httptest.NewRecorder() + NoContent(w) + if w.Code != http.StatusNoContent { + t.Fatalf("expected status %d, got %d", http.StatusNoContent, w.Code) + } + if w.Body.Len() != 0 { + t.Fatalf("expected empty body, got %q", w.Body.String()) + } + }) + + t.Run("reply builder accepted and no content", func(t *testing.T) { + w := httptest.NewRecorder() + Reply().Header("X-Job", "1").Meta(map[string]int{"n": 1}).Accepted(w, testResponse{Key: "value"}) + if w.Code != http.StatusAccepted || w.Header().Get("X-Job") != "1" { + t.Fatalf("unexpected accepted response: %d %v", w.Code, w.Header()) + } + + w = httptest.NewRecorder() + Reply().Header("X-Job", "2").NoContent(w) + if w.Code != http.StatusNoContent || w.Header().Get("X-Job") != "2" { + t.Fatalf("unexpected no content response: %d %v", w.Code, w.Header()) + } + if w.Body.Len() != 0 { + t.Fatalf("expected empty body, got %q", w.Body.String()) + } + if ct := w.Header().Get("Content-Type"); ct != "" { + t.Fatalf("expected no content type, got %q", ct) + } + }) + t.Run("problem helper", func(t *testing.T) { w := httptest.NewRecorder() ProblemResponse(w, NewNotFoundProblem("user missing"))