-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Unable to pass DB in echo.Context to handler function #2075
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This is example for setting DB into context with middleware: const (
dbContextKey = "__db"
)
type DummyDB struct{}
func (db *DummyDB) QuerySomething() (string, error) {
return "result", nil
}
func dbMiddleware(db *DummyDB) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Set(dbContextKey, db)
return next(c)
}
}
}
func main() {
e := echo.New()
e.Use(middleware.Logger())
db := new(DummyDB)
e.Use(dbMiddleware(db))
e.GET("/", func(c echo.Context) error {
db := c.Get(dbContextKey).(*DummyDB)
result, err := db.QuerySomething()
if err != nil {
return err
}
return c.String(http.StatusOK, result)
})
log.Fatal(e.Start(":8080"))
} Or you could structure your code into "controller" that have db injected to them type DummyDB struct{}
func (db *DummyDB) GetUsernameByID(ctx context.Context, ID int) (string, error) {
return "adam", nil
}
func main() {
e := echo.New()
e.Use(middleware.Logger())
api := e.Group("/api")
db := new(DummyDB)
RegisterRoutes(api, db)
log.Fatal(e.Start(":8080"))
}
// -----------------------------------------------------
// -----------------------------------------------------
// -----------------------------------------------------
// this is located in "users" package. So you would group code by domain
// package users
type db interface { // if you use interface in this package you can easily test handler with mocked db
GetUsernameByID(ctx context.Context, ID int) (string, error)
}
type users struct {
db db
}
func RegisterRoutes(parent *echo.Group, db db) {
u := users{
db: db,
}
g := parent.Group("/users")
// register all routes on controller
g.GET("/:id/username", u.username)
}
func (ctrl *users) username(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid ID")
}
result, err := ctrl.db.GetUsernameByID(c.Request().Context(), id)
if err != nil {
return err
}
return c.String(http.StatusOK, result)
} And test with mocked db type mockDummyDB struct {
mock.Mock
}
func (m *mockDummyDB) GetUsernameByID(ctx context.Context, ID int) (string, error) {
args := m.Called(ctx, ID)
return args.Get(0).(string), args.Error(1)
}
func TestName(t *testing.T) {
var testCases = []struct {
name string
whenID string
resultID int
resultUsername string
resultError error
expect string
expectErr string
}{
{
name: "ok",
whenID: "1",
resultID: 1,
resultUsername: "test",
resultError: nil,
expect: "test",
},
{
name: "nok, db failure",
whenID: "1",
resultID: 1,
resultUsername: "",
resultError: errors.New("db_error"),
expect: "",
expectErr: "db_error",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/does-not-matter-in-this-example", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetParamNames("id")
c.SetParamValues(tc.whenID)
mockedDB := new(mockDummyDB)
mockedDB.On("GetUsernameByID", mock.Anything, tc.resultID).Return(tc.resultUsername, tc.resultError)
users := users{mockedDB}
err := users.username(c)
assert.Equal(t, tc.expect, rec.Body.String())
if tc.expectErr != "" {
assert.EqualError(t, err, tc.expectErr)
} else {
assert.NoError(t, err)
}
mockedDB.AssertExpectations(t)
})
}
} nb: I did not try if it really works. but you should get the idea. |
Can you check the middleware setup for me . Repo is at https://github.com/cyberkryption/gorm-swagger Would it be better to use the controllers option, I also need to refactor routing at some stage. |
controller-like structs have their advantages. You can set your services there instead of DB connection have have better layering in you application. "controller" for your transport layer, service for your domain related logic including necessary objects (repositories or plain db connection) for accessing storage layer. For smaller application this layer may be overkill but for larger application this helps with code organization. I have applications where I have multiple different transport layers - http and messagebus (mqtt) which use same service layer code and do not know anything about storage layer as it is contained in service layer code. |
Would it be ok to close this issue? Just pointing out that we have also https://github.com/labstack/echo/discussions |
Apologies, I forgot to close it. |
Issue Description
Checklist
Expected behaviour
I am new to echo and am building a rest api using swagger. I want to add db to echo.Context so that it is defined for api handlers giving them access to database.
I searched the issues and found #514 which seems to suggest that adding something like e.Context().Set("db", db) in place of the ContxtDB function in main.go might work but this failed as well.
However, I am struggling to understand why the db is undefined. Have I made a silly mistake somewhere that I just cant see?
Actual behaviour
db undefined in licence.go in api package
Steps to reproduce
See below
Working code to debug
Licence.go in licence-server/api.go in api package
Any help appreciated
The text was updated successfully, but these errors were encountered: