-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d055069
commit 220fb51
Showing
22 changed files
with
445 additions
and
140 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,143 +1,66 @@ | ||
// Package godi is a golang dependency injector container | ||
package godi | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"reflect" | ||
"strings" | ||
) | ||
|
||
var ( | ||
// ErrCannotBeResolved is returned when the container is not able to resolve the dependency. | ||
ErrCannotBeResolved = errors.New("the DI parameter cannot be resolved") | ||
// ErrCannotBeResolvedPossibleNeedExport is returned when the container is not able to resolve the dependency, possibly due to an unexported field. | ||
ErrCannotBeResolvedPossibleNeedExport = errors.New("the DI parameter cannot be resolved, possible unexported field for autowire notation") | ||
// ErrCircularReference is returned when there is a circular dependency reference. | ||
ErrCircularReference = errors.New("circular reference") | ||
) | ||
// Package gody is the wrapper around dependency injection container | ||
package gody | ||
|
||
import "github.com/olbrichattila/godi/internal/container" | ||
|
||
// Container is an just a wrapper around the internal container to be able to modify the implementation without effecting end users if they would think of referencing the sub package | ||
type Container interface { | ||
Set(string, interface{}) | ||
Get(interface{}) (interface{}, error) | ||
Build(map[string]interface{}) | ||
GetDependency(string) (interface{}, error) | ||
GetDependencies() map[string]interface{} | ||
Flush() | ||
Delete(paramName string) | ||
Count() int | ||
} | ||
|
||
// dependencyMap stores the dependency. | ||
type dependencyMap struct { | ||
dependency interface{} | ||
// Cont is the container structure which follows Container interface | ||
type Cont struct { | ||
c *container.Cont | ||
} | ||
|
||
// New creates a new dependency injector container. | ||
func New() *Cont { | ||
return &Cont{ | ||
dependencies: make(map[string]*dependencyMap), | ||
} | ||
// New creates a new container | ||
func New() Container { | ||
return &Cont{c: container.New()} | ||
} | ||
|
||
// Cont is the container returned by New. | ||
type Cont struct { | ||
callStack map[string]bool | ||
dependencies map[string]*dependencyMap | ||
// Build entire dependency tree | ||
func (t *Cont) Build(dependencies map[string]interface{}) { | ||
t.c.Build(dependencies) | ||
} | ||
|
||
// Set registers a new dependency. Provide a "packagePath.InterfaceName" as a string and your dependency, which should be an interface or struct. | ||
// Set Set dependencies to the container | ||
func (t *Cont) Set(paramName string, dependency interface{}) { | ||
t.dependencies[paramName] = &dependencyMap{dependency: dependency} | ||
t.c.Set(paramName, dependency) | ||
} | ||
|
||
// Get resolves dependencies. Use a construct function with your dependency interface type hints. They will be resolved recursively. | ||
func (t *Cont) Get(obj interface{}) (interface{}, error) { | ||
t.callStack = make(map[string]bool) | ||
return t.getRecursive(obj) | ||
return t.c.Get(obj) | ||
} | ||
|
||
// getRecursive resolves dependencies recursively, tracking call stack to detect circular references. | ||
func (t *Cont) getRecursive(obj interface{}) (interface{}, error) { | ||
v := reflect.ValueOf(obj) | ||
|
||
if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { | ||
rt := v.Type() | ||
|
||
if err := t.resolveConstructor(v, rt); err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := t.resolveAutoWire(v); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
return obj, nil | ||
// GetDependency retrieve the dependency, or returns error | ||
func (t *Cont) GetDependency(paramName string) (interface{}, error) { | ||
return t.c.GetDependency(paramName) | ||
} | ||
|
||
// resolveConstructor resolves dependencies for the constructor method. | ||
func (t *Cont) resolveConstructor(v reflect.Value, rt reflect.Type) error { | ||
if method, found := rt.MethodByName("Construct"); found { | ||
passParams := []reflect.Value{v} | ||
methodType := method.Type | ||
numParams := methodType.NumIn() | ||
|
||
for i := 1; i < numParams; i++ { | ||
paramType := methodType.In(i) | ||
param, fullTypeName, err := t.resolveConstructorParam(paramType) | ||
if err != nil { | ||
return err | ||
} | ||
if t.callStack[fullTypeName] { | ||
return fmt.Errorf("%w: circular call: %s", ErrCircularReference, fullTypeName) | ||
} | ||
t.callStack[fullTypeName] = true | ||
|
||
if _, err := t.getRecursive(param); err != nil { | ||
delete(t.callStack, fullTypeName) | ||
return err | ||
} | ||
delete(t.callStack, fullTypeName) | ||
passParams = append(passParams, reflect.ValueOf(param)) | ||
} | ||
|
||
method.Func.Call(passParams) | ||
} | ||
|
||
return nil | ||
// GetDependencies returns the entire dependency map | ||
func (t *Cont) GetDependencies() map[string]interface{} { | ||
return t.c.GetDependencies() | ||
} | ||
|
||
// resolveAutoWire resolves dependencies for struct fields with the "di" tag. | ||
func (t *Cont) resolveAutoWire(v reflect.Value) error { | ||
vTyp := v.Elem().Type() | ||
for i := 0; i < v.Elem().NumField(); i++ { | ||
field := vTyp.Field(i) | ||
tag := field.Tag.Get("di") | ||
if tag == "autowire" { | ||
resolvedField := v.Elem().FieldByName(field.Name) | ||
|
||
if !resolvedField.CanSet() { | ||
return fmt.Errorf("%w: the field name: %s", ErrCannotBeResolvedPossibleNeedExport, field.Name) | ||
} | ||
|
||
value, _, err := t.resolveConstructorParam(field.Type) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if _, err := t.getRecursive(value); err != nil { | ||
return err | ||
} | ||
|
||
fieldValue := reflect.ValueOf(value) | ||
if field.Type.Kind() == reflect.Interface && !fieldValue.Type().Implements(field.Type) { | ||
return fmt.Errorf("provided value does not implement the field's interface: %s", field.Name) | ||
} | ||
|
||
resolvedField.Set(fieldValue) | ||
} | ||
} | ||
return nil | ||
// Flush dependencies | ||
func (t *Cont) Flush() { | ||
t.c.Flush() | ||
} | ||
|
||
// resolveConstructorParam resolves a constructor parameter by its type. | ||
func (t *Cont) resolveConstructorParam(paramType reflect.Type) (interface{}, string, error) { | ||
pkgPath := paramType.PkgPath() + "/" + paramType.Name() | ||
fullTypeName := strings.Join(strings.Split(pkgPath, "/")[1:], ".") | ||
param, ok := t.dependencies[fullTypeName] | ||
if !ok { | ||
return nil, fullTypeName, fmt.Errorf("%w: dependency name: %s", ErrCannotBeResolved, fullTypeName) | ||
} | ||
// Delete one dependency | ||
func (t *Cont) Delete(paramName string) { | ||
t.c.Delete(paramName) | ||
} | ||
|
||
return param.dependency, fullTypeName, nil | ||
// Count returns how any dependencies provided | ||
func (t *Cont) Count() int { | ||
return t.c.Count() | ||
} |
Oops, something went wrong.