-
I am new to Golang and Echo. I really appreciate your ideas/suggestions to improve the code with below aspects:
|
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 5 replies
-
You do not need so detailed error handling in handlers. You could just Here is how defaultErrorHandler is implemented: Line 352 in 1ac4a8f p.s. if you swallow all errors in handler your middleware are not aware that error occurred. |
Beta Was this translation helpful? Give feedback.
-
This is my personal opinion and may not be shared by others. using binding payload directly to object is asking for problems. For example if you are using JSON and introduce new (public) field in your struct that is not meant to be unmarshaled to you could have attacker providing that field and therefore filling it with unwanted data. This is probably overkill for small applications but I tend to create controllerlike structs that take dependencies from constructor so I do not need to use global instances and therefore can test/mock stuff more easily For example for HTTP layer type HTTPHandlers struct {
db DB // in my own code a service for that domain.
}
func RegisterPromoAreaHandlers(parentRoute *echo.Group, db DB) *HTTPHandlers {
h := &HTTPHandlers{db: db}
promo := parentRoute.Group("/promo")
promo.POST("", h.createPromotionalArea)
//promo.GET("/:id", h.getPromotionalArea)
return h
}
func (h *HTTPHandlers) createPromotionalArea(c echo.Context) error {
payload := PromoArea{} // could/should be different type of struct so transport layer can be different from business domain
if err := c.Bind(&payload); err != nil {
return err
}
promo, err := h.db.StorePromoArea(
c.Request().Context(),
PromoArea{
// explicitly map fields so no newly added field would slip through
Name: payload.Name,
},
)
if err != nil {
return err
}
return c.JSON(http.StatusCreated, promo) // could/should be different type of struct so transport layer can be different from business domain
} Database or service you pass in type DB interface {
StorePromoArea(ctx context.Context, p PromoArea) (PromoArea, error)
} and simple main for api structure func main() {
e := echo.New()
db := &db{}
// connect to db
api := e.Group("/api")
RegisterPromoAreaHandlers(api, db)
// register other handlers for other domains
if err := e.Start(":8080"); err != http.ErrServerClosed {
log.Fatal(err)
}
} and your handler test can be something like that with mocking service/db layer import (
"context"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
type dbMock struct {
mock.Mock
}
func (m *dbMock) StorePromoArea(ctx context.Context, p PromoArea) (PromoArea, error) {
args := m.Called(ctx, p)
return args.Get(0).(PromoArea), args.Error(1)
}
func TestHTTPHandlers_createPromotionalArea(t *testing.T) {
var testCases = []struct {
name string
whenPayload string
dBWhen PromoArea
dBReturn PromoArea
dBError error
expect string
expectError string
}{
{
name: "ok, happy case",
whenPayload: `{"ID": 1, "name": "test"}`, // ID could be sent
dBWhen: PromoArea{Name: "test"}, // but not be sent to db
dBReturn: PromoArea{ID: 999, Name: "test"},
expect: `{"ID":999, "Name":"test"}`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
db := new(dbMock)
db.On("StorePromoArea", mock.Anything, tc.dBWhen).Return(tc.dBReturn, tc.dBError)
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tc.whenPayload))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
e := echo.New()
c := e.NewContext(req, rec)
h := HTTPHandlers{db: db}
err := h.createPromotionalArea(c)
assert.JSONEq(t, tc.expect, string(rec.Body.Bytes()))
if tc.expectError != "" {
assert.EqualError(t, err, tc.expectError)
} else {
assert.NoError(t, err)
}
})
}
} |
Beta Was this translation helpful? Give feedback.
This is my personal opinion and may not be shared by others.
using binding payload directly to object is asking for problems. For example if you are using JSON and introduce new (public) field in your struct that is not meant to be unmarshaled to you could have attacker providing that field and therefore filling it with unwanted data.
This is probably overkill for small applications but I tend to create controllerlike structs that take dependencies from constructor so I do not need to use global instances and therefore can test/mock stuff more easily
For example for HTTP layer