1
0
mirror of https://github.com/kataras/iris.git synced 2026-05-07 14:43:49 +00:00

remove the old 'mvc' folder - examples are not changed yet - add the 'di' package inside the mvc2 package - which will be renamed to 'mvc' on the next commit - new mvc.Application and some dublications removed - The new version will be version 9 because it will contain breaking changes (not to the end-developer's controllers but to the API they register them) - get ready for 'Christmas Edition' for believers

Former-commit-id: c7114233dee90ee308c0a3e77ec2ad0c361094b8
This commit is contained in:
Gerasimos (Makis) Maropoulos
2017-12-15 20:28:06 +02:00
parent 4e15f4ea88
commit 55dfd195e0
48 changed files with 984 additions and 3591 deletions

View File

@@ -1,7 +1,7 @@
package mvc2
import (
"github.com/kataras/di"
"github.com/kataras/iris/mvc2/di"
"reflect"
)

View File

@@ -3,8 +3,9 @@ package mvc2
import (
"fmt"
"reflect"
"strings"
"github.com/kataras/di"
"github.com/kataras/iris/mvc2/di"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
@@ -75,9 +76,9 @@ func (c *C) EndRequest(ctx context.Context) {}
// ControllerActivator returns a new controller type info description.
// Its functionality can be overriden by the end-dev.
type ControllerActivator struct {
// the router is used on the `Activate` and can be used by end-dev on the `OnActivate`
// the router is used on the `Activate` and can be used by end-dev on the `BeforeActivate`
// to register any custom controller's functions as handlers but we will need it here
// in order to not create a new type like `ActivationPayload` for the `OnActivate`.
// in order to not create a new type like `ActivationPayload` for the `BeforeActivate`.
Router router.Party
// initRef BaseController // the BaseController as it's passed from the end-dev.
@@ -88,7 +89,7 @@ type ControllerActivator struct {
FullName string
// the methods names that is already binded to a handler,
// the BeginRequest, EndRequest and OnActivate are reserved by the internal implementation.
// the BeginRequest, EndRequest and BeforeActivate are reserved by the internal implementation.
reservedMethods []string
// the bindings that comes from the Engine and the controller's filled fields if any.
@@ -100,6 +101,16 @@ type ControllerActivator struct {
injector *di.StructInjector
}
func getNameOf(typ reflect.Type) string {
elemTyp := di.IndirectType(typ)
typName := elemTyp.Name()
pkgPath := elemTyp.PkgPath()
fullname := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + typName
return fullname
}
func newControllerActivator(router router.Party, controller interface{}, d *di.D) *ControllerActivator {
var (
val = reflect.ValueOf(controller)
@@ -116,7 +127,7 @@ func newControllerActivator(router router.Party, controller interface{}, d *di.D
// the end-developer when declaring the controller,
// activate listeners needs them in order to know if something set-ed already or not,
// look `BindTypeExists`.
d.Values = append(lookupNonZeroFieldsValues(val), d.Values...)
d.Values = append(di.LookupNonZeroFieldsValues(val), d.Values...)
c := &ControllerActivator{
// give access to the Router to the end-devs if they need it for some reason,
@@ -142,7 +153,7 @@ func newControllerActivator(router router.Party, controller interface{}, d *di.D
}
func whatReservedMethods(typ reflect.Type) []string {
methods := []string{"OnActivate"}
methods := []string{"BeforeActivate"}
if isBaseController(typ) {
methods = append(methods, "BeginRequest", "EndRequest")
}
@@ -182,7 +193,6 @@ func (c *ControllerActivator) parseMethods() {
}
func (c *ControllerActivator) activate() {
c.injector = c.Dependencies.Struct(c.Value)
c.parseMethods()
}
@@ -233,18 +243,40 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...)
// get the function's input arguments' bindings.
funcDependencies := c.Dependencies.Clone()
funcDependencies.Add(pathParams...)
funcDependencies.AddValue(pathParams...)
funcInjector := funcDependencies.Func(m.Func)
// we will make use of 'n' to make a slice of reflect.Value
// to pass into if the function has input arguments that
// are will being filled by the funcDependencies.
n := len(funcIn)
// the element value, not the pointer, wil lbe used to create a
// new controller on each incoming request.
elemTyp := indirectTyp(c.Type)
implementsBase := isBaseController(c.Type)
// Remember:
// we cannot simply do that and expect to work:
// hasStructInjector = c.injector != nil && c.injector.Valid
// hasFuncInjector = funcInjector != nil && funcInjector.Valid
// because
// the `Handle` can be called from `BeforeActivate` callbacks
// and before activation, the c.injector is nil because
// we may not have the dependencies binded yet. But if `c.injector.Valid`
// inside the Handelr works because it's set on the `activate()` method.
// To solve this we can make check on the FIRST `Handle`,
// if c.injector is nil, then set it with the current bindings,
// so the user should bind the dependencies needed before the `Handle`
// this is a logical flow, so we will choose that one ->
if c.injector == nil {
c.injector = c.Dependencies.Struct(c.Value)
}
var (
hasStructInjector = c.injector != nil && c.injector.Valid
hasFuncInjector = funcInjector != nil && funcInjector.Valid
implementsBase = isBaseController(c.Type)
// we will make use of 'n' to make a slice of reflect.Value
// to pass into if the function has input arguments that
// are will being filled by the funcDependencies.
n = len(funcIn)
elemTyp = di.IndirectType(c.Type)
)
handler := func(ctx context.Context) {
ctrl := reflect.New(elemTyp)
@@ -263,11 +295,11 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
defer b.EndRequest(ctx)
}
if !c.injector.Valid && !funcInjector.Valid {
if !hasStructInjector && !hasFuncInjector {
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
} else {
ctxValue := reflect.ValueOf(ctx)
if c.injector.Valid {
if hasStructInjector {
elem := ctrl.Elem()
c.injector.InjectElem(elem, ctxValue)
if ctx.IsStopped() {
@@ -276,13 +308,13 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
// we do this in order to reduce in := make...
// if not func input binders, we execute the handler with empty input args.
if !funcInjector.Valid {
if !hasFuncInjector {
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
}
}
// otherwise, it has one or more valid input binders,
// make the input and call the func using those.
if funcInjector.Valid {
if hasFuncInjector {
in := make([]reflect.Value, n, n)
in[0] = ctrl
funcInjector.Inject(&in, ctxValue)

View File

@@ -24,12 +24,11 @@ func (c *testControllerHandle) BeginRequest(ctx iris.Context) {
c.reqField = ctx.URLParam("reqfield")
}
func (c *testControllerHandle) OnActivate(t *ControllerActivator) { // OnActivate(t *mvc.TController) {
// t.Handle("GET", "/", "Get")
t.Handle("GET", "/histatic", "HiStatic")
t.Handle("GET", "/hiservice", "HiService")
t.Handle("GET", "/hiparam/{ps:string}", "HiParamBy")
t.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy")
func (c *testControllerHandle) BeforeActivate(ca *ControllerActivator) { // BeforeActivate(t *mvc.TController) {
ca.Handle("GET", "/histatic", "HiStatic")
ca.Handle("GET", "/hiservice", "HiService")
ca.Handle("GET", "/hiparam/{ps:string}", "HiParamBy")
ca.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy")
}
func (c *testControllerHandle) HiStatic() string {
@@ -51,8 +50,10 @@ func (c *testControllerHandle) HiParamEmptyInputBy() string {
func TestControllerHandle(t *testing.T) {
app := iris.New()
m := New()
m.Bind(&TestServiceImpl{prefix: "service:"}).Controller(app, new(testControllerHandle))
m := NewEngine()
m.Dependencies.Add(&TestServiceImpl{prefix: "service:"})
m.Controller(app, new(testControllerHandle))
e := httptest.New(t, app)
// test the index, is not part of the current package's implementation but do it.

View File

@@ -63,7 +63,7 @@ func (c *testControllerAny) Any() {
func TestControllerMethodFuncs(t *testing.T) {
app := iris.New()
m := New()
m := NewEngine()
m.Controller(app, new(testController))
m.Controller(app.Party("/all"), new(testControllerAll))
m.Controller(app.Party("/any"), new(testControllerAny))
@@ -113,7 +113,7 @@ func (c *testControllerBeginAndEndRequestFunc) Post() {
func TestControllerBeginAndEndRequestFunc(t *testing.T) {
app := iris.New()
New().Controller(app.Party("/profile/{username}"), new(testControllerBeginAndEndRequestFunc))
NewEngine().Controller(app.Party("/profile/{username}"), new(testControllerBeginAndEndRequestFunc))
e := httptest.New(t, app)
usernames := []string{
@@ -156,7 +156,7 @@ func TestControllerBeginAndEndRequestFuncBindMiddleware(t *testing.T) {
ctx.Writef("forbidden")
}
New().Controller(app.Party("/profile/{username}", middlewareCheck),
NewEngine().Controller(app.Party("/profile/{username}", middlewareCheck),
new(testControllerBeginAndEndRequestFunc))
e := httptest.New(t, app)
@@ -230,7 +230,7 @@ func (c *testControllerEndRequestAwareness) EndRequest(ctx context.Context) {
func TestControllerEndRequestAwareness(t *testing.T) {
app := iris.New()
New().Controller(app.Party("/era/{username}"), new(testControllerEndRequestAwareness))
NewEngine().Controller(app.Party("/era/{username}"), new(testControllerEndRequestAwareness))
e := httptest.New(t, app)
usernames := []string{
@@ -284,8 +284,8 @@ func TestControllerBind(t *testing.T) {
myTitlePtr := &testBindType{title: t1}
// test bind value to value of the correct type
myTitleV := testBindType{title: t2}
m := New()
m.Bind(myTitlePtr, myTitleV)
m := NewEngine()
m.Dependencies.Add(myTitlePtr, myTitleV)
// or just app
m.Controller(app.Party("/"), new(testControllerBindStruct))
m.Controller(app.Party("/deep"), new(testControllerBindDeep))
@@ -345,8 +345,9 @@ func TestControllerInsideControllerRecursively(t *testing.T) {
)
app := iris.New()
New().Bind(&testBindType{title: title}).
Controller(app.Party("/user/{username}"), new(testCtrl0))
m := NewEngine()
m.Dependencies.Add(&testBindType{title: title})
m.Controller(app.Party("/user/{username}"), new(testCtrl0))
e := httptest.New(t, app)
e.GET("/user/" + username).Expect().
@@ -378,7 +379,7 @@ func (c *testControllerRelPathFromFunc) GetSomethingByElseThisBy(bool, int) {} /
func TestControllerRelPathFromFunc(t *testing.T) {
app := iris.New()
New().Controller(app, new(testControllerRelPathFromFunc))
NewEngine().Controller(app, new(testControllerRelPathFromFunc))
e := httptest.New(t, app)
e.GET("/").Expect().Status(iris.StatusOK).
@@ -420,12 +421,8 @@ type testControllerActivateListener struct {
TitlePointer *testBindType
}
func (c *testControllerActivateListener) OnActivate(ca *ControllerActivator) {
if !ca.Dependencies.BindExists(&testBindType{}) {
ca.Dependencies.Bind(&testBindType{
title: "default title",
})
}
func (c *testControllerActivateListener) BeforeActivate(ca *ControllerActivator) {
ca.Dependencies.AddOnce(&testBindType{title: "default title"})
}
func (c *testControllerActivateListener) Get() string {
@@ -434,12 +431,14 @@ func (c *testControllerActivateListener) Get() string {
func TestControllerActivateListener(t *testing.T) {
app := iris.New()
New().Controller(app, new(testControllerActivateListener))
New().Bind(&testBindType{ // will bind to all controllers under this .New() MVC Engine.
NewEngine().Controller(app, new(testControllerActivateListener))
m := NewEngine()
m.Dependencies.Add(&testBindType{ // will bind to all controllers under this .New() MVC Engine.
title: "my title",
}).Controller(app.Party("/manual"), new(testControllerActivateListener))
})
m.Controller(app.Party("/manual"), new(testControllerActivateListener))
// or
New().Controller(app.Party("/manual2"), &testControllerActivateListener{
NewEngine().Controller(app.Party("/manual2"), &testControllerActivateListener{
TitlePointer: &testBindType{
title: "my title",
},

92
mvc2/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...,
)
}

108
mvc2/di/func.go Normal file
View File

@@ -0,0 +1,108 @@
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) {
// 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.Valid = len(s.inputs) > 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
mvc2/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
mvc2/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
mvc2/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
}

100
mvc2/di/values.go Normal file
View File

@@ -0,0 +1,100 @@
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
}

View File

@@ -1,65 +1,52 @@
package mvc2
import (
"errors"
"github.com/kataras/di"
"github.com/kataras/iris/mvc2/di"
"github.com/kataras/golog"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
)
var (
errNil = errors.New("nil")
errBad = errors.New("bad")
errAlreadyExists = errors.New("already exists")
)
type Engine struct {
dependencies *di.D
Dependencies *di.D
}
func New() *Engine {
func NewEngine() *Engine {
return &Engine{
dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker),
Dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker),
}
}
func (e *Engine) Bind(values ...interface{}) *Engine {
e.dependencies.Bind(values...)
return e
}
func (e *Engine) Child() *Engine {
child := New()
child.dependencies = e.dependencies.Clone()
func (e *Engine) Clone() *Engine {
child := NewEngine()
child.Dependencies = e.Dependencies.Clone()
return child
}
func (e *Engine) Handler(handler interface{}) context.Handler {
h, err := MakeHandler(handler, e.dependencies.Values...)
h, err := MakeHandler(handler, e.Dependencies.Values...)
if err != nil {
golog.Errorf("mvc handler: %v", err)
}
return h
}
func (e *Engine) Controller(router router.Party, controller interface{}, onActivate ...func(*ControllerActivator)) {
ca := newControllerActivator(router, controller, e.dependencies)
func (e *Engine) Controller(router router.Party, controller interface{}, beforeActivate ...func(*ControllerActivator)) {
ca := newControllerActivator(router, controller, e.Dependencies)
// give a priority to the "onActivate"
// give a priority to the "beforeActivate"
// callbacks, if any.
for _, cb := range onActivate {
for _, cb := range beforeActivate {
cb(ca)
}
// check if controller has an "OnActivate" function
// check if controller has an "BeforeActivate" function
// which accepts the controller activator and call it.
if activateListener, ok := controller.(interface {
OnActivate(*ControllerActivator)
BeforeActivate(*ControllerActivator)
}); ok {
activateListener.OnActivate(ca)
activateListener.BeforeActivate(ca)
}
ca.activate()

View File

@@ -9,7 +9,8 @@ import (
)
func TestMvcEngineInAndHandler(t *testing.T) {
m := New().Bind(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
m := NewEngine()
m.Dependencies.Add(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
var (
h1 = m.Handler(testConsumeUserHandler)

View File

@@ -5,6 +5,8 @@ import (
"strings"
"github.com/fatih/structs"
"github.com/kataras/iris/mvc2/di"
"github.com/kataras/iris/context"
)
@@ -405,7 +407,7 @@ func (r View) Dispatch(ctx context.Context) { // r as Response view.
setViewData(ctx, m)
} else if m, ok := r.Data.(context.Map); ok {
setViewData(ctx, m)
} else if indirectVal(reflect.ValueOf(r.Data)).Kind() == reflect.Struct {
} else if di.IndirectValue(reflect.ValueOf(r.Data)).Kind() == reflect.Struct {
setViewData(ctx, structs.Map(r))
}
}

View File

@@ -71,7 +71,7 @@ func (c *testControllerMethodResult) GetThingWithTryDefaultBy(index int) Result
func TestControllerMethodResult(t *testing.T) {
app := iris.New()
New().Controller(app, new(testControllerMethodResult))
NewEngine().Controller(app, new(testControllerMethodResult))
e := httptest.New(t, app)
@@ -175,7 +175,7 @@ func (c *testControllerMethodResultTypes) GetCustomStructWithError() (s testCust
func TestControllerMethodResultTypes(t *testing.T) {
app := iris.New()
New().Controller(app, new(testControllerMethodResultTypes))
NewEngine().Controller(app, new(testControllerMethodResultTypes))
e := httptest.New(t, app, httptest.LogLevel("debug"))
@@ -266,8 +266,8 @@ func (t *testControllerViewResultRespectCtxViewData) Get() Result {
func TestControllerViewResultRespectCtxViewData(t *testing.T) {
app := iris.New()
New().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) {
ca.Dependencies.Bind(t)
NewEngine().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) {
ca.Dependencies.Add(t)
})
e := httptest.New(t, app)

View File

@@ -2,7 +2,7 @@ package mvc2
import (
"fmt"
"github.com/kataras/di"
"github.com/kataras/iris/mvc2/di"
"reflect"
"runtime"
@@ -23,7 +23,7 @@ func isContextHandler(handler interface{}) (context.Handler, bool) {
}
func validateHandler(handler interface{}) error {
if typ := reflect.TypeOf(handler); !isFunc(typ) {
if typ := reflect.TypeOf(handler); !di.IsFunc(typ) {
return fmt.Errorf("handler expected to be a kind of func but got typeof(%s)", typ.String())
}
return nil

83
mvc2/ideas/1/main.go Normal file
View File

@@ -0,0 +1,83 @@
package main
import (
"fmt"
"github.com/kataras/iris"
"github.com/kataras/iris/sessions"
mvc "github.com/kataras/iris/mvc2"
)
func main() {
app := iris.New()
mvc.New(app.Party("/todo")).Configure(TodoApp)
// no let's have a clear "mvc" package without any conversions and type aliases,
// it's one extra import path for a whole new world, it worths it.
//
// app.UseMVC(app.Party("/todo")).Configure(func(app *iris.MVCApplication))
app.Run(iris.Addr(":8080"))
}
func TodoApp(app *mvc.Application) {
// You can use normal middlewares at MVC apps of course.
app.Router.Use(func(ctx iris.Context) {
ctx.Application().Logger().Infof("Path: %s", ctx.Path())
ctx.Next()
})
// Add dependencies which will be binding to the controller(s),
// can be either a function which accepts an iris.Context and returns a single value (dynamic binding)
// or a static struct value (service).
app.AddDependencies(
mvc.Session(sessions.New(sessions.Config{})),
&prefixedLogger{prefix: "DEV"},
)
app.Register(new(TodoController))
// All dependencies of the parent *mvc.Application
// are cloned to that new child, thefore it has access to the same session as well.
app.NewChild(app.Router.Party("/sub")).
Register(new(TodoSubController))
}
// If controller's fields (or even its functions) expecting an interface
// but a struct value is binded then it will check if that struct value implements
// the interface and if true then it will bind it as expected.
type LoggerService interface {
Log(string)
}
type prefixedLogger struct {
prefix string
}
func (s *prefixedLogger) Log(msg string) {
fmt.Printf("%s: %s\n", s.prefix, msg)
}
type TodoController struct {
Logger LoggerService
Session *sessions.Session
}
func (c *TodoController) Get() string {
count := c.Session.Increment("count", 1)
body := fmt.Sprintf("Hello from TodoController\nTotal visits from you: %d", count)
c.Logger.Log(body)
return body
}
type TodoSubController struct {
Session *sessions.Session
}
func (c *TodoSubController) Get() string {
count, _ := c.Session.GetIntDefault("count", 1)
return fmt.Sprintf("Hello from TodoSubController.\nRead-only visits count: %d", count)
}

90
mvc2/mvc.go Normal file
View File

@@ -0,0 +1,90 @@
package mvc2
import "github.com/kataras/iris/core/router"
// Application is the high-level compoment of the "mvc" package.
// It's the API that you will be using to register controllers among wih their
// dependencies that your controllers may expecting.
// It contains the Router(iris.Party) in order to be able to register
// template layout, middleware, done handlers as you used with the
// standard Iris APIBuilder.
//
// The Engine is created by the `New` method and it's the dependencies holder
// and controllers factory.
//
// See `mvc#New` for more.
type Application struct {
Engine *Engine
Router router.Party
}
func newApp(engine *Engine, subRouter router.Party) *Application {
return &Application{
Engine: engine,
Router: subRouter,
}
}
// New returns a new mvc Application based on a "subRouter".
// Application creates a new engine which is responsible for binding the dependencies
// and creating and activating the app's controller(s).
//
// Example: `New(app.Party("/todo"))`.
func New(subRouter router.Party) *Application {
return newApp(NewEngine(), subRouter)
}
// Configure can be used to pass one or more functions that accept this
// Application, use this to add dependencies and controller(s).
//
// Example: `New(app.Party("/todo")).Configure(func(mvcApp *mvc.Application){...})`.
func (app *Application) Configure(configurators ...func(*Application)) *Application {
for _, c := range configurators {
c(app)
}
return app
}
// AddDependencies adds one or more values as dependencies.
// The value can be a single struct value-instance or a function
// which has one input and one output, the input should be
// an `iris.Context` and the output can be any type, that output type
// will be binded to the controller's field, if matching or to the
// controller's methods, if matching.
//
// The dependencies can be changed per-controller as well via a `beforeActivate`
// on the `Register` method or when the controller has the `BeforeActivate(c *ControllerActivator)`
// method defined.
//
// It returns this Application.
//
// Example: `.AddDependencies(loggerService{prefix: "dev"}, func(ctx iris.Context) User {...})`.
func (app *Application) AddDependencies(values ...interface{}) *Application {
app.Engine.Dependencies.Add(values...)
return app
}
// Register adds a controller for the current Router.
// It accept any custom struct which its functions will be transformed
// to routes.
//
// The second, optional and variadic argument is the "beforeActive",
// use that when you want to modify the controller before the activation
// and registration to the main Iris Application.
//
// It returns this Application.
//
// Example: `.Register(new(TodoController))`.
func (app *Application) Register(controller interface{}, beforeActivate ...func(*ControllerActivator)) *Application {
app.Engine.Controller(app.Router, controller, beforeActivate...)
return app
}
// NewChild creates and returns a new Application which will be adapted
// to the "subRouter", it adopts
// the dependencies bindings from the parent(current) one.
//
// Example: `.NewChild(irisApp.Party("/sub")).Register(new(TodoSubController))`.
func (app *Application) NewChild(subRouter router.Party) *Application {
return newApp(app.Engine.Clone(), subRouter)
}

View File

@@ -7,7 +7,8 @@ import (
)
func TestPathParamsBinder(t *testing.T) {
m := New().Bind(PathParamsBinder)
m := NewEngine()
m.Dependencies.Add(PathParamsBinder)
got := ""
@@ -25,7 +26,8 @@ func TestPathParamsBinder(t *testing.T) {
}
}
func TestPathParamBinder(t *testing.T) {
m := New().Bind(PathParamBinder("username"))
m := NewEngine()
m.Dependencies.Add(PathParamBinder("username"))
got := ""
executed := false

View File

@@ -2,10 +2,8 @@ package mvc2
import (
"reflect"
"strings"
"github.com/kataras/iris/context"
"github.com/kataras/pkg/zerocheck"
)
var baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem()
@@ -20,58 +18,6 @@ func isContext(inTyp reflect.Type) bool {
return inTyp.Implements(contextTyp)
}
func indirectVal(v reflect.Value) reflect.Value {
return reflect.Indirect(v)
}
func indirectTyp(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
}
func getNameOf(typ reflect.Type) string {
elemTyp := indirectTyp(typ)
typName := elemTyp.Name()
pkgPath := elemTyp.PkgPath()
fullname := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + typName
return fullname
}
func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
n := funcTyp.NumIn()
funcIn := make([]reflect.Type, n, n)
@@ -80,72 +26,3 @@ func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
}
return funcIn
}
// 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 f.PkgPath != "" {
continue // skip unexported.
}
if indirectTyp(f.Type).Kind() == reflect.Struct &&
!structFieldIgnored(f) {
fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...)
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
}
func lookupNonZeroFieldsValues(v reflect.Value) (bindValues []reflect.Value) {
elem := indirectVal(v)
fields := lookupFields(indirectTyp(v.Type()), nil)
for _, f := range fields {
if fieldVal := elem.FieldByIndex(f.Index); f.Type.Kind() == reflect.Ptr && !zerocheck.IsZero(fieldVal) {
bindValues = append(bindValues, fieldVal)
}
}
return
}

View File

@@ -17,12 +17,12 @@ type SessionController struct {
Session *sessions.Session
}
// OnActivate called, once per application lifecycle NOT request,
// BeforeActivate called, once per application lifecycle NOT request,
// every single time the dev registers a specific SessionController-based controller.
// It makes sure that its "Manager" field is filled
// even if the caller didn't provide any sessions manager via the `app.Controller` function.
func (s *SessionController) OnActivate(ca *ControllerActivator) {
if didntBindManually := ca.Dependencies.BindIfNotExists(defaultSessionManager); didntBindManually {
func (s *SessionController) BeforeActivate(ca *ControllerActivator) {
if didntBindManually := ca.Dependencies.AddOnce(defaultSessionManager); didntBindManually {
ca.Router.GetReporter().Add(
`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field,
therefore this controller is using the default sessions manager instead.