mirror of
https://github.com/kataras/iris.git
synced 2026-01-10 21:45:57 +00:00
create a new package, name it as hero, I was thinking super or superb but hero is better name for what it does - the goal is to split the new 'mvc handlers' from the mvc system because they are not the same, users should know that they can use these type of rich binded handlers without controllers as well, like a normal handler and that I implemented here, the old files exist on the mvc package but will be removed at the next commit, I have to decide if we want type aliases for Result or no
Former-commit-id: cb775edc72bedc88aeab4c5a6de6bfc6bd56fae2
This commit is contained in:
11
hero/di/TODO.txt
Normal file
11
hero/di/TODO.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
I can do one of the followings to this "di" folder when I finish the cleanup and document it a bit,
|
||||
although I'm sick I will try to finish it tomorrow.
|
||||
|
||||
End-users don't need this.
|
||||
1) So, rename this to "internal".
|
||||
|
||||
I don't know if something similar exist in Go,
|
||||
it's a dependency injection framework at the end, and a very fast one.
|
||||
|
||||
2) So I'm thinking to push it to a different repo,
|
||||
like https://github.com/kataras/di or even to my small common https://github.com/kataras/pkg collection.
|
||||
129
hero/di/di.go
Normal file
129
hero/di/di.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package di
|
||||
|
||||
import "reflect"
|
||||
|
||||
type (
|
||||
// Hijacker is a type which is used to catch fields or function's input argument
|
||||
// to bind a custom object based on their type.
|
||||
Hijacker func(reflect.Type) (*BindObject, bool)
|
||||
// TypeChecker checks if a specific field's or function input argument's
|
||||
// is valid to be binded.
|
||||
TypeChecker func(reflect.Type) bool
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultHijacker is the hijacker used on the package-level Struct & Func functions.
|
||||
DefaultHijacker Hijacker
|
||||
// DefaultTypeChecker is the typechecker used on the package-level Struct & Func functions.
|
||||
DefaultTypeChecker TypeChecker
|
||||
)
|
||||
|
||||
// Struct is being used to return a new injector based on
|
||||
// a struct value instance, if it contains fields that the types of those
|
||||
// are matching with one or more of the `Values` then they are binded
|
||||
// with the injector's `Inject` and `InjectElem` methods.
|
||||
func Struct(s interface{}, values ...reflect.Value) *StructInjector {
|
||||
if s == nil {
|
||||
return &StructInjector{Has: false}
|
||||
}
|
||||
|
||||
return MakeStructInjector(
|
||||
ValueOf(s),
|
||||
DefaultHijacker,
|
||||
DefaultTypeChecker,
|
||||
Values(values).CloneWithFieldsOf(s)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Func is being used to return a new injector based on
|
||||
// a function, if it contains input arguments that the types of those
|
||||
// are matching with one or more of the `Values` then they are binded
|
||||
// to the function's input argument when called
|
||||
// with the injector's `Inject` method.
|
||||
func Func(fn interface{}, values ...reflect.Value) *FuncInjector {
|
||||
if fn == nil {
|
||||
return &FuncInjector{Has: false}
|
||||
}
|
||||
|
||||
return MakeFuncInjector(
|
||||
ValueOf(fn),
|
||||
DefaultHijacker,
|
||||
DefaultTypeChecker,
|
||||
values...,
|
||||
)
|
||||
}
|
||||
|
||||
// D is the Dependency Injection container,
|
||||
// it contains the Values that can be changed before the injectors.
|
||||
// `Struct` and the `Func` methods returns an injector for specific
|
||||
// struct instance-value or function.
|
||||
type D struct {
|
||||
Values
|
||||
|
||||
hijacker Hijacker
|
||||
goodFunc TypeChecker
|
||||
}
|
||||
|
||||
// New creates and returns a new Dependency Injection container.
|
||||
// See `Values` field and `Func` and `Struct` methods for more.
|
||||
func New() *D {
|
||||
return &D{}
|
||||
}
|
||||
|
||||
// Hijack sets a hijacker function, read the `Hijacker` type for more explanation.
|
||||
func (d *D) Hijack(fn Hijacker) *D {
|
||||
d.hijacker = fn
|
||||
return d
|
||||
}
|
||||
|
||||
// GoodFunc sets a type checker for a valid function that can be binded,
|
||||
// read the `TypeChecker` type for more explanation.
|
||||
func (d *D) GoodFunc(fn TypeChecker) *D {
|
||||
d.goodFunc = fn
|
||||
return d
|
||||
}
|
||||
|
||||
// Clone returns a new Dependency Injection container, it adopts the
|
||||
// parent's (current "D") hijacker, good func type checker and all dependencies values.
|
||||
func (d *D) Clone() *D {
|
||||
return &D{
|
||||
Values: d.Values.Clone(),
|
||||
hijacker: d.hijacker,
|
||||
goodFunc: d.goodFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// Struct is being used to return a new injector based on
|
||||
// a struct value instance, if it contains fields that the types of those
|
||||
// are matching with one or more of the `Values` then they are binded
|
||||
// with the injector's `Inject` and `InjectElem` methods.
|
||||
func (d *D) Struct(s interface{}) *StructInjector {
|
||||
if s == nil {
|
||||
return &StructInjector{Has: false}
|
||||
}
|
||||
|
||||
return MakeStructInjector(
|
||||
ValueOf(s),
|
||||
d.hijacker,
|
||||
d.goodFunc,
|
||||
d.Values.CloneWithFieldsOf(s)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Func is being used to return a new injector based on
|
||||
// a function, if it contains input arguments that the types of those
|
||||
// are matching with one or more of the `Values` then they are binded
|
||||
// to the function's input argument when called
|
||||
// with the injector's `Inject` method.
|
||||
func (d *D) Func(fn interface{}) *FuncInjector {
|
||||
if fn == nil {
|
||||
return &FuncInjector{Has: false}
|
||||
}
|
||||
|
||||
return MakeFuncInjector(
|
||||
ValueOf(fn),
|
||||
d.hijacker,
|
||||
d.goodFunc,
|
||||
d.Values...,
|
||||
)
|
||||
}
|
||||
215
hero/di/func.go
Normal file
215
hero/di/func.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package di
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type (
|
||||
targetFuncInput struct {
|
||||
Object *BindObject
|
||||
InputIndex int
|
||||
}
|
||||
|
||||
// FuncInjector keeps the data that are needed in order to do the binding injection
|
||||
// as fast as possible and with the best possible and safest way.
|
||||
FuncInjector struct {
|
||||
// the original function, is being used
|
||||
// only the .Call, which is referring to the same function, always.
|
||||
fn reflect.Value
|
||||
typ reflect.Type
|
||||
goodFunc TypeChecker
|
||||
|
||||
inputs []*targetFuncInput
|
||||
// Length is the number of the valid, final binded input arguments.
|
||||
Length int
|
||||
// Valid is True when `Length` is > 0, it's statically set-ed for
|
||||
// performance reasons.
|
||||
Has bool
|
||||
|
||||
trace string // for debug info.
|
||||
|
||||
lost []*missingInput // Author's note: don't change this to a map.
|
||||
}
|
||||
)
|
||||
|
||||
type missingInput struct {
|
||||
index int // the function's input argument's index.
|
||||
found bool
|
||||
}
|
||||
|
||||
func (s *FuncInjector) miss(index int) {
|
||||
s.lost = append(s.lost, &missingInput{
|
||||
index: index,
|
||||
})
|
||||
}
|
||||
|
||||
// MakeFuncInjector returns a new func injector, which will be the object
|
||||
// that the caller should use to bind input arguments of the "fn" function.
|
||||
//
|
||||
// The hijack and the goodFunc are optional, the "values" is the dependencies collection.
|
||||
func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *FuncInjector {
|
||||
typ := IndirectType(fn.Type())
|
||||
s := &FuncInjector{
|
||||
fn: fn,
|
||||
typ: typ,
|
||||
goodFunc: goodFunc,
|
||||
}
|
||||
|
||||
if !IsFunc(typ) {
|
||||
return s
|
||||
}
|
||||
|
||||
defer s.refresh()
|
||||
|
||||
n := typ.NumIn()
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
inTyp := typ.In(i)
|
||||
|
||||
if hijack != nil {
|
||||
b, ok := hijack(inTyp)
|
||||
|
||||
if ok && b != nil {
|
||||
s.inputs = append(s.inputs, &targetFuncInput{
|
||||
InputIndex: i,
|
||||
Object: b,
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
matched := false
|
||||
|
||||
for j, v := range values {
|
||||
if s.addValue(i, v) {
|
||||
matched = true
|
||||
// remove this value, so it will not try to get binded
|
||||
// again, a next value even with the same type is able to be
|
||||
// used to other input arg. One value per input argument, order
|
||||
// matters if same type of course.
|
||||
//if len(values) > j+1 {
|
||||
values = append(values[:j], values[j+1:]...)
|
||||
//}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !matched {
|
||||
// if no binding for this input argument,
|
||||
// this will make the func injector invalid state,
|
||||
// but before this let's make a list of failed
|
||||
// inputs, so they can be used for a re-try
|
||||
// with different set of binding "values".
|
||||
s.miss(i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *FuncInjector) refresh() {
|
||||
s.Length = len(s.inputs)
|
||||
s.Has = s.Length > 0
|
||||
}
|
||||
|
||||
func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool {
|
||||
defer s.refresh()
|
||||
|
||||
if s.typ.NumIn() < inputIndex {
|
||||
return false
|
||||
}
|
||||
|
||||
inTyp := s.typ.In(inputIndex)
|
||||
|
||||
// the binded values to the func's inputs.
|
||||
b, err := MakeBindObject(value, s.goodFunc)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if b.IsAssignable(inTyp) {
|
||||
// println(inTyp.String() + " is assignable to " + val.Type().String())
|
||||
// fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n",
|
||||
// i, b.Type.String(), value.String(), val.Pointer())
|
||||
s.inputs = append(s.inputs, &targetFuncInput{
|
||||
InputIndex: inputIndex,
|
||||
Object: &b,
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *FuncInjector) Retry(retryFn func(inIndex int, inTyp reflect.Type) (reflect.Value, bool)) bool {
|
||||
for _, missing := range s.lost {
|
||||
if missing.found {
|
||||
continue
|
||||
}
|
||||
|
||||
invalidIndex := missing.index
|
||||
|
||||
inTyp := s.typ.In(invalidIndex)
|
||||
v, ok := retryFn(invalidIndex, inTyp)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if !s.addValue(invalidIndex, v) {
|
||||
continue
|
||||
}
|
||||
|
||||
// if this value completes an invalid index
|
||||
// then remove this from the invalid input indexes.
|
||||
missing.found = true
|
||||
}
|
||||
|
||||
return s.Length == s.typ.NumIn()
|
||||
}
|
||||
|
||||
// String returns a debug trace text.
|
||||
func (s *FuncInjector) String() (trace string) {
|
||||
for i, in := range s.inputs {
|
||||
bindmethodTyp := bindTypeString(in.Object.BindType)
|
||||
typIn := s.typ.In(in.InputIndex)
|
||||
// remember: on methods that are part of a struct (i.e controller)
|
||||
// the input index = 1 is the begggining instead of the 0,
|
||||
// because the 0 is the controller receiver pointer of the method.
|
||||
trace += fmt.Sprintf("[%d] %s binding: '%s' for input position: %d and type: '%s'\n",
|
||||
i+1, bindmethodTyp, in.Object.Type.String(), in.InputIndex, typIn.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Inject accepts an already created slice of input arguments
|
||||
// and fills them, the "ctx" is optional and it's used
|
||||
// on the dependencies that depends on one or more input arguments, these are the "ctx".
|
||||
func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) {
|
||||
args := *in
|
||||
for _, input := range s.inputs {
|
||||
input.Object.Assign(ctx, func(v reflect.Value) {
|
||||
// fmt.Printf("assign input index: %d for value: %v\n",
|
||||
// input.InputIndex, v.String())
|
||||
args[input.InputIndex] = v
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
*in = args
|
||||
}
|
||||
|
||||
// Call calls the "Inject" with a new slice of input arguments
|
||||
// that are computed by the length of the input argument from the MakeFuncInjector's "fn" function.
|
||||
//
|
||||
// If the function needs a receiver, so
|
||||
// the caller should be able to in[0] = receiver before injection,
|
||||
// then the `Inject` method should be used instead.
|
||||
func (s *FuncInjector) Call(ctx ...reflect.Value) []reflect.Value {
|
||||
in := make([]reflect.Value, s.Length, s.Length)
|
||||
s.Inject(&in, ctx...)
|
||||
return s.fn.Call(in)
|
||||
}
|
||||
123
hero/di/object.go
Normal file
123
hero/di/object.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package di
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// BindType is the type of a binded object/value, it's being used to
|
||||
// check if the value is accessible after a function call with a "ctx" when needed ( Dynamic type)
|
||||
// or it's just a struct value (a service | Static type).
|
||||
type BindType uint32
|
||||
|
||||
const (
|
||||
// Static is the simple assignable value, a static value.
|
||||
Static BindType = iota
|
||||
// Dynamic returns a value but it depends on some input arguments from the caller,
|
||||
// on serve time.
|
||||
Dynamic
|
||||
)
|
||||
|
||||
func bindTypeString(typ BindType) string {
|
||||
switch typ {
|
||||
case Dynamic:
|
||||
return "Dynamic"
|
||||
default:
|
||||
return "Static"
|
||||
}
|
||||
}
|
||||
|
||||
// BindObject contains the dependency value's read-only information.
|
||||
// FuncInjector and StructInjector keeps information about their
|
||||
// input arguments/or fields, these properties contain a `BindObject` inside them.
|
||||
type BindObject struct {
|
||||
Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' .
|
||||
Value reflect.Value
|
||||
|
||||
BindType BindType
|
||||
ReturnValue func([]reflect.Value) reflect.Value
|
||||
}
|
||||
|
||||
// MakeBindObject accepts any "v" value, struct, pointer or a function
|
||||
// and a type checker that is used to check if the fields (if "v.elem()" is struct)
|
||||
// or the input arguments (if "v.elem()" is func)
|
||||
// are valid to be included as the final object's dependencies, even if the caller added more
|
||||
// the "di" is smart enough to select what each "v" needs and what not before serve time.
|
||||
func MakeBindObject(v reflect.Value, goodFunc TypeChecker) (b BindObject, err error) {
|
||||
if IsFunc(v) {
|
||||
b.BindType = Dynamic
|
||||
b.ReturnValue, b.Type, err = MakeReturnValue(v, goodFunc)
|
||||
} else {
|
||||
b.BindType = Static
|
||||
b.Type = v.Type()
|
||||
b.Value = v
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var errBad = errors.New("bad")
|
||||
|
||||
// MakeReturnValue takes any function
|
||||
// that accept custom values and returns something,
|
||||
// it returns a binder function, which accepts a slice of reflect.Value
|
||||
// and returns a single one reflect.Value for that.
|
||||
// It's being used to resolve the input parameters on a "x" consumer faster.
|
||||
//
|
||||
// The "fn" can have the following form:
|
||||
// `func(myService) MyViewModel`.
|
||||
//
|
||||
// The return type of the "fn" should be a value instance, not a pointer, for your own protection.
|
||||
// The binder function should return only one value.
|
||||
func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Value) reflect.Value, reflect.Type, error) {
|
||||
typ := IndirectType(fn.Type())
|
||||
|
||||
// invalid if not a func.
|
||||
if typ.Kind() != reflect.Func {
|
||||
return nil, typ, errBad
|
||||
}
|
||||
|
||||
// invalid if not returns one single value.
|
||||
if typ.NumOut() != 1 {
|
||||
return nil, typ, errBad
|
||||
}
|
||||
|
||||
if goodFunc != nil {
|
||||
if !goodFunc(typ) {
|
||||
return nil, typ, errBad
|
||||
}
|
||||
}
|
||||
|
||||
outTyp := typ.Out(0)
|
||||
zeroOutVal := reflect.New(outTyp).Elem()
|
||||
|
||||
bf := func(ctxValue []reflect.Value) reflect.Value {
|
||||
results := fn.Call(ctxValue)
|
||||
if len(results) == 0 {
|
||||
return zeroOutVal
|
||||
}
|
||||
|
||||
v := results[0]
|
||||
if !v.IsValid() {
|
||||
return zeroOutVal
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
return bf, outTyp, nil
|
||||
}
|
||||
|
||||
// IsAssignable checks if "to" type can be used as "b.Value/ReturnValue".
|
||||
func (b *BindObject) IsAssignable(to reflect.Type) bool {
|
||||
return equalTypes(b.Type, to)
|
||||
}
|
||||
|
||||
// Assign sets the values to a setter, "toSetter" contains the setter, so the caller
|
||||
// can use it for multiple and different structs/functions as well.
|
||||
func (b *BindObject) Assign(ctx []reflect.Value, toSetter func(reflect.Value)) {
|
||||
if b.BindType == Dynamic {
|
||||
toSetter(b.ReturnValue(ctx))
|
||||
return
|
||||
}
|
||||
toSetter(b.Value)
|
||||
}
|
||||
202
hero/di/reflect.go
Normal file
202
hero/di/reflect.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package di
|
||||
|
||||
import "reflect"
|
||||
|
||||
// EmptyIn is just an empty slice of reflect.Value.
|
||||
var EmptyIn = []reflect.Value{}
|
||||
|
||||
// IsZero returns true if a value is nil.
|
||||
// Remember; fields to be checked should be exported otherwise it returns false.
|
||||
// Notes for users:
|
||||
// Boolean's zero value is false, even if not set-ed.
|
||||
// UintXX are not zero on 0 because they are pointers to.
|
||||
func IsZero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
zero := true
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
zero = zero && IsZero(v.Field(i))
|
||||
}
|
||||
|
||||
if typ := v.Type(); typ != nil && v.IsValid() {
|
||||
f, ok := typ.MethodByName("IsZero")
|
||||
// if not found
|
||||
// if has input arguments (1 is for the value receiver, so > 1 for the actual input args)
|
||||
// if output argument is not boolean
|
||||
// then skip this IsZero user-defined function.
|
||||
if !ok || f.Type.NumIn() > 1 || f.Type.NumOut() != 1 && f.Type.Out(0).Kind() != reflect.Bool {
|
||||
return zero
|
||||
}
|
||||
|
||||
method := v.Method(f.Index)
|
||||
// no needed check but:
|
||||
if method.IsValid() && !method.IsNil() {
|
||||
// it shouldn't panic here.
|
||||
zero = method.Call(EmptyIn)[0].Interface().(bool)
|
||||
}
|
||||
}
|
||||
|
||||
return zero
|
||||
case reflect.Func, reflect.Map, reflect.Slice:
|
||||
return v.IsNil()
|
||||
case reflect.Array:
|
||||
zero := true
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
zero = zero && IsZero(v.Index(i))
|
||||
}
|
||||
return zero
|
||||
}
|
||||
// if not any special type then use the reflect's .Zero
|
||||
// usually for fields, but remember if it's boolean and it's false
|
||||
// then it's zero, even if set-ed.
|
||||
|
||||
if !v.CanInterface() {
|
||||
// if can't interface, i.e return value from unexported field or method then return false
|
||||
return false
|
||||
}
|
||||
zero := reflect.Zero(v.Type())
|
||||
return v.Interface() == zero.Interface()
|
||||
}
|
||||
|
||||
func IndirectValue(v reflect.Value) reflect.Value {
|
||||
return reflect.Indirect(v)
|
||||
}
|
||||
|
||||
func ValueOf(o interface{}) reflect.Value {
|
||||
if v, ok := o.(reflect.Value); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return reflect.ValueOf(o)
|
||||
}
|
||||
|
||||
func ValuesOf(valuesAsInterface []interface{}) (values []reflect.Value) {
|
||||
for _, v := range valuesAsInterface {
|
||||
values = append(values, ValueOf(v))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func IndirectType(typ reflect.Type) reflect.Type {
|
||||
switch typ.Kind() {
|
||||
case reflect.Ptr, reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
||||
return typ.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
func goodVal(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
|
||||
if v.IsNil() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return v.IsValid()
|
||||
}
|
||||
|
||||
// IsFunc returns true if the passed type is function.
|
||||
func IsFunc(kindable interface {
|
||||
Kind() reflect.Kind
|
||||
}) bool {
|
||||
return kindable.Kind() == reflect.Func
|
||||
}
|
||||
|
||||
func equalTypes(got reflect.Type, expected reflect.Type) bool {
|
||||
if got == expected {
|
||||
return true
|
||||
}
|
||||
// if accepts an interface, check if the given "got" type does
|
||||
// implement this "expected" user handler's input argument.
|
||||
if expected.Kind() == reflect.Interface {
|
||||
// fmt.Printf("expected interface = %s and got to set on the arg is: %s\n", expected.String(), got.String())
|
||||
return got.Implements(expected)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// for controller's fields only.
|
||||
func structFieldIgnored(f reflect.StructField) bool {
|
||||
if !f.Anonymous {
|
||||
return true // if not anonymous(embedded), ignore it.
|
||||
}
|
||||
|
||||
s := f.Tag.Get("ignore")
|
||||
return s == "true" // if has an ignore tag then ignore it.
|
||||
}
|
||||
|
||||
type field struct {
|
||||
Type reflect.Type
|
||||
Name string // the actual name.
|
||||
Index []int // the index of the field, slice if it's part of a embedded struct
|
||||
CanSet bool // is true if it's exported.
|
||||
|
||||
// this could be empty, but in our cases it's not,
|
||||
// it's filled with the bind object (as service which means as static value)
|
||||
// and it's filled from the lookupFields' caller.
|
||||
AnyValue reflect.Value
|
||||
}
|
||||
|
||||
// NumFields returns the total number of fields, and the embedded, even if the embedded struct is not exported,
|
||||
// it will check for its exported fields.
|
||||
func NumFields(elemTyp reflect.Type, skipUnexported bool) int {
|
||||
return len(lookupFields(elemTyp, skipUnexported, nil))
|
||||
}
|
||||
|
||||
func lookupFields(elemTyp reflect.Type, skipUnexported bool, parentIndex []int) (fields []field) {
|
||||
if elemTyp.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
|
||||
for i, n := 0, elemTyp.NumField(); i < n; i++ {
|
||||
f := elemTyp.Field(i)
|
||||
|
||||
if IndirectType(f.Type).Kind() == reflect.Struct &&
|
||||
!structFieldIgnored(f) {
|
||||
fields = append(fields, lookupFields(f.Type, skipUnexported, append(parentIndex, i))...)
|
||||
continue
|
||||
}
|
||||
|
||||
// skip unexported fields here,
|
||||
// after the check for embedded structs, these can be binded if their
|
||||
// fields are exported.
|
||||
isExported := f.PkgPath == ""
|
||||
if skipUnexported && !isExported {
|
||||
continue
|
||||
}
|
||||
|
||||
index := []int{i}
|
||||
if len(parentIndex) > 0 {
|
||||
index = append(parentIndex, i)
|
||||
}
|
||||
|
||||
field := field{
|
||||
Type: f.Type,
|
||||
Name: f.Name,
|
||||
Index: index,
|
||||
CanSet: isExported,
|
||||
}
|
||||
|
||||
fields = append(fields, field)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LookupNonZeroFieldsValues lookup for filled fields based on the "v" struct value instance.
|
||||
// It returns a slice of reflect.Value (same type as `Values`) that can be binded,
|
||||
// like the end-developer's custom values.
|
||||
func LookupNonZeroFieldsValues(v reflect.Value, skipUnexported bool) (bindValues []reflect.Value) {
|
||||
elem := IndirectValue(v)
|
||||
fields := lookupFields(IndirectType(v.Type()), skipUnexported, nil)
|
||||
|
||||
for _, f := range fields {
|
||||
if fieldVal := elem.FieldByIndex(f.Index); /*f.Type.Kind() == reflect.Ptr &&*/
|
||||
!IsZero(fieldVal) {
|
||||
bindValues = append(bindValues, fieldVal)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
201
hero/di/struct.go
Normal file
201
hero/di/struct.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package di
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Scope uint8
|
||||
|
||||
const (
|
||||
Stateless Scope = iota
|
||||
Singleton
|
||||
)
|
||||
|
||||
type (
|
||||
targetStructField struct {
|
||||
Object *BindObject
|
||||
FieldIndex []int
|
||||
}
|
||||
|
||||
// StructInjector keeps the data that are needed in order to do the binding injection
|
||||
// as fast as possible and with the best possible and safest way.
|
||||
StructInjector struct {
|
||||
initRef reflect.Value
|
||||
initRefAsSlice []reflect.Value // useful when the struct is passed on a func as input args via reflection.
|
||||
elemType reflect.Type
|
||||
//
|
||||
fields []*targetStructField
|
||||
// is true when contains bindable fields and it's a valid target struct,
|
||||
// it maybe 0 but struct may contain unexported fields or exported but no bindable (Stateless)
|
||||
// see `setState`.
|
||||
Has bool
|
||||
CanInject bool // if any bindable fields when the state is NOT singleton.
|
||||
Scope Scope
|
||||
}
|
||||
)
|
||||
|
||||
func (s *StructInjector) countBindType(typ BindType) (n int) {
|
||||
for _, f := range s.fields {
|
||||
if f.Object.BindType == typ {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MakeStructInjector returns a new struct injector, which will be the object
|
||||
// that the caller should use to bind exported fields or
|
||||
// embedded unexported fields that contain exported fields
|
||||
// of the "v" struct value or pointer.
|
||||
//
|
||||
// The hijack and the goodFunc are optional, the "values" is the dependencies collection.
|
||||
func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *StructInjector {
|
||||
s := &StructInjector{
|
||||
initRef: v,
|
||||
initRefAsSlice: []reflect.Value{v},
|
||||
elemType: IndirectType(v.Type()),
|
||||
}
|
||||
|
||||
fields := lookupFields(s.elemType, true, nil)
|
||||
for _, f := range fields {
|
||||
if hijack != nil {
|
||||
if b, ok := hijack(f.Type); ok && b != nil {
|
||||
s.fields = append(s.fields, &targetStructField{
|
||||
FieldIndex: f.Index,
|
||||
Object: b,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for _, val := range values {
|
||||
// the binded values to the struct's fields.
|
||||
b, err := MakeBindObject(val, goodFunc)
|
||||
|
||||
if err != nil {
|
||||
return s // if error stop here.
|
||||
}
|
||||
|
||||
if b.IsAssignable(f.Type) {
|
||||
// fmt.Printf("bind the object to the field: %s at index: %#v and type: %s\n", f.Name, f.Index, f.Type.String())
|
||||
s.fields = append(s.fields, &targetStructField{
|
||||
FieldIndex: f.Index,
|
||||
Object: &b,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.Has = len(s.fields) > 0
|
||||
// set the overall state of this injector.
|
||||
s.fillStruct()
|
||||
s.setState()
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// set the state, once.
|
||||
// Here the "initRef" have already the static bindings and the manually-filled fields.
|
||||
func (s *StructInjector) setState() {
|
||||
// note for zero length of struct's fields:
|
||||
// if struct doesn't contain any field
|
||||
// so both of the below variables will be 0,
|
||||
// so it's a singleton.
|
||||
// At the other hand the `s.HasFields` maybe false
|
||||
// but the struct may contain UNEXPORTED fields or non-bindable fields (request-scoped on both cases)
|
||||
// so a new controller/struct at the caller side should be initialized on each request,
|
||||
// we should not depend on the `HasFields` for singleton or no, this is the reason I
|
||||
// added the `.State` now.
|
||||
|
||||
staticBindingsFieldsLength := s.countBindType(Static)
|
||||
allStructFieldsLength := NumFields(s.elemType, false)
|
||||
// check if unexported(and exported) fields are set-ed manually or via binding (at this time we have all fields set-ed inside the "initRef")
|
||||
// i.e &Controller{unexportedField: "my value"}
|
||||
// or dependencies values = "my value" and Controller struct {Field string}
|
||||
// if so then set the temp staticBindingsFieldsLength to that number, so for example:
|
||||
// if static binding length is 0
|
||||
// but an unexported field is set-ed then act that as singleton.
|
||||
if allStructFieldsLength > staticBindingsFieldsLength {
|
||||
structFieldsUnexportedNonZero := LookupNonZeroFieldsValues(s.initRef, false)
|
||||
staticBindingsFieldsLength = len(structFieldsUnexportedNonZero)
|
||||
}
|
||||
|
||||
// println("staticBindingsFieldsLength: ", staticBindingsFieldsLength)
|
||||
// println("allStructFieldsLength: ", allStructFieldsLength)
|
||||
|
||||
// if the number of static values binded is equal to the
|
||||
// total struct's fields(including unexported fields this time) then set as singleton.
|
||||
if staticBindingsFieldsLength == allStructFieldsLength {
|
||||
s.Scope = Singleton
|
||||
// the default is `Stateless`, which means that a new instance should be created
|
||||
// on each inject action by the caller.
|
||||
return
|
||||
}
|
||||
|
||||
s.CanInject = s.Scope == Stateless && s.Has
|
||||
}
|
||||
|
||||
// fill the static bindings values once.
|
||||
func (s *StructInjector) fillStruct() {
|
||||
if !s.Has {
|
||||
return
|
||||
}
|
||||
// if field is Static then set it to the value that passed by the caller,
|
||||
// so will have the static bindings already and we can just use that value instead
|
||||
// of creating new instance.
|
||||
destElem := IndirectValue(s.initRef)
|
||||
for _, f := range s.fields {
|
||||
// if field is Static then set it to the value that passed by the caller,
|
||||
// so will have the static bindings already and we can just use that value instead
|
||||
// of creating new instance.
|
||||
if f.Object.BindType == Static {
|
||||
destElem.FieldByIndex(f.FieldIndex).Set(f.Object.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a debug trace message.
|
||||
func (s *StructInjector) String() (trace string) {
|
||||
for i, f := range s.fields {
|
||||
elemField := s.elemType.FieldByIndex(f.FieldIndex)
|
||||
trace += fmt.Sprintf("[%d] %s binding: '%s' for field '%s %s'\n",
|
||||
i+1, bindTypeString(f.Object.BindType), f.Object.Type.String(),
|
||||
elemField.Name, elemField.Type.String())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *StructInjector) Inject(dest interface{}, ctx ...reflect.Value) {
|
||||
if dest == nil {
|
||||
return
|
||||
}
|
||||
|
||||
v := IndirectValue(ValueOf(dest))
|
||||
s.InjectElem(v, ctx...)
|
||||
}
|
||||
|
||||
func (s *StructInjector) InjectElem(destElem reflect.Value, ctx ...reflect.Value) {
|
||||
for _, f := range s.fields {
|
||||
f.Object.Assign(ctx, func(v reflect.Value) {
|
||||
destElem.FieldByIndex(f.FieldIndex).Set(v)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StructInjector) Acquire() reflect.Value {
|
||||
if s.Scope == Singleton {
|
||||
return s.initRef
|
||||
}
|
||||
return reflect.New(s.elemType)
|
||||
}
|
||||
|
||||
func (s *StructInjector) AcquireSlice() []reflect.Value {
|
||||
if s.Scope == Singleton {
|
||||
return s.initRefAsSlice
|
||||
}
|
||||
return []reflect.Value{reflect.New(s.elemType)}
|
||||
}
|
||||
126
hero/di/values.go
Normal file
126
hero/di/values.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package di
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Values is a shortcut of []reflect.Value,
|
||||
// it makes easier to remove and add dependencies.
|
||||
type Values []reflect.Value
|
||||
|
||||
// NewValues returns new empty (dependencies) values.
|
||||
func NewValues() Values {
|
||||
return Values{}
|
||||
}
|
||||
|
||||
// Clone returns a copy of the current values.
|
||||
func (bv Values) Clone() Values {
|
||||
if n := len(bv); n > 0 {
|
||||
values := make(Values, n, n)
|
||||
copy(values, bv)
|
||||
return values
|
||||
}
|
||||
|
||||
return NewValues()
|
||||
}
|
||||
|
||||
// CloneWithFieldsOf will return a copy of the current values
|
||||
// plus the "s" struct's fields that are filled(non-zero) by the caller.
|
||||
func (bv Values) CloneWithFieldsOf(s interface{}) Values {
|
||||
values := bv.Clone()
|
||||
|
||||
// add the manual filled fields to the dependencies.
|
||||
filledFieldValues := LookupNonZeroFieldsValues(ValueOf(s), true)
|
||||
values = append(values, filledFieldValues...)
|
||||
return values
|
||||
}
|
||||
|
||||
// Len returns the length of the current "bv" values slice.
|
||||
func (bv Values) Len() int {
|
||||
return len(bv)
|
||||
}
|
||||
|
||||
// Add adds values as dependencies, if the struct's fields
|
||||
// or the function's input arguments needs them, they will be defined as
|
||||
// bindings (at build-time) and they will be used (at serve-time).
|
||||
func (bv *Values) Add(values ...interface{}) {
|
||||
bv.AddValues(ValuesOf(values)...)
|
||||
}
|
||||
|
||||
// AddValues same as `Add` but accepts reflect.Value dependencies instead of interface{}
|
||||
// and appends them to the list if they pass some checks.
|
||||
func (bv *Values) AddValues(values ...reflect.Value) {
|
||||
for _, v := range values {
|
||||
if !goodVal(v) {
|
||||
continue
|
||||
}
|
||||
*bv = append(*bv, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unbinds a binding value based on the type,
|
||||
// it returns true if at least one field is not binded anymore.
|
||||
//
|
||||
// The "n" indicates the number of elements to remove, if <=0 then it's 1,
|
||||
// this is useful because you may have bind more than one value to two or more fields
|
||||
// with the same type.
|
||||
func (bv *Values) Remove(value interface{}, n int) bool {
|
||||
return bv.remove(reflect.TypeOf(value), n)
|
||||
}
|
||||
|
||||
func (bv *Values) remove(typ reflect.Type, n int) (ok bool) {
|
||||
input := *bv
|
||||
for i, in := range input {
|
||||
if equalTypes(in.Type(), typ) {
|
||||
ok = true
|
||||
input = input[:i+copy(input[i:], input[i+1:])]
|
||||
if n > 1 {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
*bv = input
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Has returns true if a binder responsible to
|
||||
// bind and return a type of "typ" is already registered to this controller.
|
||||
func (bv Values) Has(value interface{}) bool {
|
||||
return bv.valueTypeExists(reflect.TypeOf(value))
|
||||
}
|
||||
|
||||
func (bv Values) valueTypeExists(typ reflect.Type) bool {
|
||||
for _, in := range bv {
|
||||
if equalTypes(in.Type(), typ) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AddOnce binds a value to the controller's field with the same type,
|
||||
// if it's not binded already.
|
||||
//
|
||||
// Returns false if binded already or the value is not the proper one for binding,
|
||||
// otherwise true.
|
||||
func (bv *Values) AddOnce(value interface{}) bool {
|
||||
return bv.addIfNotExists(reflect.ValueOf(value))
|
||||
}
|
||||
|
||||
func (bv *Values) addIfNotExists(v reflect.Value) bool {
|
||||
var (
|
||||
typ = v.Type() // no element, raw things here.
|
||||
)
|
||||
|
||||
if !goodVal(v) {
|
||||
return false
|
||||
}
|
||||
|
||||
if bv.valueTypeExists(typ) {
|
||||
return false
|
||||
}
|
||||
|
||||
bv.Add(v)
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user