Skip to content

wrap error and provider more error context information #52

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 43 additions & 22 deletions di.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package di
import (
"context"
"errors"
"fmt"
"io"
"reflect"
"strconv"
Expand Down Expand Up @@ -66,6 +67,25 @@ var singletonInstances = make(map[string]interface{})
var userCreatedInstances = make(map[string]bool)
var beanPostprocessors = make(map[reflect.Type][]func(bean interface{}) error)

var ErrContainerIsAlreadyInitialized = errors.New("container is already initialized")

var ErrCannotRegisterBeanPostProcessor = fmt.Errorf("%w: can't register bean postprocessor", ErrContainerIsAlreadyInitialized)
var ErrCannotRegisterNewBeanFactory = fmt.Errorf("%w: can't register new bean factory", ErrContainerIsAlreadyInitialized)
var ErrCannotRegisterNewBean = fmt.Errorf("%w: can't register new bean", ErrContainerIsAlreadyInitialized)
var ErrReinitializationNotSupported = fmt.Errorf("%w: reinitialization is not supported", ErrContainerIsAlreadyInitialized)
var ErrUnsupportedDependencyType = errors.New(unsupportedDependencyType)
var ErrRequestScopedBeansCantBeInjected = errors.New(requestScopedBeansCantBeInjected)
var ErrBeanInstanceMustBePointer = errors.New("bean instance must be a pointer")
var ErrBeanTypeMustBePointer = errors.New("bean type must be a pointer")
var ErrUnsupportedScope = errors.New("unsupported scope")
var ErrNoCandidatesFound = errors.New("no candidates found for the injection")
var ErrNoDependencyFound = errors.New("no dependency found")
var ErrInvalidOptionalValue = errors.New("invalid di.optional value")
var ErrBeanFactoryMustReturnPointer = errors.New("bean factory must return pointer")
var ErrCannotFindSetContextMethod = errors.New("unexpected behavior: can't find method SetContext()")
var ErrContainerIsNotInitialized = errors.New("container is not initialized: can't lookup instances of beans yet")
var ErrMoreThenOneCandidateFound = errors.New("more then one candidate found for the injection")

// InitializingBean is an interface marking beans that need to be additionally initialized after the container is ready.
type InitializingBean interface {
// PostConstruct method will be called on a bean after the container is initialized.
Expand All @@ -89,7 +109,7 @@ func RegisterBeanPostprocessor(beanType reflect.Type, postprocessor func(bean in
initializeShutdownLock.Lock()
defer initializeShutdownLock.Unlock()
if atomic.CompareAndSwapInt32(&containerInitialized, 1, 1) {
return errors.New("container is already initialized: can't register bean postprocessor")
return ErrCannotRegisterBeanPostProcessor
}
beanPostprocessors[beanType] = append(beanPostprocessors[beanType], postprocessor)
return nil
Expand All @@ -100,7 +120,7 @@ func InitializeContainer() error {
initializeShutdownLock.Lock()
defer initializeShutdownLock.Unlock()
if atomic.CompareAndSwapInt32(&containerInitialized, 1, 1) {
return errors.New("container is already initialized: reinitialization is not supported")
return ErrReinitializationNotSupported
}
err := createSingletonInstances()
if err != nil {
Expand All @@ -126,10 +146,10 @@ func RegisterBean(beanID string, beanType reflect.Type) (overwritten bool, err e
initializeShutdownLock.Lock()
defer initializeShutdownLock.Unlock()
if atomic.CompareAndSwapInt32(&containerInitialized, 1, 1) {
return false, errors.New("container is already initialized: can't register new bean")
return false, ErrCannotRegisterNewBean
}
if beanType.Kind() != reflect.Ptr {
return false, errors.New("bean type must be a pointer")
return false, ErrBeanTypeMustBePointer
}
var existingBeanType reflect.Type
var ok bool
Expand All @@ -152,7 +172,7 @@ func RegisterBean(beanID string, beanType reflect.Type) (overwritten bool, err e
}
if field.Type.Kind() != reflect.Ptr && field.Type.Kind() != reflect.Interface &&
field.Type.Kind() != reflect.Slice && field.Type.Kind() != reflect.Map {
return false, errors.New(unsupportedDependencyType)
return false, ErrUnsupportedDependencyType
}
}
beans[beanID] = beanType
Expand All @@ -167,11 +187,11 @@ func RegisterBeanInstance(beanID string, beanInstance interface{}) (overwritten
initializeShutdownLock.Lock()
defer initializeShutdownLock.Unlock()
if atomic.CompareAndSwapInt32(&containerInitialized, 1, 1) {
return false, errors.New("container is already initialized: can't register new bean")
return false, ErrCannotRegisterNewBean
}
beanType := reflect.TypeOf(beanInstance)
if beanType.Kind() != reflect.Ptr {
return false, errors.New("bean instance must be a pointer")
return false, ErrBeanInstanceMustBePointer
}
var existingBeanType reflect.Type
var ok bool
Expand All @@ -197,7 +217,7 @@ func RegisterBeanFactory(beanID string, beanScope Scope, beanFactory func(ctx co
initializeShutdownLock.Lock()
defer initializeShutdownLock.Unlock()
if atomic.CompareAndSwapInt32(&containerInitialized, 1, 1) {
return false, errors.New("container is already initialized: can't register new bean factory")
return false, ErrCannotRegisterNewBeanFactory
}
var existingBeanType reflect.Type
var ok bool
Expand Down Expand Up @@ -237,7 +257,8 @@ func getScope(bean reflect.Type) (*Scope, error) {
case string(Request):
return &request, nil
}
return nil, errors.New("unsupported scope: " + beanScope)

return nil, fmt.Errorf("%w:%s", ErrUnsupportedScope, beanScope)
}

func injectSingletonDependencies() error {
Expand Down Expand Up @@ -280,10 +301,10 @@ func injectDependencies(beanID string, instance interface{}, chain map[string]bo
if optionalDependency {
continue
}
return errors.New("no candidates found for the injection")
return ErrNoCandidatesFound
}
if len(candidates) > 1 {
return errors.New("more then one candidate found for the injection")
return ErrMoreThenOneCandidateFound
}
beanToInject = candidates[0]
}
Expand All @@ -295,7 +316,7 @@ func injectDependencies(beanID string, instance interface{}, chain map[string]bo
logrus.Trace("no dependency found, injecting nil since the dependency marked as optional")
continue
}
return errors.New("no dependency found")
return fmt.Errorf("%w: on bean [%s] miss dependency [%s]", ErrNoDependencyFound, beanID, beanToInject)
}
if beanScope == Request {
return errors.New(requestScopedBeansCantBeInjected)
Expand All @@ -307,7 +328,7 @@ func injectDependencies(beanID string, instance interface{}, chain map[string]bo
fieldToInject.Set(reflect.ValueOf(instanceToInject))
case reflect.Slice:
if fieldToInject.Type().Elem().Kind() != reflect.Ptr && fieldToInject.Type().Elem().Kind() != reflect.Interface {
return errors.New(unsupportedDependencyType)
return ErrUnsupportedDependencyType
}
candidates := findInjectionCandidates(fieldToInject.Type().Elem())
if len(candidates) < 1 {
Expand All @@ -331,7 +352,7 @@ func injectDependencies(beanID string, instance interface{}, chain map[string]bo
}
case reflect.Map:
if fieldToInject.Type().Elem().Kind() != reflect.Ptr && fieldToInject.Type().Elem().Kind() != reflect.Interface {
return errors.New(unsupportedDependencyType)
return ErrUnsupportedDependencyType
}
candidates := findInjectionCandidates(fieldToInject.Type().Elem())
if len(candidates) < 1 {
Expand All @@ -345,7 +366,7 @@ func injectDependencies(beanID string, instance interface{}, chain map[string]bo
beanToInjectType := beans[beanToInject]
logInjection(beanID, instanceElement, beanToInject, beanToInjectType)
if scopes[beanToInject] == Request {
return errors.New(requestScopedBeansCantBeInjected)
return ErrRequestScopedBeansCantBeInjected
}
instanceToInject, err := getInstance(context.Background(), beanToInject, chain)
if err != nil {
Expand All @@ -354,7 +375,7 @@ func injectDependencies(beanID string, instance interface{}, chain map[string]bo
fieldToInject.SetMapIndex(reflect.ValueOf(beanToInject), reflect.ValueOf(instanceToInject))
}
default:
return errors.New(unsupportedDependencyType)
return ErrUnsupportedDependencyType
}
}
return nil
Expand All @@ -373,7 +394,7 @@ func isOptional(field reflect.StructField) (bool, error) {
optionalTag := field.Tag.Get(string(optional))
value, err := strconv.ParseBool(optionalTag)
if optionalTag != "" && err != nil {
return false, errors.New("invalid di.optional value: " + optionalTag)
return false, fmt.Errorf("%w:%s", ErrInvalidOptionalValue, optionalTag)
}
return value, nil
}
Expand Down Expand Up @@ -415,7 +436,7 @@ func createSingletonInstances() error {
return err
}
if reflect.TypeOf(beanInstance).Kind() != reflect.Ptr {
return errors.New("bean factory must return pointer")
return ErrBeanFactoryMustReturnPointer
}
singletonInstances[beanID] = beanInstance
logrus.WithFields(logrus.Fields{
Expand All @@ -435,7 +456,7 @@ func createInstance(ctx context.Context, beanID string) (interface{}, error) {
return nil, err
}
if reflect.TypeOf(beanInstance).Kind() != reflect.Ptr {
return nil, errors.New("bean factory must return pointer")
return nil, ErrBeanFactoryMustReturnPointer
}
return beanInstance, nil
}
Expand Down Expand Up @@ -482,7 +503,7 @@ func setContext(ctx context.Context, beanID string, instance interface{}) error
if bean.Implements(contextAwareBean) {
setContextMethod, ok := bean.MethodByName(contextAwareBean.Method(0).Name)
if !ok {
return errors.New("unexpected behavior: can't find method SetContext() in bean " + bean.String())
return fmt.Errorf("%w: in bean %s", ErrCannotFindSetContextMethod, bean.String())
}
logrus.WithField("beanID", beanID).WithField("context", ctx).Trace("setting context to bean")
setContextMethod.Func.Call([]reflect.Value{reflect.ValueOf(instance), reflect.ValueOf(ctx)})
Expand All @@ -504,7 +525,7 @@ func GetInstance(beanID string) interface{} {
// instead.
func GetInstanceSafe(beanID string) (interface{}, error) {
if atomic.CompareAndSwapInt32(&containerInitialized, 0, 0) {
return nil, errors.New("container is not initialized: can't lookup instances of beans yet")
return nil, ErrContainerIsNotInitialized
}
if scopes[beanID] == Request {
return nil, errors.New("request-scoped beans can't be retrieved directly from the container: they can only be retrieved from the web-context")
Expand All @@ -514,7 +535,7 @@ func GetInstanceSafe(beanID string) (interface{}, error) {

func getRequestBeanInstance(ctx context.Context, beanID string) interface{} {
if atomic.CompareAndSwapInt32(&containerInitialized, 0, 0) {
panic("container is not initialized: can't lookup instances of beans yet")
panic(ErrContainerIsNotInitialized)
}
beanInstance, err := getInstance(ctx, beanID, make(map[string]bool))
if err != nil {
Expand Down
35 changes: 18 additions & 17 deletions di_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ func TestDITestSuite(t *testing.T) {
func (suite *TestSuite) TestInitializeContainerTwice() {
err := InitializeContainer()
assert.NoError(suite.T(), err)
expectedError := errors.New("container is already initialized: reinitialization is not supported")
err = InitializeContainer()
if assert.Error(suite.T(), err) {
assert.Equal(suite.T(), expectedError, err)
assert.ErrorIs(suite.T(), err, ErrReinitializationNotSupported)
assert.ErrorIs(suite.T(), err, ErrContainerIsAlreadyInitialized)
}
}

Expand All @@ -69,16 +69,17 @@ func (suite *TestSuite) TestGetInstanceBeforeContainerInitialization() {
func (suite *TestSuite) TestRegisterBeanAfterContainerInitialization() {
err := InitializeContainer()
assert.NoError(suite.T(), err)
expectedError := errors.New("container is already initialized: can't register new bean")
overwritten, err := RegisterBean("", nil)
assert.False(suite.T(), overwritten)
if assert.Error(suite.T(), err) {
assert.Equal(suite.T(), expectedError, err)
assert.ErrorIs(suite.T(), err, ErrCannotRegisterNewBean)
assert.ErrorIs(suite.T(), err, ErrContainerIsAlreadyInitialized)
}
overwritten, err = RegisterBeanInstance("", nil)
assert.False(suite.T(), overwritten)
if assert.Error(suite.T(), err) {
assert.Equal(suite.T(), expectedError, err)
assert.ErrorIs(suite.T(), err, ErrCannotRegisterNewBean)
assert.ErrorIs(suite.T(), err, ErrContainerIsAlreadyInitialized)
}
}

Expand Down Expand Up @@ -114,10 +115,10 @@ func (suite *TestSuite) TestBeanFactoryCalledOnce() {
func (suite *TestSuite) TestRegisterBeanPostprocessorAfterContainerInitialization() {
err := InitializeContainer()
assert.NoError(suite.T(), err)
expectedError := errors.New("container is already initialized: can't register bean postprocessor")
err = RegisterBeanPostprocessor(reflect.TypeOf((*string)(nil)), nil)
if assert.Error(suite.T(), err) {
assert.Equal(suite.T(), expectedError, err)
assert.ErrorIs(suite.T(), err, ErrCannotRegisterBeanPostProcessor)
assert.ErrorIs(suite.T(), err, ErrContainerIsAlreadyInitialized)
}
}

Expand Down Expand Up @@ -172,11 +173,11 @@ func (suite *TestSuite) TestRegisterSingletonBeanUnsupportedScope() {
type SingletonBean struct {
Scope Scope `di.scope:"invalid"`
}
expectedError := errors.New("unsupported scope: invalid")
overwritten, err := RegisterBean("", reflect.TypeOf((*SingletonBean)(nil)))
assert.False(suite.T(), overwritten)
if assert.Error(suite.T(), err) {
assert.Equal(suite.T(), expectedError, err)
assert.ErrorIs(suite.T(), err, ErrUnsupportedScope)
assert.ErrorContains(suite.T(), err, "invalid")
}
}

Expand All @@ -199,10 +200,10 @@ func (suite *TestSuite) TestRegisterSingletonBeanWrongOptionalValue() {
overwritten, err := RegisterBean("singletonBean", reflect.TypeOf((*SingletonBean)(nil)))
assert.False(suite.T(), overwritten)
assert.NoError(suite.T(), err)
expectedError := errors.New("invalid di.optional value: fls")
err = InitializeContainer()
if assert.Error(suite.T(), err) {
assert.Equal(suite.T(), expectedError, err)
assert.ErrorIs(suite.T(), err, ErrInvalidOptionalValue)
assert.ErrorContains(suite.T(), err, "fls")
}
}

Expand All @@ -213,10 +214,10 @@ func (suite *TestSuite) TestRegisterSingletonBeanMissingImplicitlyRequiredDepend
overwritten, err := RegisterBean("singletonBean", reflect.TypeOf((*SingletonBean)(nil)))
assert.False(suite.T(), overwritten)
assert.NoError(suite.T(), err)
expectedError := errors.New("no dependency found")
err = InitializeContainer()
if assert.Error(suite.T(), err) {
assert.Equal(suite.T(), expectedError, err)
assert.ErrorIs(suite.T(), err, ErrNoDependencyFound)
assert.ErrorContains(suite.T(), err, "someOtherBean")
}
}

Expand All @@ -227,10 +228,10 @@ func (suite *TestSuite) TestRegisterSingletonBeanMissingExplicitlyRequiredDepend
overwritten, err := RegisterBean("singletonBean", reflect.TypeOf((*SingletonBean)(nil)))
assert.False(suite.T(), overwritten)
assert.NoError(suite.T(), err)
expectedError := errors.New("no dependency found")
err = InitializeContainer()
if assert.Error(suite.T(), err) {
assert.Equal(suite.T(), expectedError, err)
assert.ErrorIs(suite.T(), err, ErrNoDependencyFound)
assert.ErrorContains(suite.T(), err, "someOtherBean")
}
}

Expand Down Expand Up @@ -361,11 +362,11 @@ func (suite *TestSuite) TestRegisterSingletonBeanFactory() {
func (suite *TestSuite) TestRegisterBeanFactoryAfterContainerInitialization() {
err := InitializeContainer()
assert.NoError(suite.T(), err)
expectedFactoryError := errors.New("container is already initialized: can't register new bean factory")
overwritten, err := RegisterBeanFactory("", Singleton, nil)
assert.False(suite.T(), overwritten)
assert.Error(suite.T(), err)
assert.Equal(suite.T(), expectedFactoryError, err)
assert.ErrorIs(suite.T(), err, ErrCannotRegisterNewBeanFactory)
assert.ErrorIs(suite.T(), err, ErrContainerIsAlreadyInitialized)
}

func (suite *TestSuite) TestRegisterPrototypeBean() {
Expand Down