From 0a282f0fc2fbe289a89fd9cc0ba94939108fb205 Mon Sep 17 00:00:00 2001 From: Adam Bouqdib Date: Sat, 6 May 2023 07:19:25 +0300 Subject: [PATCH] feat: implement errors interfaces & improve tests (#91) --- .golangci.yml | 1 + errors.go | 11 +++- errors_test.go | 153 +++++++++++++++++++++---------------------------- 3 files changed, 75 insertions(+), 90 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index ff7cf4c..d4d14fa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -51,6 +51,7 @@ issues: - path: (.+)_test.go linters: - errcheck + - errorlint - funlen - goerr113 - gosec diff --git a/errors.go b/errors.go index 38f945b..86a9b55 100644 --- a/errors.go +++ b/errors.go @@ -6,7 +6,6 @@ import ( "encoding" "encoding/xml" "errors" - "net/http" "strconv" "github.com/abemedia/go-don/internal/byteconv" @@ -33,6 +32,14 @@ func (e *HTTPError) Error() string { return e.err.Error() } +func (e *HTTPError) Is(err error) bool { + return errors.Is(e.err, err) || errors.Is(StatusError(e.code), err) +} + +func (e *HTTPError) Unwrap() error { + return e.err +} + func (e *HTTPError) StatusCode() int { if e.code != 0 { return e.code @@ -43,7 +50,7 @@ func (e *HTTPError) StatusCode() int { return sc.StatusCode() } - return http.StatusInternalServerError + return fasthttp.StatusInternalServerError } func (e *HTTPError) MarshalText() ([]byte, error) { diff --git a/errors_test.go b/errors_test.go index efc2a4b..fd81d55 100644 --- a/errors_test.go +++ b/errors_test.go @@ -1,11 +1,9 @@ package don_test import ( - "context" "encoding/xml" "errors" "fmt" - "net/http" "testing" "github.com/abemedia/go-don" @@ -13,103 +11,82 @@ import ( _ "github.com/abemedia/go-don/encoding/text" _ "github.com/abemedia/go-don/encoding/xml" _ "github.com/abemedia/go-don/encoding/yaml" - "github.com/abemedia/go-don/internal/byteconv" - "github.com/abemedia/go-don/pkg/httptest" + "github.com/goccy/go-json" "github.com/google/go-cmp/cmp" + "github.com/valyala/fasthttp" + "gopkg.in/yaml.v3" ) -func TestError(t *testing.T) { - tests := []struct { - err error - mime string - body string - code int - }{ - { - err: don.ErrBadRequest, - mime: "text/plain; charset=utf-8", - body: "Bad Request\n", - code: http.StatusBadRequest, - }, - { - err: don.Error(errors.New("test"), http.StatusBadRequest), - mime: "text/plain; charset=utf-8", - body: "test\n", - code: http.StatusBadRequest, - }, - { - err: errors.New("test"), - mime: "text/plain; charset=utf-8", - body: "test\n", - code: http.StatusInternalServerError, - }, - { - err: errors.New("test"), - mime: "application/json; charset=utf-8", - body: `{"message":"test"}` + "\n", - code: http.StatusInternalServerError, - }, - { - err: errors.New("test"), - mime: "application/x-yaml; charset=utf-8", - body: "message: test\n", - code: http.StatusInternalServerError, - }, - { - err: errors.New("test"), - mime: "application/xml; charset=utf-8", - body: "test", - code: http.StatusInternalServerError, - }, - { - err: &testError{}, - mime: "text/plain; charset=utf-8", - body: "test\n", - code: http.StatusInternalServerError, - }, - { - err: &testError{}, - mime: "application/json; charset=utf-8", - body: `{"custom":"test"}` + "\n", - code: http.StatusInternalServerError, - }, - { - err: &testError{}, - mime: "application/x-yaml; charset=utf-8", - body: "custom: test\n", - code: http.StatusInternalServerError, - }, - { - err: &testError{}, - mime: "application/xml; charset=utf-8", - body: "test", - code: http.StatusInternalServerError, - }, +func TestError_Is(t *testing.T) { + if !errors.Is(don.Error(errTest, 0), errTest) { + t.Error("should match wrapped error") } + if !errors.Is(don.Error(errTest, fasthttp.StatusBadRequest), don.ErrBadRequest) { + t.Error("should match status error") + } +} - for _, tc := range tests { - ctx := httptest.NewRequest("", "/", "", map[string]string{"Accept": tc.mime}) +func TestError_Unwrap(t *testing.T) { + if errors.Unwrap(don.Error(errTest, 0)) != errTest { + t.Error("should unwrap wrapped error") + } +} - api := don.New(nil) - api.Get("/", don.H(func(ctx context.Context, req don.Empty) (any, error) { return nil, tc.err })) - api.RequestHandler()(ctx) +func TestError_StatusCode(t *testing.T) { + if don.Error(don.ErrBadRequest, 0).StatusCode() != fasthttp.StatusBadRequest { + t.Error("should respect wrapped error's status code") + } + if don.Error(don.ErrBadRequest, fasthttp.StatusConflict).StatusCode() != fasthttp.StatusConflict { + t.Error("should ignore wrapped error's status code if explicitly set") + } +} - type response struct { - Code int - Body string - Header map[string]string - } +func TestError_MarshalText(t *testing.T) { + b, _ := don.Error(errTest, 0).MarshalText() + if diff := cmp.Diff("test", string(b)); diff != "" { + t.Error(diff) + } + b, _ = don.Error(&testError{}, 0).MarshalText() + if diff := cmp.Diff("custom", string(b)); diff != "" { + t.Error(diff) + } +} - res := response{ctx.Response.StatusCode(), string(ctx.Response.Body()), map[string]string{}} - ctx.Response.Header.VisitAll(func(key, value []byte) { res.Header[string(key)] = string(value) }) +func TestError_MarshalJSON(t *testing.T) { + b, _ := json.Marshal(don.Error(errTest, 0)) + if diff := cmp.Diff(`{"message":"test"}`, string(b)); diff != "" { + t.Error(diff) + } + b, _ = json.Marshal(don.Error(&testError{}, 0)) + if diff := cmp.Diff(`{"custom":"test"}`, string(b)); diff != "" { + t.Error(diff) + } +} - want := response{tc.code, tc.body, map[string]string{"Content-Type": tc.mime}} - if diff := cmp.Diff(want, res); diff != "" { - t.Error(diff) - } +func TestError_MarshalXML(t *testing.T) { + b, _ := xml.Marshal(don.Error(errTest, 0)) + if diff := cmp.Diff("test", string(b)); diff != "" { + t.Error(diff) + } + b, _ = xml.Marshal(don.Error(&testError{}, 0)) + if diff := cmp.Diff("test", string(b)); diff != "" { + t.Error(diff) } } +func TestError_MarshalYAML(t *testing.T) { + b, _ := yaml.Marshal(don.Error(errTest, 0)) + if diff := cmp.Diff("message: test\n", string(b)); diff != "" { + t.Error(diff) + } + b, _ = yaml.Marshal(don.Error(&testError{}, 0)) + if diff := cmp.Diff("custom: test\n", string(b)); diff != "" { + t.Error(diff) + } +} + +var errTest = errors.New("test") + type testError struct{} func (e *testError) Error() string { @@ -117,7 +94,7 @@ func (e *testError) Error() string { } func (e *testError) MarshalText() ([]byte, error) { - return byteconv.Atob(e.Error()), nil + return []byte("custom"), nil } func (e *testError) MarshalJSON() ([]byte, error) {