1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-19 02:47:04 +00:00

OK, my dream-idea is implemented. TODO: Some examples and doc.go is not updated yet, comments on the mvc/di subpackage, the tutorial/vuejs-todo-mvc is running but not finished yet (it's using browser's localstorage and it should be replaced by the http requests that are registered via iris mvc

Former-commit-id: 0ea7e01ce1d78bcb78b40f3b0f5c03ad7c9abaea
This commit is contained in:
Gerasimos (Makis) Maropoulos
2017-12-16 06:38:28 +02:00
parent 55dfd195e0
commit 34664aa311
41 changed files with 437 additions and 408 deletions

92
mvc/di/di.go Normal file
View File

@@ -0,0 +1,92 @@
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
)
// 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 explaination.
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 explaination.
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 {
clone := New()
clone.hijacker = d.hijacker
clone.goodFunc = d.goodFunc
// copy the current dynamic bindings (func binders)
// and static struct bindings (services) to this new child.
if n := len(d.Values); n > 0 {
values := make(Values, n, n)
copy(values, d.Values)
clone.Values = values
}
return clone
}
// 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 nil
}
v := ValueOf(s)
return MakeStructInjector(
v,
d.hijacker,
d.goodFunc,
d.Values...,
)
}
// 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 `Fill` method.
func (d *D) Func(fn interface{}) *FuncInjector {
return MakeFuncInjector(
ValueOf(fn),
d.hijacker,
d.goodFunc,
d.Values...,
)
}

107
mvc/di/func.go Normal file
View File

@@ -0,0 +1,107 @@
package di
import "reflect"
type (
targetFuncInput struct {
Object *BindObject
InputIndex int
}
FuncInjector struct {
// the original function, is being used
// only the .Call, which is refering to the same function, always.
fn reflect.Value
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.
Valid bool //
}
)
func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *FuncInjector {
typ := IndirectType(fn.Type())
s := &FuncInjector{
fn: fn,
}
if !IsFunc(typ) {
return s
}
n := typ.NumIn()
// function input can have many values of the same types,
// so keep track of them in order to not set a func input to a next bind value,
// i.e (string, string) with two different binder funcs because of the different param's name.
consumedValues := make(map[int]bool, n)
for i := 0; i < n; i++ {
inTyp := typ.In(i)
if hijack != nil {
if b, ok := hijack(inTyp); ok && b != nil {
s.inputs = append(s.inputs, &targetFuncInput{
InputIndex: i,
Object: b,
})
continue
}
}
for valIdx, val := range values {
if _, shouldSkip := consumedValues[valIdx]; shouldSkip {
continue
}
inTyp := typ.In(i)
// the binded values to the func's inputs.
b, err := MakeBindObject(val, goodFunc)
if err != nil {
return s // if error stop here.
}
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(), val.String(), val.Pointer())
s.inputs = append(s.inputs, &targetFuncInput{
InputIndex: i,
Object: &b,
})
consumedValues[valIdx] = true
break
}
}
}
// s.Length = n
s.Length = len(s.inputs)
s.Valid = s.Length > 0
return s
}
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
}
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)
}

97
mvc/di/object.go Normal file
View File

@@ -0,0 +1,97 @@
package di
import (
"errors"
"reflect"
)
type BindType uint32
const (
Static BindType = iota // simple assignable value, a static value.
Dynamic // dynamic value, depends on some input arguments from the caller.
)
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
}
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
}
func (b *BindObject) IsAssignable(to reflect.Type) bool {
return equalTypes(b.Type, to)
}
func (b *BindObject) Assign(ctx []reflect.Value, toSetter func(reflect.Value)) {
if b.BindType == Dynamic {
toSetter(b.ReturnValue(ctx))
return
}
toSetter(b.Value)
}

180
mvc/di/reflect.go Normal file
View File

@@ -0,0 +1,180 @@
package di
import "reflect"
var emptyIn = []reflect.Value{}
// IsZero returns true if a value is nil, remember boolean's false is zero.
// Remember; fields to be checked should be exported otherwise it returns false.
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 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()
}
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
Index []int // the index of the field, slice if it's part of a embedded struct
Name string // the actual name
// 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
}
func lookupFields(elemTyp reflect.Type, 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, append(parentIndex, i))...)
continue
}
// skip unexported fields here,
// after the check for embedded structs, these can be binded if their
// fields are exported.
if f.PkgPath != "" {
continue
}
index := []int{i}
if len(parentIndex) > 0 {
index = append(parentIndex, i)
}
field := field{
Type: f.Type,
Name: f.Name,
Index: index,
}
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) (bindValues []reflect.Value) {
elem := IndirectValue(v)
fields := lookupFields(IndirectType(v.Type()), nil)
for _, f := range fields {
if fieldVal := elem.FieldByIndex(f.Index); f.Type.Kind() == reflect.Ptr && !IsZero(fieldVal) {
bindValues = append(bindValues, fieldVal)
}
}
return
}

84
mvc/di/struct.go Normal file
View File

@@ -0,0 +1,84 @@
package di
import "reflect"
type (
targetStructField struct {
Object *BindObject
FieldIndex []int
}
StructInjector struct {
elemType reflect.Type
//
fields []*targetStructField
Valid bool // is True when contains fields and it's a valid target struct.
}
)
func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *StructInjector {
s := &StructInjector{
elemType: IndirectType(v.Type()),
}
fields := lookupFields(s.elemType, 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.Valid = len(s.fields) > 0
return s
}
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) {
// fmt.Printf("%s for %s at index: %d\n", destElem.Type().String(), f.Object.Type.String(), f.FieldIndex)
destElem.FieldByIndex(f.FieldIndex).Set(v)
})
}
}
func (s *StructInjector) New(ctx ...reflect.Value) reflect.Value {
dest := reflect.New(s.elemType)
s.InjectElem(dest, ctx...)
return dest
}

98
mvc/di/values.go Normal file
View File

@@ -0,0 +1,98 @@
package di
import "reflect"
type Values []reflect.Value
func NewValues() Values {
return Values{}
}
// Add binds values to this controller, if you want to share
// binding values between controllers use the Engine's `Bind` function instead.
func (bv *Values) Add(values ...interface{}) {
for _, val := range values {
bv.AddValue(reflect.ValueOf(val))
}
}
// AddValue same as `Add` but accepts reflect.Value
// instead.
func (bv *Values) AddValue(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 {
input := *bv
for _, in := range input {
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.AddValue(v)
return true
}