Skip to content

Commit f79d8ed

Browse files
committed
remove Void
1 parent 369b486 commit f79d8ed

File tree

3 files changed

+39
-46
lines changed

3 files changed

+39
-46
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
gojsonclient
55
============
66

7-
A Go package that provides a client for JSON/REST HTTP services.
7+
A Go package that provides a client for JSON/REST HTTP services, with automatic retry/backoff.
88

99
```go
1010
import "github.com/blizzy78/gojsonclient"

client.go

+23-32
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@ type RetryFunc func(ctx context.Context, httpRes *http.Response, err error) erro
3939

4040
// Request represents a JSON/REST HTTP request.
4141
type Request[Req any, Res any] struct {
42-
uri string
43-
method string
44-
req Req
45-
marshalRequest MarshalJSONFunc[Req]
46-
unmarshalResponse UnmarshalJSONFunc[Res]
42+
uri string
43+
method string
44+
req Req
45+
ignoreResponseBody bool
46+
marshalRequest MarshalJSONFunc[Req]
47+
unmarshalResponse UnmarshalJSONFunc[Res]
4748
}
4849

4950
// RequestOpt is a function that configures a Request.
@@ -58,7 +59,7 @@ type UnmarshalJSONFunc[T any] func(httpRes *http.Response, val *T) error
5859
// Response represents a JSON/REST HTTP response.
5960
type Response[T any] struct {
6061
// Res is the value decoded from the response body.
61-
// Res will be the default value of T if StatusCode==http.StatusNoContent, or if T is Void or *Void.
62+
// Res will be the default value of T if StatusCode==http.StatusNoContent, or if the response body is ignored.
6263
Res T
6364

6465
// StatusCode is the HTTP response status code.
@@ -68,15 +69,12 @@ type Response[T any] struct {
6869
Status string
6970
}
7071

71-
// Void can be used as a request type to indicate that the request has no body,
72-
// or as a response type to indicate that the response has no body.
73-
type Void struct{}
74-
7572
type httpError string
7673

7774
var _ error = httpError("")
7875

7976
// New creates a new Client with the given options.
77+
//
8078
// The default options are: slog.Default() as the logger, http.DefaultClient as the HTTP client,
8179
// request timeout of 30s, maximum number of attempts of 5, gobackoff.New() as the backoff,
8280
// and a retry function that returns an error if the HTTP response status code is http.StatusBadRequest.
@@ -214,18 +212,25 @@ func WithUnmarshalResponseFunc[Req any, Res any](fun UnmarshalJSONFunc[Res]) Req
214212
}
215213
}
216214

217-
// Do executes req and returns the response.
215+
// WithIgnoreResponseBody configures a Request to ignore the response body, regardless of status code.
216+
// The response body will always be ignored if the status code is http.StatusNoContent.
217+
func WithIgnoreResponseBody[Req any, Res any]() RequestOpt[Req, Res] {
218+
return func(req *Request[Req, Res]) {
219+
req.ignoreResponseBody = true
220+
}
221+
}
222+
223+
// Do executes req with client and returns the response.
224+
//
225+
// If the request data is nil, the request will be made without a body.
226+
// If the response status code is http.StatusNoContent or the response body should be ignored,
227+
// Response.Res will be the default value of Res.
218228
//
219229
// If an HTTP request fails, it is retried using backoff according to the retry function, up to the
220230
// maximum number of attempts.
221231
// If the context is canceled, or if the retry function returns a non-nil error, Do stops and returns
222232
// a gobackoff.AbortError.
223233
//
224-
// If Req is Void or *Void, the request will be made without a body.
225-
// If the response status code is http.StatusNoContent, Response.Res will be the default value of Res.
226-
// If Res is Void or *Void, the response body will be ignored, and Response.Res will be the default
227-
// value of Res.
228-
//
229234
// Do is safe to call concurrently with the same Request.
230235
func Do[Req any, Res any](ctx context.Context, client *Client, req *Request[Req, Res]) (*Response[Res], error) {
231236
var res *Response[Res]
@@ -296,11 +301,7 @@ func do[Req any, Res any](ctx context.Context, client *Client, req *Request[Req,
296301
func newHTTPRequest[Req any, Res any](ctx context.Context, client *Client, req *Request[Req, Res]) (*http.Request, error) {
297302
var jsonReqData io.Reader = http.NoBody
298303

299-
switch any(req.req).(type) {
300-
case Void:
301-
case *Void:
302-
303-
default:
304+
if any(req.req) != nil {
304305
buf := bytes.Buffer{}
305306

306307
if err := req.marshalRequest(&buf, req.req); err != nil {
@@ -328,24 +329,14 @@ func newHTTPRequest[Req any, Res any](ctx context.Context, client *Client, req *
328329
}
329330

330331
func response[Req any, Res any](httpRes *http.Response, req *Request[Req, Res]) (*Response[Res], error) {
331-
if httpRes.StatusCode == http.StatusNoContent {
332+
if httpRes.StatusCode == http.StatusNoContent || req.ignoreResponseBody {
332333
return &Response[Res]{
333334
StatusCode: httpRes.StatusCode,
334335
Status: httpRes.Status,
335336
}, nil
336337
}
337338

338339
var jsonRes Res
339-
340-
switch any(jsonRes).(type) {
341-
case Void:
342-
case *Void:
343-
return &Response[Res]{
344-
StatusCode: httpRes.StatusCode,
345-
Status: httpRes.Status,
346-
}, nil
347-
}
348-
349340
if err := req.unmarshalResponse(httpRes, &jsonRes); err != nil {
350341
return nil, fmt.Errorf("decode response: %w", err)
351342
}

client_test.go

+15-13
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func TestDo_Marshal(t *testing.T) {
6969

7070
client := New()
7171

72-
req := NewRequest[*testReq, *testRes](server.URL, http.MethodGet, &reqData,
72+
req := NewRequest(server.URL, http.MethodGet, &reqData,
7373
WithMarshalRequestFunc[*testReq, *testRes](func(writer io.Writer, val *testReq) error {
7474
_, err := writer.Write([]byte(val.Message))
7575
return err //nolint:wrapcheck // we don't add new info here
@@ -98,8 +98,8 @@ func TestDo_Unmarshal(t *testing.T) {
9898

9999
client := New()
100100

101-
req := NewRequest[*testReq, *testRes](server.URL, http.MethodGet, &reqData,
102-
WithUnmarshalResponseFunc[*testReq, *testRes](func(httpRes *http.Response, _ **testRes) error {
101+
req := NewRequest(server.URL, http.MethodGet, &reqData,
102+
WithUnmarshalResponseFunc[*testReq](func(httpRes *http.Response, _ **testRes) error {
103103
data, _ := io.ReadAll(httpRes.Body)
104104
is.Equal(string(data), resData.Reply)
105105

@@ -305,13 +305,13 @@ func TestDo_RetryMaxAttempts(t *testing.T) {
305305
is.Equal(attempts, 5)
306306
}
307307

308-
func TestNewHTTPRequest_Void(t *testing.T) {
308+
func TestNewHTTPRequest_NoBody(t *testing.T) {
309309
is := is.New(t)
310310

311311
client := New()
312312

313-
req := NewRequest[*Void, *Void]("", http.MethodGet, nil,
314-
WithMarshalRequestFunc[*Void, *Void](func(_ io.Writer, _ *Void) error {
313+
req := NewRequest("", http.MethodGet, nil,
314+
WithMarshalRequestFunc[any, any](func(_ io.Writer, _ any) error {
315315
is.Fail()
316316
return nil
317317
}),
@@ -321,11 +321,13 @@ func TestNewHTTPRequest_Void(t *testing.T) {
321321
is.NoErr(err)
322322
}
323323

324-
func TestResponse_Void(t *testing.T) {
324+
func TestResponse_IgnoreBody(t *testing.T) {
325325
is := is.New(t)
326326

327-
req := NewRequest[*Void, *Void]("", http.MethodGet, nil,
328-
WithUnmarshalResponseFunc[*Void, *Void](func(_ *http.Response, _ **Void) error {
327+
req := NewRequest("", http.MethodGet, nil,
328+
WithIgnoreResponseBody[any, any](),
329+
330+
WithUnmarshalResponseFunc[any](func(_ *http.Response, _ *any) error {
329331
is.Fail()
330332
return nil
331333
}),
@@ -337,15 +339,15 @@ func TestResponse_Void(t *testing.T) {
337339
Body: http.NoBody,
338340
}
339341

340-
_, err := response[*Void, *Void](&httpRes, req)
342+
_, err := response(&httpRes, req)
341343
is.NoErr(err)
342344
}
343345

344346
func TestResponse_NoContent(t *testing.T) {
345347
is := is.New(t)
346348

347-
req := NewRequest[*Void, *Void]("", http.MethodGet, nil,
348-
WithUnmarshalResponseFunc[*Void, *Void](func(_ *http.Response, _ **Void) error {
349+
req := NewRequest("", http.MethodGet, nil,
350+
WithUnmarshalResponseFunc[any](func(_ *http.Response, _ *any) error {
349351
is.Fail()
350352
return nil
351353
}),
@@ -357,7 +359,7 @@ func TestResponse_NoContent(t *testing.T) {
357359
Body: http.NoBody,
358360
}
359361

360-
_, err := response[*Void, *Void](&httpRes, req)
362+
_, err := response(&httpRes, req)
361363
is.NoErr(err)
362364
}
363365

0 commit comments

Comments
 (0)