diff --git a/README.md b/README.md index 842d54b..ea67f41 100644 --- a/README.md +++ b/README.md @@ -311,12 +311,15 @@ http.ListenAndServe(":8080", nil) $ curl localhost:8080/pets/update -d '{"name":"Lola"}' {"name":"Lola 3000"} ``` -If you have empty request or response body or you want to ignore them, use `fetch.Empty`: +#### Ignoring request or response +If you have an 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 })) ``` +Alternatively, you can use `fetch.ToHandlerFuncEmptyIn` and `fetch.ToHandlerFuncEmptyOut` functions. +#### Wrappers If you need to access http request attributes wrap the input with `fetch.Request`: ```go type Pet struct { @@ -330,7 +333,7 @@ http.HandleFunc("/pets", fetch.ToHandlerFunc(func(req fetch.Request[Pet]) (*fetc return nil, nil })) ``` -If you have go1.23 and above you can access the wildcards as well. +If you have `go1.23` and above you can access the wildcards as well. ```go http.HandleFunc("GET /pets/{id}", fetch.ToHandlerFunc(func(in fetch.Request[fetch.Empty]) (*fetch.Empty, error) { fmt.Println("id from url:", in.PathValues["id"]) diff --git a/to_handler.go b/to_handler.go index 5699007..75626bd 100644 --- a/to_handler.go +++ b/to_handler.go @@ -49,6 +49,12 @@ func (cfg HandlerConfig) respondError(w http.ResponseWriter, err error) { // In type as a request body and Out type as a response body. type ApplyFunc[In any, Out any] func(in In) (Out, error) +// ConsumeFunc serves as ApplyFunc ignoring HTTP response +type ConsumeFunc[In any] func(in In) error + +// SupplyFunc serves as ApplyFunc ignoring HTTP request +type SupplyFunc[Out any] func() (Out, error) + /* ToHandlerFunc converts ApplyFunc into http.HandlerFunc, which can be used later in http.ServeMux#HandleFunc. @@ -110,6 +116,19 @@ func ToHandlerFunc[In any, Out any](apply ApplyFunc[In, Out]) http.HandlerFunc { } } +func ToHandlerFuncEmptyOut[In any](consume ConsumeFunc[In]) http.HandlerFunc { + return ToHandlerFunc[In, Empty](func(in In) (Empty, error) { + err := consume(in) + return Empty{}, err + }) +} + +func ToHandlerFuncEmptyIn[Out any](supply SupplyFunc[Out]) http.HandlerFunc { + return ToHandlerFunc[Empty, Out](func(_ Empty) (Out, error) { + return supply() + }) +} + func readAndParseBody(r *http.Request, in any) error { reqBody, err := io.ReadAll(r.Body) if err != nil { diff --git a/to_handler_test.go b/to_handler_test.go index 7be32fd..0f607f4 100644 --- a/to_handler_test.go +++ b/to_handler_test.go @@ -16,7 +16,7 @@ func TestToHandlerFunc_EmptyIn(t *testing.T) { assert(t, err, nil) f(mw, r) assert(t, mw.status, 200) - assert(t, string(mw.body), `{"name":"Lola"}`) + assert(t, mw.body, `{"name":"Lola"}`) } func TestToHandlerFunc_EmptyOut(t *testing.T) { @@ -61,11 +61,9 @@ func TestToHandlerFunc_J(t *testing.T) { return M{"status": "ok"}, nil }) mw := newMockWriter() - mux := http.NewServeMux() - mux.HandleFunc("/j", f) r, err := http.NewRequest("POST", "/j", bytes.NewBuffer([]byte(`{"name":"Lola"}`))) assert(t, err, nil) - mux.ServeHTTP(mw, r) + f.ServeHTTP(mw, r) assert(t, mw.status, 200) assert(t, string(mw.body), `{"status":"ok"}`) } @@ -78,12 +76,10 @@ func TestToHandlerFunc_Header(t *testing.T) { return Empty{}, nil }) mw := newMockWriter() - mux := http.NewServeMux() - mux.HandleFunc("/pets", f) r, err := http.NewRequest("POST", "/pets", bytes.NewBuffer([]byte(`{}`))) r.Header.Set("Content", "mycontent") assert(t, err, nil) - mux.ServeHTTP(mw, r) + f.ServeHTTP(mw, r) assert(t, mw.status, 200) } @@ -95,11 +91,9 @@ func TestToHandlerFunc_UrlParameter(t *testing.T) { return Empty{}, nil }) mw := newMockWriter() - mux := http.NewServeMux() - mux.HandleFunc("/pets", f) r, err := http.NewRequest("POST", "/pets?name=Lola", bytes.NewBuffer([]byte(``))) assert(t, err, nil) - mux.ServeHTTP(mw, r) + f.ServeHTTP(mw, r) assert(t, mw.status, 200) } @@ -112,11 +106,9 @@ func TestToHandlerFunc_ParseErrors(t *testing.T) { 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) + f.ServeHTTP(mw, r) if mw.status != 400 || mw.body != `{"error":"parse request body: body is empty"}` { t.Errorf("Wrong writer: %+v", mw) } @@ -129,11 +121,9 @@ func TestToHandlerFunc_Context(t *testing.T) { return Empty{}, nil }) mw := newMockWriter() - mux := http.NewServeMux() - mux.HandleFunc("/pets", f) r, err := http.NewRequest("POST", "/pets", bytes.NewBuffer([]byte(`{"name":"Lola"}`))) assert(t, err, nil) - mux.ServeHTTP(mw, r) + f.ServeHTTP(mw, r) assert(t, mw.status, 200) } @@ -142,11 +132,9 @@ func TestToHandlerFunc_ErrorStatus(t *testing.T) { return nil, &Error{Status: 403} }) mw := newMockWriter() - mux := http.NewServeMux() - mux.HandleFunc("/pets", f) r, err := http.NewRequest("POST", "/pets", bytes.NewBuffer([]byte(`{"name":"Lola"}`))) assert(t, err, nil) - mux.ServeHTTP(mw, r) + f.ServeHTTP(mw, r) assert(t, mw.status, 403) } @@ -158,11 +146,9 @@ func TestToHandlerFunc_Response(t *testing.T) { return Response[*Pet]{Status: 201, Body: &Pet{Name: "Lola"}}, 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) + f.ServeHTTP(mw, r) if mw.status != 201 || string(mw.body) != `{"name":"Lola"}` { t.Errorf("wrong writer: %+v", mw) } @@ -183,11 +169,9 @@ func TestToHandlerFunc_Middleware(t *testing.T) { 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) + f.ServeHTTP(mw, r) assert(t, mw.status, 422) } @@ -215,3 +199,36 @@ func TestToHandlerFunc_ExtractPathValues_GoLess23(t *testing.T) { } } } + +func TestToHandlerFuncEmptyIn(t *testing.T) { + type Pet struct { + Name string + } + f := ToHandlerFuncEmptyIn(func() (Response[*Pet], error) { + return Response[*Pet]{Status: 201, Body: &Pet{Name: "Lola"}}, nil + }) + mw := newMockWriter() + f.ServeHTTP(mw, nil) + if mw.status != 201 || string(mw.body) != `{"name":"Lola"}` { + t.Errorf("wrong writer: %+v", mw) + } +} + +func TestToHandlerFuncEmptyOut(t *testing.T) { + type Pet struct { + Name string + } + f := ToHandlerFuncEmptyOut(func(in Request[Pet]) error { + if in.Body.Name != "Lola" { + t.Fatalf("expected body %+v", in.Body) + } + return nil + }) + mw := newMockWriter() + r, err := http.NewRequest("POST", "/pets", bytes.NewBuffer([]byte(`{"name":"Lola"}`))) + assert(t, err, nil) + f.ServeHTTP(mw, r) + if mw.status != 200 || string(mw.body) != `` { + t.Errorf("wrong writer: %+v", mw) + } +}