From a16859ca8f49f9da2431e6a94b1d91942b49388e Mon Sep 17 00:00:00 2001 From: keitosuwahara Date: Mon, 9 Feb 2026 19:46:10 +0900 Subject: [PATCH 1/3] add/Is_method --- httperror.go | 14 +++++++++++++ httperror_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/httperror.go b/httperror.go index 682cce2a0..68eef29b6 100644 --- a/httperror.go +++ b/httperror.go @@ -73,6 +73,20 @@ func (he *HTTPError) Error() string { return fmt.Sprintf("code=%d, message=%v, err=%v", he.Code, msg, he.err.Error()) } +// Is checks if this error is equal to the target error. +func (he *HTTPError) Is(target error) bool { + if he == target { + return true + } + switch t := target.(type) { + case *HTTPError: + return he.Code == t.Code + case *httpError: + return he.Code == t.code + } + return false +} + // Wrap eturns new HTTPError with given errors wrapped inside func (he HTTPError) Wrap(err error) error { return &HTTPError{ diff --git a/httperror_test.go b/httperror_test.go index 9ae88abcb..d27ac9b0a 100644 --- a/httperror_test.go +++ b/httperror_test.go @@ -65,3 +65,55 @@ func TestNewHTTPError(t *testing.T) { assert.Equal(t, err2, err) } + +func TestHTTPError_Is(t *testing.T) { + var testCases = []struct { + name string + err *HTTPError + target error + expect bool + }{ + { + name: "ok, same instance", + err: &HTTPError{Code: http.StatusNotFound}, + target: &HTTPError{Code: http.StatusNotFound}, + expect: true, + }, + { + name: "ok, different instance, same code", + err: &HTTPError{Code: http.StatusNotFound}, + target: &HTTPError{Code: http.StatusNotFound, Message: "different"}, + expect: true, + }, + { + name: "ok, target is sentinel error", + err: &HTTPError{Code: http.StatusNotFound}, + target: ErrNotFound, + expect: true, + }, + { + name: "nok, different code", + err: &HTTPError{Code: http.StatusNotFound}, + target: &HTTPError{Code: http.StatusInternalServerError}, + expect: false, + }, + { + name: "nok, target is sentinel error with different code", + err: &HTTPError{Code: http.StatusNotFound}, + target: ErrInternalServerError, + expect: false, + }, + { + name: "nok, target is different error type", + err: &HTTPError{Code: http.StatusNotFound}, + target: errors.New("some error"), + expect: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expect, errors.Is(tc.err, tc.target)) + }) + } +} From 9e37c93010459ff3604e4abee70e7249b60231ef Mon Sep 17 00:00:00 2001 From: keitosuwahara Date: Thu, 12 Feb 2026 22:29:13 +0900 Subject: [PATCH 2/3] removed Is method and improved StatusCode method --- httperror.go | 18 +++++++--------- httperror_test.go | 52 +++++++++++++++++++---------------------------- 2 files changed, 28 insertions(+), 42 deletions(-) diff --git a/httperror.go b/httperror.go index 68eef29b6..ed30626f2 100644 --- a/httperror.go +++ b/httperror.go @@ -73,18 +73,14 @@ func (he *HTTPError) Error() string { return fmt.Sprintf("code=%d, message=%v, err=%v", he.Code, msg, he.err.Error()) } -// Is checks if this error is equal to the target error. -func (he *HTTPError) Is(target error) bool { - if he == target { - return true +// HTTPStatusCode returns status code from error if it implements HTTPStatusCoder interface. +// If error does not implement the interface it returns 0. +func HTTPStatusCode(err error) int { + var sc HTTPStatusCoder + if errors.As(err, &sc) { + return sc.StatusCode() } - switch t := target.(type) { - case *HTTPError: - return he.Code == t.Code - case *httpError: - return he.Code == t.code - } - return false + return 0 } // Wrap eturns new HTTPError with given errors wrapped inside diff --git a/httperror_test.go b/httperror_test.go index d27ac9b0a..30bf7813a 100644 --- a/httperror_test.go +++ b/httperror_test.go @@ -5,9 +5,11 @@ package echo import ( "errors" - "github.com/stretchr/testify/assert" + "fmt" "net/http" "testing" + + "github.com/stretchr/testify/assert" ) func TestHTTPError_StatusCode(t *testing.T) { @@ -66,54 +68,42 @@ func TestNewHTTPError(t *testing.T) { assert.Equal(t, err2, err) } -func TestHTTPError_Is(t *testing.T) { +func TestHTTPStatusCode(t *testing.T) { var testCases = []struct { name string - err *HTTPError - target error - expect bool + err error + expect int }{ { - name: "ok, same instance", - err: &HTTPError{Code: http.StatusNotFound}, - target: &HTTPError{Code: http.StatusNotFound}, - expect: true, - }, - { - name: "ok, different instance, same code", + name: "ok, HTTPError", err: &HTTPError{Code: http.StatusNotFound}, - target: &HTTPError{Code: http.StatusNotFound, Message: "different"}, - expect: true, + expect: http.StatusNotFound, }, { - name: "ok, target is sentinel error", - err: &HTTPError{Code: http.StatusNotFound}, - target: ErrNotFound, - expect: true, + name: "ok, sentinel error", + err: ErrNotFound, + expect: http.StatusNotFound, }, { - name: "nok, different code", - err: &HTTPError{Code: http.StatusNotFound}, - target: &HTTPError{Code: http.StatusInternalServerError}, - expect: false, + name: "ok, wrapped HTTPError", + err: fmt.Errorf("wrapped: %w", &HTTPError{Code: http.StatusTeapot}), + expect: http.StatusTeapot, }, { - name: "nok, target is sentinel error with different code", - err: &HTTPError{Code: http.StatusNotFound}, - target: ErrInternalServerError, - expect: false, + name: "nok, normal error", + err: errors.New("error"), + expect: 0, }, { - name: "nok, target is different error type", - err: &HTTPError{Code: http.StatusNotFound}, - target: errors.New("some error"), - expect: false, + name: "nok, nil", + err: nil, + expect: 0, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expect, errors.Is(tc.err, tc.target)) + assert.Equal(t, tc.expect, HTTPStatusCode(tc.err)) }) } } From 4dd9f4641305ef32492e8fbdaacd000bf8c9453e Mon Sep 17 00:00:00 2001 From: keitosuwahara Date: Fri, 13 Feb 2026 00:18:50 +0900 Subject: [PATCH 3/3] rename and moved to HttpStatusCoder below --- httperror.go | 20 ++++++++++---------- httperror_test.go | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/httperror.go b/httperror.go index ed30626f2..6e14da3d9 100644 --- a/httperror.go +++ b/httperror.go @@ -14,6 +14,16 @@ type HTTPStatusCoder interface { StatusCode() int } +// StatusCode returns status code from error if it implements HTTPStatusCoder interface. +// If error does not implement the interface it returns 0. +func StatusCode(err error) int { + var sc HTTPStatusCoder + if errors.As(err, &sc) { + return sc.StatusCode() + } + return 0 +} + // Following errors can produce HTTP status code by implementing HTTPStatusCoder interface var ( ErrBadRequest = &httpError{http.StatusBadRequest} // 400 @@ -73,16 +83,6 @@ func (he *HTTPError) Error() string { return fmt.Sprintf("code=%d, message=%v, err=%v", he.Code, msg, he.err.Error()) } -// HTTPStatusCode returns status code from error if it implements HTTPStatusCoder interface. -// If error does not implement the interface it returns 0. -func HTTPStatusCode(err error) int { - var sc HTTPStatusCoder - if errors.As(err, &sc) { - return sc.StatusCode() - } - return 0 -} - // Wrap eturns new HTTPError with given errors wrapped inside func (he HTTPError) Wrap(err error) error { return &HTTPError{ diff --git a/httperror_test.go b/httperror_test.go index 30bf7813a..0a91bbc9c 100644 --- a/httperror_test.go +++ b/httperror_test.go @@ -68,7 +68,7 @@ func TestNewHTTPError(t *testing.T) { assert.Equal(t, err2, err) } -func TestHTTPStatusCode(t *testing.T) { +func TestStatusCode(t *testing.T) { var testCases = []struct { name string err error @@ -103,7 +103,7 @@ func TestHTTPStatusCode(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expect, HTTPStatusCode(tc.err)) + assert.Equal(t, tc.expect, StatusCode(tc.err)) }) } }