From c3195dfe36a0dfbadff0bb4bd6eb6198fef2f3f6 Mon Sep 17 00:00:00 2001 From: Max Beutel Date: Mon, 26 Nov 2018 05:19:08 +0800 Subject: [PATCH] Add ResponseDecoder interface (#49) --- response.go | 22 ++++++++++++++++++++++ sling.go | 49 ++++++++++++++++++++++++++---------------------- sling_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 97 insertions(+), 26 deletions(-) create mode 100644 response.go diff --git a/response.go b/response.go new file mode 100644 index 0000000..68a2fe7 --- /dev/null +++ b/response.go @@ -0,0 +1,22 @@ +package sling + +import ( + "encoding/json" + "net/http" +) + +// ResponseDecoder decodes http responses into struct values. +// Decode decodes the response into the value pointed to by v. +type ResponseDecoder interface { + Decode(resp *http.Response, v interface{}) error +} + +// jsonDecoder decodes http response JSON into a JSON-tagged struct value. +type jsonDecoder struct { +} + +// Decode the Response Body into the value pointed to by v. +// Caller must provide a non-nil v and close the resp.Body. +func (d jsonDecoder) Decode(resp *http.Response, v interface{}) error { + return json.NewDecoder(resp.Body).Decode(v) +} diff --git a/sling.go b/sling.go index c75fd67..fe9c54e 100644 --- a/sling.go +++ b/sling.go @@ -2,7 +2,6 @@ package sling import ( "encoding/base64" - "encoding/json" "io" "net/http" "net/url" @@ -37,15 +36,18 @@ type Sling struct { queryStructs []interface{} // body provider bodyProvider BodyProvider + // response decoder + responseDecoder ResponseDecoder } // New returns a new Sling with an http DefaultClient. func New() *Sling { return &Sling{ - httpClient: http.DefaultClient, - method: "GET", - header: make(http.Header), - queryStructs: make([]interface{}, 0), + httpClient: http.DefaultClient, + method: "GET", + header: make(http.Header), + queryStructs: make([]interface{}, 0), + responseDecoder: jsonDecoder{}, } } @@ -68,12 +70,13 @@ func (s *Sling) New() *Sling { headerCopy[k] = v } return &Sling{ - httpClient: s.httpClient, - method: s.method, - rawURL: s.rawURL, - header: headerCopy, - queryStructs: append([]interface{}{}, s.queryStructs...), - bodyProvider: s.bodyProvider, + httpClient: s.httpClient, + method: s.method, + rawURL: s.rawURL, + header: headerCopy, + queryStructs: append([]interface{}{}, s.queryStructs...), + bodyProvider: s.bodyProvider, + responseDecoder: s.responseDecoder, } } @@ -336,6 +339,15 @@ func addHeaders(req *http.Request, header http.Header) { // Sending +// ResponseDecoder sets the Sling's response decoder. +func (s *Sling) ResponseDecoder(decoder ResponseDecoder) *Sling { + if decoder == nil { + return s + } + s.responseDecoder = decoder + return s +} + // ReceiveSuccess creates a new HTTP request and returns the response. Success // responses (2XX) are JSON decoded into the value pointed to by successV. // Any error creating the request, sending it, or decoding a 2XX response @@ -377,7 +389,7 @@ func (s *Sling) Do(req *http.Request, successV, failureV interface{}) (*http.Res // Decode from json if successV != nil || failureV != nil { - err = decodeResponseJSON(resp, successV, failureV) + err = decodeResponse(resp, s.responseDecoder, successV, failureV) } return resp, err } @@ -387,22 +399,15 @@ func (s *Sling) Do(req *http.Request, successV, failureV interface{}) (*http.Res // otherwise. If the successV or failureV argument to decode into is nil, // decoding is skipped. // Caller is responsible for closing the resp.Body. -func decodeResponseJSON(resp *http.Response, successV, failureV interface{}) error { +func decodeResponse(resp *http.Response, decoder ResponseDecoder, successV, failureV interface{}) error { if code := resp.StatusCode; 200 <= code && code <= 299 { if successV != nil { - return decodeResponseBodyJSON(resp, successV) + return decoder.Decode(resp, successV) } } else { if failureV != nil { - return decodeResponseBodyJSON(resp, failureV) + return decoder.Decode(resp, failureV) } } return nil } - -// decodeResponseBodyJSON JSON decodes a Response Body into the value pointed -// to by v. -// Caller must provide a non-nil v and close the resp.Body. -func decodeResponseBodyJSON(resp *http.Response, v interface{}) error { - return json.NewDecoder(resp.Body).Decode(v) -} diff --git a/sling_test.go b/sling_test.go index ca80334..5fa70fc 100644 --- a/sling_test.go +++ b/sling_test.go @@ -2,6 +2,7 @@ package sling import ( "bytes" + "encoding/xml" "errors" "fmt" "io" @@ -28,15 +29,22 @@ var paramsA = struct { } var paramsB = FakeParams{KindName: "recent", Count: 25} -// Json-tagged model struct +// Json/XML-tagged model struct type FakeModel struct { - Text string `json:"text,omitempty"` - FavoriteCount int64 `json:"favorite_count,omitempty"` - Temperature float64 `json:"temperature,omitempty"` + Text string `json:"text,omitempty" xml:"text"` + FavoriteCount int64 `json:"favorite_count,omitempty" xml:"favorite_count"` + Temperature float64 `json:"temperature,omitempty" xml:"temperature"` } var modelA = FakeModel{Text: "note", FavoriteCount: 12} +// Non-Json response decoder +type xmlResponseDecoder struct{} + +func (d xmlResponseDecoder) Decode(resp *http.Response, v interface{}) error { + return xml.NewDecoder(resp.Body).Decode(v) +} + func TestNew(t *testing.T) { sling := New() if sling.httpClient != http.DefaultClient { @@ -717,6 +725,42 @@ func TestDo_onFailureWithNilValue(t *testing.T) { } } +func TestReceive_success_nonDefaultDecoder(t *testing.T) { + client, mux, server := testServer() + defer server.Close() + mux.HandleFunc("/foo/submit", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/xml") + data := ` + Some text + 24 + 10.5 + ` + fmt.Fprintf(w, xml.Header) + fmt.Fprintf(w, data) + }) + + endpoint := New().Client(client).Base("http://example.com/").Path("foo/").Post("submit") + + model := new(FakeModel) + apiError := new(APIError) + resp, err := endpoint.New().ResponseDecoder(xmlResponseDecoder{}).Receive(model, apiError) + + if err != nil { + t.Errorf("expected nil, got %v", err) + } + if resp.StatusCode != 200 { + t.Errorf("expected %d, got %d", 200, resp.StatusCode) + } + expectedModel := &FakeModel{Text: "Some text", FavoriteCount: 24, Temperature: 10.5} + if !reflect.DeepEqual(expectedModel, model) { + t.Errorf("expected %v, got %v", expectedModel, model) + } + expectedAPIError := &APIError{} + if !reflect.DeepEqual(expectedAPIError, apiError) { + t.Errorf("failureV should be zero valued, exepcted %v, got %v", expectedAPIError, apiError) + } +} + func TestReceive_success(t *testing.T) { client, mux, server := testServer() defer server.Close()