From e0b192b3d208f344d7a5c9ec3424fe93d23fef13 Mon Sep 17 00:00:00 2001 From: SystemGlitch Date: Mon, 30 Sep 2024 23:15:37 +0200 Subject: [PATCH] fix: HTTP response error handling #42 (#43) --- resend.go | 22 +++++++--- resend_test.go | 106 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 6 deletions(-) diff --git a/resend.go b/resend.go index 01f21cf..baab835 100644 --- a/resend.go +++ b/resend.go @@ -144,18 +144,28 @@ func handleError(resp *http.Response) error { // Handles errors most likely caused by the client case http.StatusUnprocessableEntity, http.StatusBadRequest: r := &InvalidRequestError{} - err := json.NewDecoder(resp.Body).Decode(r) - if err != nil { - return err + if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") { + err := json.NewDecoder(resp.Body).Decode(r) + if err != nil { + r.Message = resp.Status + } + } else { + r.Message = resp.Status } return errors.New("[ERROR]: " + r.Message) default: // Tries to parse `message` attr from error r := &DefaultError{} - err := json.NewDecoder(resp.Body).Decode(r) - if err != nil { - return err + + if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") { + err := json.NewDecoder(resp.Body).Decode(r) + if err != nil { + r.Message = resp.Status + } + } else { + r.Message = resp.Status } + if r.Message != "" { return errors.New("[ERROR]: " + r.Message) } diff --git a/resend_test.go b/resend_test.go index 6773c24..bbef1a7 100644 --- a/resend_test.go +++ b/resend_test.go @@ -1,8 +1,11 @@ package resend import ( + "bytes" "context" "errors" + "fmt" + "io" "net/http" "testing" @@ -44,3 +47,106 @@ func TestResendRequestShouldReturnErrorIfContextIsCancelled(t *testing.T) { assert.True(t, errors.Unwrap(err) == context.Canceled) assert.Nil(t, res) } + +func TestHandleError(t *testing.T) { + cases := []struct { + desc string + resp *http.Response + want error + }{ + { + desc: "validation_error", + resp: &http.Response{ + StatusCode: http.StatusUnprocessableEntity, + Status: fmt.Sprintf("%d %s", http.StatusUnprocessableEntity, http.StatusText(http.StatusUnprocessableEntity)), + Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, + Body: io.NopCloser(bytes.NewBufferString(`{"message":"Validation error"}`)), + }, + want: errors.New("[ERROR]: Validation error"), + }, + { + desc: "validation_error_no_json", + resp: &http.Response{ + StatusCode: http.StatusUnprocessableEntity, + Status: fmt.Sprintf("%d %s", http.StatusUnprocessableEntity, http.StatusText(http.StatusUnprocessableEntity)), + Body: io.NopCloser(bytes.NewBufferString(`Validation error`)), + }, + want: errors.New("[ERROR]: 422 Unprocessable Entity"), + }, + { + desc: "bad_request", + resp: &http.Response{ + StatusCode: http.StatusBadRequest, + Status: fmt.Sprintf("%d %s", http.StatusBadRequest, http.StatusText(http.StatusBadRequest)), + Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, + Body: io.NopCloser(bytes.NewBufferString(`{"message":"Validation error"}`)), + }, + want: errors.New("[ERROR]: Validation error"), + }, + { + desc: "bad_request_no_json", + resp: &http.Response{ + StatusCode: http.StatusBadRequest, + Status: fmt.Sprintf("%d %s", http.StatusBadRequest, http.StatusText(http.StatusBadRequest)), + Body: io.NopCloser(bytes.NewBufferString(`Validation error`)), + }, + want: errors.New("[ERROR]: 400 Bad Request"), + }, + { + desc: "bad_request_invalid_json", + resp: &http.Response{ + StatusCode: http.StatusBadRequest, + Status: fmt.Sprintf("%d %s", http.StatusBadRequest, http.StatusText(http.StatusBadRequest)), + Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, + Body: io.NopCloser(bytes.NewBufferString(`{`)), + }, + want: errors.New("[ERROR]: 400 Bad Request"), + }, + { + desc: "server_error", + resp: &http.Response{ + StatusCode: http.StatusInternalServerError, + Status: fmt.Sprintf("%d %s", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)), + Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, + Body: io.NopCloser(bytes.NewBufferString(`{"message":"Server error"}`)), + }, + want: errors.New("[ERROR]: Server error"), + }, + { + desc: "server_error_no_json", + resp: &http.Response{ + StatusCode: http.StatusInternalServerError, + Status: fmt.Sprintf("%d %s", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)), + Body: io.NopCloser(bytes.NewBufferString(`Server error`)), + }, + want: errors.New("[ERROR]: 500 Internal Server Error"), + }, + { + desc: "server_error_invalid_json", + resp: &http.Response{ + StatusCode: http.StatusInternalServerError, + Status: fmt.Sprintf("%d %s", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)), + Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, + Body: io.NopCloser(bytes.NewBufferString(`{`)), + }, + want: errors.New("[ERROR]: 500 Internal Server Error"), + }, + { + desc: "server_error_no_message", + resp: &http.Response{ + StatusCode: http.StatusInternalServerError, + Status: fmt.Sprintf("%d %s", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)), + Header: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, + Body: io.NopCloser(bytes.NewBufferString(`{}`)), + }, + want: errors.New("[ERROR]: Unknown Error"), + }, + } + + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + err := handleError(c.resp) + assert.Equal(t, c.want, err) + }) + } +}