Skip to content

Commit

Permalink
body is empty error
Browse files Browse the repository at this point in the history
  • Loading branch information
glossd committed Dec 9, 2024
1 parent b498387 commit 7852776
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 26 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ http.HandleFunc("/pets", fetch.ToHandlerFunc(func(in Pet) (*Pet, error) {
}))
http.ListenAndServe(":8080", nil)
```
If you don't need request or response body, you can use `any` to fit the function signature. However, `fetch.Empty` will completely ignore the request or response body.
If you have empty request or response body or you want to ignore them, use `fetch.Empty`:
```go
http.HandleFunc("/default-pet", fetch.ToHandlerFunc(func(_ fetch.Empty) (Pet, error) {
return Pet{Name: "Teddy"}, nil
Expand Down
7 changes: 5 additions & 2 deletions fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func Do[T any](url string, config ...Config) (T, error) {
err = parseBodyInto(body, resInstance)
if err != nil {
var t T
return t, httpErr("parsing JSON error: ", err, res, body)
return t, httpErr("parse response body: ", err, res, body)
}

valueOf := reflect.Indirect(reflect.ValueOf(&t))
Expand All @@ -206,7 +206,7 @@ func Do[T any](url string, config ...Config) (T, error) {
err = parseBodyInto(body, &t)
if err != nil {
var t T
return t, httpErr("parsing JSON error: ", err, res, body)
return t, httpErr("parse response body: ", err, res, body)
}
return t, nil
}
Expand All @@ -229,6 +229,9 @@ func parseBodyInto(body []byte, v any) error {
rve.SetBytes(body)
return nil
}
if len(body) == 0 {
return fmt.Errorf("body is empty")
}
err := UnmarshalInto(string(body), v)
if err != nil {
return err
Expand Down
20 changes: 10 additions & 10 deletions respond_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import (
type mockWriter struct {
status int
header http.Header
body []byte
body string
}

func newMockWriter() *mockWriter {
return &mockWriter{
status: 0,
header: http.Header{},
body: nil,
body: "",
}
}

Expand All @@ -30,7 +30,7 @@ func (mw *mockWriter) Header() http.Header {
}

func (mw *mockWriter) Write(b []byte) (int, error) {
mw.body = b
mw.body = string(b)
return 0, nil
}

Expand All @@ -52,7 +52,7 @@ func TestRespond_Struct(t *testing.T) {
assert(t, err, nil)
assert(t, mw.status, 200)
assert(t, mw.Header().Get("Content-Type"), "application/json")
assert(t, strings.TrimSpace(string(mw.body)), `{"id":"my-id"}`)
assert(t, strings.TrimSpace(mw.body), `{"id":"my-id"}`)
}

func TestRespond_InvalidJSON(t *testing.T) {
Expand All @@ -64,7 +64,7 @@ func TestRespond_InvalidJSON(t *testing.T) {
assertNotNil(t, err)
assert(t, mw.status, 500)
assert(t, mw.Header().Get("Content-Type"), "application/json")
assert(t, strings.HasPrefix(string(mw.body), `{"error":`), true)
assert(t, strings.HasPrefix(mw.body, `{"error":`), true)
}

func TestRespond_InvalidStatus(t *testing.T) {
Expand All @@ -73,7 +73,7 @@ func TestRespond_InvalidStatus(t *testing.T) {
assertNotNil(t, err)
assert(t, mw.status, 500)
assert(t, mw.Header().Get("Content-Type"), "application/json")
assert(t, strings.HasPrefix(string(mw.body), `{"error":`), true)
assert(t, strings.HasPrefix(mw.body, `{"error":`), true)
}

func TestSetRespondErrorFormat(t *testing.T) {
Expand All @@ -85,7 +85,7 @@ func TestSetRespondErrorFormat(t *testing.T) {
mw := newMockWriter()
Respond(mw, "hello", RespondConfig{Status: 51})
assert(t, mw.header.Get("Content-Type"), "text/plain")
assert(t, string(mw.body), "RespondConfig.Status is invalid")
assert(t, mw.body, "RespondConfig.Status is invalid")
}

func TestSetRespondErrorFormat_InvalidFormats(t *testing.T) {
Expand Down Expand Up @@ -125,8 +125,8 @@ func TestRespondResponse(t *testing.T) {
mw := newMockWriter()
err := Respond(mw, Response[Pet]{Status: 201, Body: Pet{Name: "Lola"}})
assert(t, err, nil)
if mw.status != 201 || string(mw.body) != `{"name":"Lola"}` {
t.Errorf("wrong writer: %+v, %s", mw, string(mw.body))
if mw.status != 201 || mw.body != `{"name":"Lola"}` {
t.Errorf("wrong writer: %+v, %s", mw, mw.body)
}
}

Expand All @@ -136,7 +136,7 @@ func TestRespondError(t *testing.T) {
assert(t, err, nil)
assert(t, mw.status, 400)
assert(t, mw.Header().Get("Content-Type"), "application/json")
assert(t, string(mw.body), `{"error":"wrong"}`)
assert(t, mw.body, `{"error":"wrong"}`)
}

func assert[T comparable](t *testing.T, got, want T) {
Expand Down
26 changes: 14 additions & 12 deletions to_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,33 +71,23 @@ func ToHandlerFunc[In any, Out any](apply ApplyFunc[In, Out]) http.HandlerFunc {
}
resInstance := reflect.New(resType.Type).Interface()
if !isEmptyType(resInstance) {
reqBody, err := io.ReadAll(r.Body)
err := readAndParseBody(r, resInstance)
if err != nil {
cfg.respondError(w, err)
return
}
err = parseBodyInto(reqBody, resInstance)
if err != nil {
cfg.respondError(w, fmt.Errorf("failed to parse request body: %s", err))
return
}
}
valueOf := reflect.Indirect(reflect.ValueOf(&in))
valueOf.FieldByName("PathValues").Set(reflect.ValueOf(extractPathValues(r)))
valueOf.FieldByName("Context").Set(reflect.ValueOf(r.Context()))
valueOf.FieldByName("Headers").Set(reflect.ValueOf(uniqueHeaders(r.Header)))
valueOf.FieldByName("Body").Set(reflect.ValueOf(resInstance).Elem())
} else if !isEmptyType(in) {
reqBody, err := io.ReadAll(r.Body)
err := readAndParseBody(r, &in)
if err != nil {
cfg.respondError(w, err)
return
}
err = parseBodyInto(reqBody, &in)
if err != nil {
cfg.respondError(w, fmt.Errorf("failed to parse request body: %s", err))
return
}
}

out, err := apply(in)
Expand All @@ -119,6 +109,18 @@ func ToHandlerFunc[In any, Out any](apply ApplyFunc[In, Out]) http.HandlerFunc {
}
}

func readAndParseBody(r *http.Request, in any) error {
reqBody, err := io.ReadAll(r.Body)
if err != nil {
return err
}
err = parseBodyInto(reqBody, in)
if err != nil {
return fmt.Errorf("parse request body: %s", err)
}
return nil
}

func extractPathValues(r *http.Request) map[string]string {
if !isGo23AndAbove() || r == nil {
return map[string]string{}
Expand Down
22 changes: 21 additions & 1 deletion to_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestToHandlerFunc_EmptyOut(t *testing.T) {
assert(t, err, nil)
f(mw, r)
assert(t, mw.status, 200)
assert(t, string(mw.body), ``)
assert(t, mw.body, ``)
}

// This test should fail to compile on go1.21 and successfully run on go1.22.
Expand Down Expand Up @@ -87,6 +87,26 @@ func TestToHandlerFunc_Header(t *testing.T) {
assert(t, mw.status, 200)
}

func TestToHandlerFunc_ParseErrors(t *testing.T) {
t.Run("Empty Request Body, Struct with fields", func(t *testing.T) {
type Pet struct {
Name string
}
f := ToHandlerFunc(func(in Request[Pet]) (Empty, error) {
return Empty{}, nil
})
mw := newMockWriter()
mux := http.NewServeMux()
mux.HandleFunc("/pets", f)
r, err := http.NewRequest("POST", "/pets", bytes.NewBuffer([]byte(``)))
assert(t, err, nil)
mux.ServeHTTP(mw, r)
if mw.status != 400 || mw.body != `{"error":"parse request body: body is empty"}` {
t.Errorf("Wrong writer: %+v", mw)
}
})
}

func TestToHandlerFunc_Context(t *testing.T) {
f := ToHandlerFunc(func(in Request[Empty]) (Empty, error) {
assert(t, in.Context.Err(), nil)
Expand Down

0 comments on commit 7852776

Please sign in to comment.