From 1e4c665b53de5d37107a4180faa95984bc5d4854 Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Thu, 20 Jul 2023 22:01:58 +0300 Subject: [PATCH] [#2957] added support for wrapped api errors --- CHANGELOG.md | 2 ++ apis/base.go | 22 ++++++------ apis/base_test.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f875e9ce..e7ed2b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,8 @@ - Added `?download` file query parameter option to instruct the browser to always download a file and not show a preview. +- Added support for wrapped API errors (_in case Go 1.20+ is used with multiple wrapped errors, `apis.ApiError` takes precedence_). + ## v0.16.10 diff --git a/apis/base.go b/apis/base.go index fc3b3a4e..37a6df49 100644 --- a/apis/base.go +++ b/apis/base.go @@ -52,6 +52,10 @@ func InitApi(app core.App) (*echo.Echo, error) { // custom error handler e.HTTPErrorHandler = func(c echo.Context, err error) { + if err == nil { + return // no error + } + if c.Response().Committed { if app.IsDebug() { log.Println("HTTPErrorHandler response was already committed:", err) @@ -61,24 +65,22 @@ func InitApi(app core.App) (*echo.Echo, error) { var apiErr *ApiError - switch v := err.(type) { - case *echo.HTTPError: + if errors.As(err, &apiErr) { + if app.IsDebug() && apiErr.RawData() != nil { + log.Println(apiErr.RawData()) + } + } else if v := new(echo.HTTPError); errors.As(err, &v) { if v.Internal != nil && app.IsDebug() { log.Println(v.Internal) } msg := fmt.Sprintf("%v", v.Message) apiErr = NewApiError(v.Code, msg, v) - case *ApiError: - if app.IsDebug() && v.RawData() != nil { - log.Println(v.RawData()) - } - apiErr = v - default: - if err != nil && app.IsDebug() { + } else { + if app.IsDebug() { log.Println(err) } - if err != nil && errors.Is(err, sql.ErrNoRows) { + if errors.Is(err, sql.ErrNoRows) { apiErr = NewNotFoundError("", err) } else { apiErr = NewBadRequestError("", err) diff --git a/apis/base_test.go b/apis/base_test.go index c93e8a9c..d3ed51d4 100644 --- a/apis/base_test.go +++ b/apis/base_test.go @@ -1,6 +1,7 @@ package apis_test import ( + "database/sql" "errors" "fmt" "net/http" @@ -314,3 +315,88 @@ func TestEagerRequestInfoCache(t *testing.T) { scenario.Test(t) } } + +func TestErrorHandler(t *testing.T) { + scenarios := []tests.ApiScenario{ + { + Name: "apis.ApiError", + Method: http.MethodGet, + Url: "/test", + BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { + e.GET("/test", func(c echo.Context) error { + return apis.NewApiError(418, "test", nil) + }) + }, + ExpectedStatus: 418, + ExpectedContent: []string{`"message":"Test."`}, + }, + { + Name: "wrapped apis.ApiError", + Method: http.MethodGet, + Url: "/test", + BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { + e.GET("/test", func(c echo.Context) error { + return fmt.Errorf("example 123: %w", apis.NewApiError(418, "test", nil)) + }) + }, + ExpectedStatus: 418, + ExpectedContent: []string{`"message":"Test."`}, + NotExpectedContent: []string{"example", "123"}, + }, + { + Name: "echo.HTTPError", + Method: http.MethodGet, + Url: "/test", + BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { + e.GET("/test", func(c echo.Context) error { + return echo.NewHTTPError(418, "test") + }) + }, + ExpectedStatus: 418, + ExpectedContent: []string{`"message":"Test."`}, + }, + { + Name: "wrapped echo.HTTPError", + Method: http.MethodGet, + Url: "/test", + BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { + e.GET("/test", func(c echo.Context) error { + return fmt.Errorf("example 123: %w", echo.NewHTTPError(418, "test")) + }) + }, + ExpectedStatus: 418, + ExpectedContent: []string{`"message":"Test."`}, + NotExpectedContent: []string{"example", "123"}, + }, + { + Name: "wrapped sql.ErrNoRows", + Method: http.MethodGet, + Url: "/test", + BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { + e.GET("/test", func(c echo.Context) error { + return fmt.Errorf("example 123: %w", sql.ErrNoRows) + }) + }, + ExpectedStatus: 404, + ExpectedContent: []string{`"data":{}`}, + NotExpectedContent: []string{"example", "123"}, + }, + { + Name: "custom error", + Method: http.MethodGet, + Url: "/test", + BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) { + e.GET("/test", func(c echo.Context) error { + return fmt.Errorf("example 123") + }) + }, + ExpectedStatus: 400, + ExpectedContent: []string{`"data":{}`}, + NotExpectedContent: []string{"example", "123"}, + }, + } + + for _, scenario := range scenarios { + scenario.Test(t) + } +}