diff --git a/backend/internal/v1/v1_common/helpers.go b/backend/internal/v1/v1_common/helpers.go index bbb539c4..b59006a5 100644 --- a/backend/internal/v1/v1_common/helpers.go +++ b/backend/internal/v1/v1_common/helpers.go @@ -1 +1,49 @@ package v1common + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +/* +Basic helper that response with the basic response json. +To use a default message, pass an empty string. + +Example: + + func handler(c echo.Context) error + // some code... + return success(c, http.StatusOk, "Something") + } +*/ +func success(c echo.Context, code int, message string) error { + if message == "" { + message = http.StatusText(code) + } + return c.JSON(code, basicResponse{Message: message}) +} + +/* +Helper that sets some of fields for the underlying error handler +to properly log/handle the error response. + +If no internal error to pass, use nil. It is recommended to always pass +an error so that the underlying error handler can log the error. + +To use a default public message, pass an empty string. + +Example: + + func handler(c echo.Context) error + // some code... + return fail(c, http.StatusNotFound, "Something not found", err) + } +*/ +func fail(c echo.Context, code int, publicErrMsg string, internalErr error) error { + c.Set("request_error", internalErr) + if publicErrMsg == "" { + publicErrMsg = http.StatusText(code) + } + return echo.NewHTTPError(code, publicErrMsg) +} diff --git a/backend/internal/v1/v1_common/helpers_test.go b/backend/internal/v1/v1_common/helpers_test.go new file mode 100644 index 00000000..4eea1aed --- /dev/null +++ b/backend/internal/v1/v1_common/helpers_test.go @@ -0,0 +1,91 @@ +package v1common + +import ( + "encoding/json" + "errors" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" +) + +func TestRequestResponders(t *testing.T) { + t.Run("Test success responder", func(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + err := success(c, http.StatusOK, "test") + assert.NoError(t, err) + data, err := io.ReadAll(rec.Body) + assert.NoError(t, err) + var resBody basicResponse + err = json.Unmarshal(data, &resBody) + assert.NoError(t, err) + assert.Equal(t, rec.Code, http.StatusOK) + assert.Equal(t, resBody, basicResponse{Message: "test"}) + }) + + t.Run("Test success responder with empty message", func(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + err := success(c, http.StatusOK, "") + assert.NoError(t, err) + data, err := io.ReadAll(rec.Body) + assert.NoError(t, err) + var resBody basicResponse + err = json.Unmarshal(data, &resBody) + assert.NoError(t, err) + assert.Equal(t, rec.Code, http.StatusOK) + assert.Equal(t, resBody, basicResponse{Message: http.StatusText(http.StatusOK)}) + }) + + t.Run("Test fail responder", func(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + testErr := errors.New("test-error") + err, ok := fail(c, http.StatusBadRequest, "test", testErr).(*echo.HTTPError) + assert.True(t, ok) + assert.Equal(t, err.Code, http.StatusBadRequest) + assert.Equal(t, err.Message, "test") + contextErr, ok := c.Get("request_error").(error) + assert.True(t, ok) + assert.Equal(t, contextErr, testErr) + }) + + t.Run("Test fail responder with empty message", func(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + testErr := errors.New("test-error") + err, ok := fail(c, http.StatusBadRequest, "", testErr).(*echo.HTTPError) + assert.True(t, ok) + assert.Equal(t, err.Code, http.StatusBadRequest) + assert.Equal(t, err.Message, http.StatusText(http.StatusBadRequest)) + contextErr, ok := c.Get("request_error").(error) + assert.True(t, ok) + assert.Equal(t, contextErr, testErr) + }) + + t.Run("Test fail responder with nil error", func(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + err, ok := fail(c, http.StatusBadRequest, "", nil).(*echo.HTTPError) + assert.True(t, ok) + assert.Equal(t, err.Code, http.StatusBadRequest) + assert.Equal(t, err.Message, http.StatusText(http.StatusBadRequest)) + contextErr, ok := c.Get("request_error").(error) + assert.False(t, ok) + assert.Equal(t, contextErr, nil) + }) +} diff --git a/backend/internal/v1/v1_common/request.go b/backend/internal/v1/v1_common/request.go new file mode 100644 index 00000000..bbb539c4 --- /dev/null +++ b/backend/internal/v1/v1_common/request.go @@ -0,0 +1 @@ +package v1common diff --git a/backend/internal/v1/v1_common/response.go b/backend/internal/v1/v1_common/response.go index bbb539c4..240416ee 100644 --- a/backend/internal/v1/v1_common/response.go +++ b/backend/internal/v1/v1_common/response.go @@ -1 +1,17 @@ package v1common + +/* +Use this for any json response that just needs a simple message field. +*/ +type basicResponse struct { + Message string `json:"message"` +} + +/* +Use this for any request that resulted in a failure state. +This includes any response that was not a 200 or 300 level codes. +*/ +type basicErrResponse struct { + Message string `json:"message"` + RequestId string `json:"request_id"` +}