added error event hooks

This commit is contained in:
Gani Georgiev 2022-12-02 16:36:15 +02:00
parent 23fbfab63a
commit 02f72638b8
7 changed files with 70 additions and 9 deletions

View File

@ -6,6 +6,8 @@
```go ```go
app.OnBeforeBootstrap() app.OnBeforeBootstrap()
app.OnAfterBootstrap() app.OnAfterBootstrap()
app.OnBeforeApiError()
app.OnAfterApiError()
app.OnRealtimeDisconnectRequest() app.OnRealtimeDisconnectRequest()
app.OnRealtimeBeforeMessageSend() app.OnRealtimeBeforeMessageSend()
app.OnRealtimeAfterMessageSend() app.OnRealtimeAfterMessageSend()

View File

@ -64,19 +64,27 @@ func InitApi(app core.App) (*echo.Echo, error) {
apiErr = NewBadRequestError("", err) apiErr = NewBadRequestError("", err)
} }
// Send response event := &core.ApiErrorEvent{
var cErr error HttpContext: c,
if c.Request().Method == http.MethodHead { Error: apiErr,
// @see https://github.com/labstack/echo/issues/608
cErr = c.NoContent(apiErr.Code)
} else {
cErr = c.JSON(apiErr.Code, apiErr)
} }
hookErr := app.OnBeforeApiError().Trigger(event, func(e *core.ApiErrorEvent) error {
// Send response
if e.HttpContext.Request().Method == http.MethodHead {
// @see https://github.com/labstack/echo/issues/608
return e.HttpContext.NoContent(apiErr.Code)
}
return e.HttpContext.JSON(apiErr.Code, apiErr)
})
// truly rare case; eg. client already disconnected // truly rare case; eg. client already disconnected
if cErr != nil && app.IsDebug() { if hookErr != nil && app.IsDebug() {
log.Println(cErr) log.Println(hookErr)
} }
app.OnAfterApiError().Trigger(event)
} }
// admin ui routes // admin ui routes

View File

@ -92,6 +92,16 @@ type App interface {
// allowing you to adjust its options and attach new routes. // allowing you to adjust its options and attach new routes.
OnBeforeServe() *hook.Hook[*ServeEvent] OnBeforeServe() *hook.Hook[*ServeEvent]
// OnBeforeApiError hook is triggered right before sending an error API
// response to the client, allowing you to further modify the error data
// or to return a completely different API response (using [hook.StopPropagation]).
OnBeforeApiError() *hook.Hook[*ApiErrorEvent]
// OnAfterApiError hook is triggered right after sending an error API
// response to the client.
// It could be used to log the final API error in external services.
OnAfterApiError() *hook.Hook[*ApiErrorEvent]
// --------------------------------------------------------------- // ---------------------------------------------------------------
// Dao event hooks // Dao event hooks
// --------------------------------------------------------------- // ---------------------------------------------------------------

View File

@ -43,6 +43,8 @@ type BaseApp struct {
onBeforeBootstrap *hook.Hook[*BootstrapEvent] onBeforeBootstrap *hook.Hook[*BootstrapEvent]
onAfterBootstrap *hook.Hook[*BootstrapEvent] onAfterBootstrap *hook.Hook[*BootstrapEvent]
onBeforeServe *hook.Hook[*ServeEvent] onBeforeServe *hook.Hook[*ServeEvent]
onBeforeApiError *hook.Hook[*ApiErrorEvent]
onAfterApiError *hook.Hook[*ApiErrorEvent]
// dao event hooks // dao event hooks
onModelBeforeCreate *hook.Hook[*ModelEvent] onModelBeforeCreate *hook.Hook[*ModelEvent]
@ -135,6 +137,8 @@ func NewBaseApp(dataDir string, encryptionEnv string, isDebug bool) *BaseApp {
onBeforeBootstrap: &hook.Hook[*BootstrapEvent]{}, onBeforeBootstrap: &hook.Hook[*BootstrapEvent]{},
onAfterBootstrap: &hook.Hook[*BootstrapEvent]{}, onAfterBootstrap: &hook.Hook[*BootstrapEvent]{},
onBeforeServe: &hook.Hook[*ServeEvent]{}, onBeforeServe: &hook.Hook[*ServeEvent]{},
onBeforeApiError: &hook.Hook[*ApiErrorEvent]{},
onAfterApiError: &hook.Hook[*ApiErrorEvent]{},
// dao event hooks // dao event hooks
onModelBeforeCreate: &hook.Hook[*ModelEvent]{}, onModelBeforeCreate: &hook.Hook[*ModelEvent]{},
@ -405,6 +409,14 @@ func (app *BaseApp) OnBeforeServe() *hook.Hook[*ServeEvent] {
return app.onBeforeServe return app.onBeforeServe
} }
func (app *BaseApp) OnBeforeApiError() *hook.Hook[*ApiErrorEvent] {
return app.onBeforeApiError
}
func (app *BaseApp) OnAfterApiError() *hook.Hook[*ApiErrorEvent] {
return app.onAfterApiError
}
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Dao event hooks // Dao event hooks
// ------------------------------------------------------------------- // -------------------------------------------------------------------

View File

@ -25,6 +25,11 @@ type ServeEvent struct {
Router *echo.Echo Router *echo.Echo
} }
type ApiErrorEvent struct {
HttpContext echo.Context
Error error
}
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Model DAO events data // Model DAO events data
// ------------------------------------------------------------------- // -------------------------------------------------------------------

View File

@ -134,6 +134,20 @@ func (scenario *ApiScenario) Test(t *testing.T) {
} }
} }
// to minimize the breaking changes we always expect the error
// events to be called on API error
if res.StatusCode >= 400 {
if scenario.ExpectedEvents == nil {
scenario.ExpectedEvents = map[string]int{}
}
if _, ok := scenario.ExpectedEvents["OnBeforeApiError"]; !ok {
scenario.ExpectedEvents["OnBeforeApiError"] = 1
}
if _, ok := scenario.ExpectedEvents["OnAfterApiError"]; !ok {
scenario.ExpectedEvents["OnAfterApiError"] = 1
}
}
if len(testApp.EventCalls) > len(scenario.ExpectedEvents) { if len(testApp.EventCalls) > len(scenario.ExpectedEvents) {
t.Errorf("[%s] Expected events %v, got %v", prefix, scenario.ExpectedEvents, testApp.EventCalls) t.Errorf("[%s] Expected events %v, got %v", prefix, scenario.ExpectedEvents, testApp.EventCalls)
} }

View File

@ -87,6 +87,16 @@ func NewTestApp(optTestDataDir ...string) (*TestApp, error) {
TestMailer: &TestMailer{}, TestMailer: &TestMailer{},
} }
t.OnBeforeApiError().Add(func(e *core.ApiErrorEvent) error {
t.EventCalls["OnBeforeApiError"]++
return nil
})
t.OnAfterApiError().Add(func(e *core.ApiErrorEvent) error {
t.EventCalls["OnAfterApiError"]++
return nil
})
t.OnModelBeforeCreate().Add(func(e *core.ModelEvent) error { t.OnModelBeforeCreate().Add(func(e *core.ModelEvent) error {
t.EventCalls["OnModelBeforeCreate"]++ t.EventCalls["OnModelBeforeCreate"]++
return nil return nil