From 804ff1fc3b727bcbc30a0d7d348a44c08c5eb923 Mon Sep 17 00:00:00 2001 From: krishan kumar <84431594+kkumar-gcc@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:23:43 +0530 Subject: [PATCH] feat: [#495] Centralize Error Messages in Framework (PR#1) (#663) * implement errors for framework * chore: update mocks * implement errors for framework * rename contract import * add errors for session * rename location to module * chore: update mocks --------- Co-authored-by: kkumar-gcc --- contracts/errors/errors.go | 11 +++ errors/errors.go | 63 ++++++++++++ errors/list.go | 12 +++ errors/modules.go | 5 + mocks/errors/Error.go | 185 ++++++++++++++++++++++++++++++++++++ session/driver/file.go | 4 +- session/manager.go | 12 +-- session/manager_test.go | 11 ++- session/service_provider.go | 5 +- session/session.go | 3 +- session/session_test.go | 6 +- 11 files changed, 299 insertions(+), 18 deletions(-) create mode 100644 contracts/errors/errors.go create mode 100644 errors/errors.go create mode 100644 errors/list.go create mode 100644 errors/modules.go create mode 100644 mocks/errors/Error.go diff --git a/contracts/errors/errors.go b/contracts/errors/errors.go new file mode 100644 index 000000000..8469532e5 --- /dev/null +++ b/contracts/errors/errors.go @@ -0,0 +1,11 @@ +package errors + +// Error is the interface that wraps the basic error methods +type Error interface { + // Args allows setting arguments for the placeholders in the text + Args(...any) Error + // Error implements the error interface and formats the error string + Error() string + // SetModule explicitly sets the module in the error message + SetModule(string) Error +} diff --git a/errors/errors.go b/errors/errors.go new file mode 100644 index 000000000..7b9e19a55 --- /dev/null +++ b/errors/errors.go @@ -0,0 +1,63 @@ +package errors + +import ( + "errors" + "fmt" + + contractserrors "github.com/goravel/framework/contracts/errors" +) + +type errorString struct { + text string + module string + args []any +} + +// New creates a new error with the provided text and optional module +func New(text string, module ...string) contractserrors.Error { + err := &errorString{ + text: text, + } + + if len(module) > 0 { + err.module = module[0] + } + + return err +} + +func (e *errorString) Args(args ...any) contractserrors.Error { + e.args = args + return e +} + +func (e *errorString) Error() string { + formattedText := e.text + + if len(e.args) > 0 { + formattedText = fmt.Sprintf(e.text, e.args...) + } + + if e.module != "" { + formattedText = fmt.Sprintf("[%s] %s", e.module, formattedText) + } + + return formattedText +} + +func (e *errorString) SetModule(module string) contractserrors.Error { + e.module = module + return e +} + +func Is(err, target error) bool { + return errors.Is(err, target) +} + +func As(err error, target any) bool { + return errors.As(err, &target) +} + +func Unwrap(err error) error { + return errors.Unwrap(err) +} diff --git a/errors/list.go b/errors/list.go new file mode 100644 index 000000000..16bf3001f --- /dev/null +++ b/errors/list.go @@ -0,0 +1,12 @@ +package errors + +var ( + ErrConfigFacadeNotSet = New("config facade is not initialized") + ErrJSONParserNotSet = New("JSON parser is not initialized") + + ErrSessionNotFound = New("session [%s] not found") + ErrSessionDriverIsNotSet = New("session driver is not set") + ErrSessionDriverNotSupported = New("session driver [%s] not supported") + ErrSessionDriverAlreadyExists = New("session driver [%s] already exists") + ErrSessionDriverExtensionFailed = New("failed to extend session [%s] driver [%v]") +) diff --git a/errors/modules.go b/errors/modules.go new file mode 100644 index 000000000..edcb5b22a --- /dev/null +++ b/errors/modules.go @@ -0,0 +1,5 @@ +package errors + +var ( + ModuleSession = "session" +) diff --git a/mocks/errors/Error.go b/mocks/errors/Error.go new file mode 100644 index 000000000..f62ce824e --- /dev/null +++ b/mocks/errors/Error.go @@ -0,0 +1,185 @@ +// Code generated by mockery. DO NOT EDIT. + +package errors + +import ( + errors "github.com/goravel/framework/contracts/errors" + mock "github.com/stretchr/testify/mock" +) + +// Error is an autogenerated mock type for the Error type +type Error struct { + mock.Mock +} + +type Error_Expecter struct { + mock *mock.Mock +} + +func (_m *Error) EXPECT() *Error_Expecter { + return &Error_Expecter{mock: &_m.Mock} +} + +// Args provides a mock function with given fields: _a0 +func (_m *Error) Args(_a0 ...interface{}) errors.Error { + var _ca []interface{} + _ca = append(_ca, _a0...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Args") + } + + var r0 errors.Error + if rf, ok := ret.Get(0).(func(...interface{}) errors.Error); ok { + r0 = rf(_a0...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(errors.Error) + } + } + + return r0 +} + +// Error_Args_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Args' +type Error_Args_Call struct { + *mock.Call +} + +// Args is a helper method to define mock.On call +// - _a0 ...interface{} +func (_e *Error_Expecter) Args(_a0 ...interface{}) *Error_Args_Call { + return &Error_Args_Call{Call: _e.mock.On("Args", + append([]interface{}{}, _a0...)...)} +} + +func (_c *Error_Args_Call) Run(run func(_a0 ...interface{})) *Error_Args_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-0) + for i, a := range args[0:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(variadicArgs...) + }) + return _c +} + +func (_c *Error_Args_Call) Return(_a0 errors.Error) *Error_Args_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Error_Args_Call) RunAndReturn(run func(...interface{}) errors.Error) *Error_Args_Call { + _c.Call.Return(run) + return _c +} + +// Error provides a mock function with given fields: +func (_m *Error) Error() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Error") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Error_Error_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Error' +type Error_Error_Call struct { + *mock.Call +} + +// Error is a helper method to define mock.On call +func (_e *Error_Expecter) Error() *Error_Error_Call { + return &Error_Error_Call{Call: _e.mock.On("Error")} +} + +func (_c *Error_Error_Call) Run(run func()) *Error_Error_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Error_Error_Call) Return(_a0 string) *Error_Error_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Error_Error_Call) RunAndReturn(run func() string) *Error_Error_Call { + _c.Call.Return(run) + return _c +} + +// SetModule provides a mock function with given fields: _a0 +func (_m *Error) SetModule(_a0 string) errors.Error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for SetModule") + } + + var r0 errors.Error + if rf, ok := ret.Get(0).(func(string) errors.Error); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(errors.Error) + } + } + + return r0 +} + +// Error_SetModule_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetModule' +type Error_SetModule_Call struct { + *mock.Call +} + +// SetModule is a helper method to define mock.On call +// - _a0 string +func (_e *Error_Expecter) SetModule(_a0 interface{}) *Error_SetModule_Call { + return &Error_SetModule_Call{Call: _e.mock.On("SetModule", _a0)} +} + +func (_c *Error_SetModule_Call) Run(run func(_a0 string)) *Error_SetModule_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Error_SetModule_Call) Return(_a0 errors.Error) *Error_SetModule_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Error_SetModule_Call) RunAndReturn(run func(string) errors.Error) *Error_SetModule_Call { + _c.Call.Return(run) + return _c +} + +// NewError creates a new instance of Error. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewError(t interface { + mock.TestingT + Cleanup(func()) +}) *Error { + mock := &Error{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/session/driver/file.go b/session/driver/file.go index 0d06f38c2..1e7b41ef6 100644 --- a/session/driver/file.go +++ b/session/driver/file.go @@ -1,11 +1,11 @@ package driver import ( - "fmt" "os" "path/filepath" "sync" + "github.com/goravel/framework/errors" "github.com/goravel/framework/support/carbon" "github.com/goravel/framework/support/file" ) @@ -80,7 +80,7 @@ func (f *File) Read(id string) (string, error) { } } - return "", fmt.Errorf("session [%s] not found", id) + return "", errors.ErrSessionNotFound.Args(id) } func (f *File) Write(id string, data string) error { diff --git a/session/manager.go b/session/manager.go index 6355f726a..79198c3b3 100644 --- a/session/manager.go +++ b/session/manager.go @@ -1,13 +1,13 @@ package session import ( - "fmt" "sync" "time" "github.com/goravel/framework/contracts/config" "github.com/goravel/framework/contracts/foundation" sessioncontract "github.com/goravel/framework/contracts/session" + "github.com/goravel/framework/errors" "github.com/goravel/framework/session/driver" "github.com/goravel/framework/support/color" ) @@ -35,7 +35,7 @@ func NewManager(config config.Config, json foundation.Json) *Manager { func (m *Manager) BuildSession(handler sessioncontract.Driver, sessionID ...string) (sessioncontract.Session, error) { if handler == nil { - return nil, ErrDriverNotSet + return nil, errors.ErrSessionDriverIsNotSet } session := m.acquireSession() @@ -60,11 +60,11 @@ func (m *Manager) Driver(name ...string) (sessioncontract.Driver, error) { } if driverName == "" { - return nil, fmt.Errorf("driver is not set") + return nil, errors.ErrSessionDriverIsNotSet } if m.drivers[driverName] == nil { - return nil, fmt.Errorf("driver [%s] not supported", driverName) + return nil, errors.ErrSessionDriverNotSupported.Args(driverName) } return m.drivers[driverName], nil @@ -72,7 +72,7 @@ func (m *Manager) Driver(name ...string) (sessioncontract.Driver, error) { func (m *Manager) Extend(driver string, handler func() sessioncontract.Driver) error { if m.drivers[driver] != nil { - return fmt.Errorf("driver [%s] already exists", driver) + return errors.ErrSessionDriverAlreadyExists.Args(driver) } m.drivers[driver] = handler() m.startGcTimer(m.drivers[driver]) @@ -98,7 +98,7 @@ func (m *Manager) getDefaultDriver() string { func (m *Manager) extendDefaultDrivers() { if err := m.Extend("file", m.createFileDriver); err != nil { - panic(fmt.Sprintf("failed to extend session file driver: %v", err)) + panic(errors.ErrSessionDriverExtensionFailed.Args("file", err)) } } diff --git a/session/manager_test.go b/session/manager_test.go index ebf215b94..972ffee53 100644 --- a/session/manager_test.go +++ b/session/manager_test.go @@ -9,6 +9,7 @@ import ( "github.com/goravel/framework/contracts/foundation" sessioncontract "github.com/goravel/framework/contracts/session" + "github.com/goravel/framework/errors" "github.com/goravel/framework/foundation/json" mockconfig "github.com/goravel/framework/mocks/config" "github.com/goravel/framework/support/str" @@ -66,14 +67,15 @@ func (s *ManagerTestSuite) TestDriver() { s.mockConfig.On("GetString", "session.driver").Return("not_supported").Once() driver, err = s.manager.Driver() s.NotNil(err) - s.Equal("driver [not_supported] not supported", err.Error()) + s.ErrorIs(err, errors.ErrSessionDriverNotSupported) + s.Equal(errors.ErrSessionDriverNotSupported.Args("not_supported").Error(), err.Error()) s.Nil(driver) // driver is not set s.mockConfig.On("GetString", "session.driver").Return("").Once() driver, err = s.manager.Driver() s.NotNil(err) - s.Equal("driver is not set", err.Error()) + s.ErrorIs(err, errors.ErrSessionDriverIsNotSet) s.Nil(driver) } @@ -88,7 +90,8 @@ func (s *ManagerTestSuite) TestExtend() { // driver already exists err = s.manager.Extend("test", NewCustomDriver) - s.Errorf(err, "driver [%s] already exists", "test") + s.ErrorIs(err, errors.ErrSessionDriverAlreadyExists) + s.Equal(err.Error(), errors.ErrSessionDriverAlreadyExists.Args("test").Error()) } func (s *ManagerTestSuite) TestBuildSession() { @@ -113,7 +116,7 @@ func (s *ManagerTestSuite) TestBuildSession() { // driver is nil session, err = s.manager.BuildSession(nil) - s.ErrorIs(err, ErrDriverNotSet) + s.ErrorIs(err, errors.ErrSessionDriverIsNotSet) s.Nil(session) } diff --git a/session/service_provider.go b/session/service_provider.go index 682b18d9a..5dfc2a697 100644 --- a/session/service_provider.go +++ b/session/service_provider.go @@ -4,6 +4,7 @@ import ( "github.com/goravel/framework/contracts/config" "github.com/goravel/framework/contracts/foundation" "github.com/goravel/framework/contracts/session" + "github.com/goravel/framework/errors" ) var ( @@ -20,12 +21,12 @@ func (receiver *ServiceProvider) Register(app foundation.Application) { app.Singleton(Binding, func(app foundation.Application) (any, error) { c := app.MakeConfig() if c == nil { - return nil, ErrConfigFacadeNotSet + return nil, errors.ErrConfigFacadeNotSet.SetModule(errors.ModuleSession) } j := app.GetJson() if j == nil { - return nil, ErrJSONNotSet + return nil, errors.ErrJSONParserNotSet.SetModule(errors.ModuleSession) } return NewManager(c, j), nil diff --git a/session/session.go b/session/session.go index 4e9fc0e4e..de0296984 100644 --- a/session/session.go +++ b/session/session.go @@ -8,6 +8,7 @@ import ( "github.com/goravel/framework/contracts/foundation" sessioncontract "github.com/goravel/framework/contracts/session" + "github.com/goravel/framework/errors" "github.com/goravel/framework/support/color" supportmaps "github.com/goravel/framework/support/maps" "github.com/goravel/framework/support/str" @@ -222,7 +223,7 @@ func (s *Session) loadSession() { func (s *Session) validateDriver() error { if s.driver == nil { - return ErrDriverNotSet + return errors.ErrSessionDriverIsNotSet } return nil } diff --git a/session/session_test.go b/session/session_test.go index e13420e6c..c8d38820e 100644 --- a/session/session_test.go +++ b/session/session_test.go @@ -1,7 +1,6 @@ package session import ( - "errors" "strings" "testing" @@ -9,6 +8,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/goravel/framework/contracts/foundation" + "github.com/goravel/framework/errors" "github.com/goravel/framework/foundation/json" mocksession "github.com/goravel/framework/mocks/session" "github.com/goravel/framework/support/str" @@ -165,7 +165,7 @@ func (s *SessionTestSuite) TestMigrate() { // when driver is nil s.session.SetDriver(nil) - s.ErrorIs(s.session.migrate(true), ErrDriverNotSet) + s.ErrorIs(s.session.migrate(true), errors.ErrSessionDriverIsNotSet) } func (s *SessionTestSuite) TestMissing() { @@ -304,7 +304,7 @@ func (s *SessionTestSuite) TestSave() { // when driver is nil s.session.SetDriver(nil) - s.ErrorIs(s.session.Save(), ErrDriverNotSet) + s.ErrorIs(s.session.Save(), errors.ErrSessionDriverIsNotSet) } func (s *SessionTestSuite) TestSetID() {