ok the new mvc system works great, all tests done and almost x3 smaller LOC used plus new three awesome features:) - next commit will be commenting out and replace the mvc package with the new mvc2

Former-commit-id: 552095d29256a1116849cc6054c82001e790e705
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-12-10 07:00:51 +02:00
parent e02da3d442
commit ed79f0c3cd
31 changed files with 1344 additions and 1572 deletions

20
.gitattributes vendored
View File

@ -1,11 +1,11 @@
*.go linguist-language=Go *.go linguist-language=Go
vendor/* linguist-vendored vendor/* linguist-vendored
_examples/* linguist-documentation _examples/* linguist-documentation
_benchmarks/* linguist-documentation _benchmarks/* linguist-documentation
# Set the default behavior, in case people don't have core.autocrlf set. # Set the default behavior, in case people don't have core.autocrlf set.
# if from windows: # if from windows:
# git config --global core.autocrlf true # git config --global core.autocrlf true
# if from unix: # if from unix:
# git config --global core.autocrlf input # git config --global core.autocrlf input
# https://help.github.com/articles/dealing-with-line-endings/#per-repository-settings # https://help.github.com/articles/dealing-with-line-endings/#per-repository-settings
* text=auto * text=auto

0
_examples/README.md Executable file → Normal file
View File

View File

@ -697,18 +697,23 @@ type Context interface {
// ServeContent serves content, headers are autoset // ServeContent serves content, headers are autoset
// receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string) // receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string)
// //
// You can define your own "Content-Type" header also, after this function call //
// Doesn't implements resuming (by range), use ctx.SendFile instead // You can define your own "Content-Type" with `context#ContentType`, before this function call.
//
// This function doesn't support resuming (by range),
// use ctx.SendFile or router's `StaticWeb` instead.
ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error
// ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename) // ServeFile serves a file (to send a file, a zip for example to the client you should use the `SendFile` instead)
// receives two parameters // receives two parameters
// filename/path (string) // filename/path (string)
// gzipCompression (bool) // gzipCompression (bool)
// //
// You can define your own "Content-Type" header also, after this function call // You can define your own "Content-Type" with `context#ContentType`, before this function call.
// This function doesn't implement resuming (by range), use ctx.SendFile instead
// //
// Use it when you want to serve css/js/... files to the client, for bigger files and 'force-download' use the SendFile. // This function doesn't support resuming (by range),
// use ctx.SendFile or router's `StaticWeb` instead.
//
// Use it when you want to serve dynamic files to the client.
ServeFile(filename string, gzipCompression bool) error ServeFile(filename string, gzipCompression bool) error
// SendFile sends file for force-download to the client // SendFile sends file for force-download to the client
// //
@ -806,6 +811,11 @@ type Context interface {
// to be executed at serve-time. The full app's fields // to be executed at serve-time. The full app's fields
// and methods are not available here for the developer's safety. // and methods are not available here for the developer's safety.
Application() Application Application() Application
// String returns the string representation of this request.
// Each context has a unique string representation, so this can be used
// as an "ID" as well, if needed.
String() string
} }
// Next calls all the next handler from the handlers chain, // Next calls all the next handler from the handlers chain,
@ -857,7 +867,11 @@ type Map map[string]interface{}
// +------------------------------------------------------------+ // +------------------------------------------------------------+
type context struct { type context struct {
// the http.ResponseWriter wrapped by custom writer // the unique id, it's empty until `String` function is called,
// it's here to cache the random, unique context's id, although `String`
// returns more than this.
id string
// the http.ResponseWriter wrapped by custom writer.
writer ResponseWriter writer ResponseWriter
// the original http.Request // the original http.Request
request *http.Request request *http.Request
@ -865,10 +879,10 @@ type context struct {
currentRouteName string currentRouteName string
// the local key-value storage // the local key-value storage
params RequestParams // url named parameters params RequestParams // url named parameters.
values memstore.Store // generic storage, middleware communication values memstore.Store // generic storage, middleware communication.
// the underline application app // the underline application app.
app Application app Application
// the route's handlers // the route's handlers
handlers Handlers handlers Handlers
@ -2721,6 +2735,19 @@ func (ctx *context) Exec(method string, path string) {
} }
} }
// String returns the string representation of this request.
// Each context has a unique string representation, so this can be used
// as an "ID" as well, if needed.
func (ctx *context) String() (s string) {
if ctx.id == "" {
// set the id here.
s = "..."
}
return
}
// Application returns the iris app instance which belongs to this context. // Application returns the iris app instance which belongs to this context.
// Worth to notice that this function returns an interface // Worth to notice that this function returns an interface
// of the Application, which contains methods that are safe // of the Application, which contains methods that are safe

0
middleware/README.md Executable file → Normal file
View File

View File

@ -14,7 +14,7 @@ var defaultManager = sessions.New(sessions.Config{})
// which requires a binded session manager in order to give // which requires a binded session manager in order to give
// direct access to the current client's session via its `Session` field. // direct access to the current client's session via its `Session` field.
type SessionController struct { type SessionController struct {
Controller C
Manager *sessions.Sessions Manager *sessions.Sessions
Session *sessions.Session Session *sessions.Session
@ -36,7 +36,7 @@ Please refer to the documentation to learn how you can provide the session manag
// BeginRequest calls the Controller's BeginRequest // BeginRequest calls the Controller's BeginRequest
// and tries to initialize the current user's Session. // and tries to initialize the current user's Session.
func (s *SessionController) BeginRequest(ctx context.Context) { func (s *SessionController) BeginRequest(ctx context.Context) {
s.Controller.BeginRequest(ctx) s.C.BeginRequest(ctx)
if s.Manager == nil { if s.Manager == nil {
ctx.Application().Logger().Errorf(`MVC SessionController: sessions manager is nil, report this as a bug ctx.Application().Logger().Errorf(`MVC SessionController: sessions manager is nil, report this as a bug
because the SessionController should predict this on its activation state and use a default one automatically`) because the SessionController should predict this on its activation state and use a default one automatically`)

244
mvc2/bind.go Normal file
View File

@ -0,0 +1,244 @@
package mvc2
import "reflect"
type bindType uint32
const (
objectType bindType = iota // simple assignable value.
functionResultType // dynamic value, depends on the context.
)
type bindObject struct {
Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' .
Value reflect.Value
BindType bindType
ReturnValue func(ctx []reflect.Value) reflect.Value
}
// makeReturnValue takes any function
// that accept a context and returns something
// and returns a binder function, which accepts the context as slice of reflect.Value
// and returns a reflect.Value for that.
// Iris uses to
// resolve and set the input parameters when a handler is executed.
//
// The "fn" can have the following form:
// `func(iris.Context) UserViewModel`.
//
// 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 and
// it can accept only one input argument,
// the Iris' Context (`context.Context` or `iris.Context`).
func makeReturnValue(fn reflect.Value) (func([]reflect.Value) reflect.Value, reflect.Type, error) {
typ := indirectTyp(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
}
// invalid if input args length is not one.
if typ.NumIn() != 1 {
return nil, typ, errBad
}
// invalid if that single input arg is not a typeof context.Context.
if !isContext(typ.In(0)) {
return nil, typ, errBad
}
outTyp := typ.Out(0)
zeroOutVal := reflect.New(outTyp).Elem()
bf := func(ctxValue []reflect.Value) reflect.Value {
results := fn.Call(ctxValue) // ctxValue is like that because of; read makeHandler.
if len(results) == 0 {
return zeroOutVal
}
v := results[0]
if !v.IsValid() {
return zeroOutVal
}
return v
}
return bf, outTyp, nil
}
func makeBindObject(v reflect.Value) (b bindObject, err error) {
if isFunc(v) {
b.BindType = functionResultType
b.ReturnValue, b.Type, err = makeReturnValue(v)
} else {
b.BindType = objectType
b.Type = v.Type()
b.Value = v
}
return
}
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 == functionResultType {
toSetter(b.ReturnValue(ctx))
return
}
toSetter(b.Value)
}
type (
targetField struct {
Object *bindObject
FieldIndex []int
}
targetFuncInput struct {
Object *bindObject
InputIndex int
}
)
type targetStruct struct {
Fields []*targetField
Valid bool // is True when contains fields and it's a valid target struct.
}
func newTargetStruct(v reflect.Value, bindValues ...reflect.Value) *targetStruct {
typ := indirectTyp(v.Type())
s := &targetStruct{}
fields := lookupFields(typ, nil)
for _, f := range fields {
for _, val := range bindValues {
// the binded values to the struct's fields.
b, err := makeBindObject(val)
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, &targetField{
FieldIndex: f.Index,
Object: &b,
})
break
}
}
}
s.Valid = len(s.Fields) > 0
return s
}
func (s *targetStruct) Fill(destElem reflect.Value, ctx ...reflect.Value) {
for _, f := range s.Fields {
f.Object.Assign(ctx, func(v reflect.Value) {
// defer func() {
// if err := recover(); err != nil {
// fmt.Printf("for index: %#v on: %s where num fields are: %d\n",
// f.FieldIndex, f.Object.Type.String(), destElem.NumField())
// }
// }()
destElem.FieldByIndex(f.FieldIndex).Set(v)
})
}
}
type targetFunc struct {
Inputs []*targetFuncInput
Valid bool // is True when contains func inputs and it's a valid target func.
}
func newTargetFunc(fn reflect.Value, bindValues ...reflect.Value) *targetFunc {
typ := indirectTyp(fn.Type())
s := &targetFunc{
Valid: false,
}
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 it's context then bind it directly here and continue to the next func's input arg.
if isContext(inTyp) {
s.Inputs = append(s.Inputs, &targetFuncInput{
InputIndex: i,
Object: &bindObject{
Type: contextTyp,
BindType: functionResultType,
ReturnValue: func(ctxValue []reflect.Value) reflect.Value {
return ctxValue[0]
},
},
})
continue
}
for valIdx, val := range bindValues {
if _, shouldSkip := consumedValues[valIdx]; shouldSkip {
continue
}
inTyp := typ.In(i)
// the binded values to the func's inputs.
b, err := makeBindObject(val)
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.Valid = len(s.Inputs) > 0
return s
}
func (s *targetFunc) Fill(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
}

View File

@ -1,4 +0,0 @@
package binder
type Input interface {
}

View File

@ -1,19 +0,0 @@
package binder
import (
"reflect"
)
type Binding interface {
AddSource(v reflect.Value, source ...reflect.Value)
}
type StructValue struct {
Type reflect.Type
Value reflect.Value
}
type FuncResultValue struct {
Type reflect.Type
ReturnValue func(ctx []reflect.Value) reflect.Value
}

View File

@ -1 +0,0 @@
package binder

View File

@ -1,53 +0,0 @@
package binder
import (
"errors"
"reflect"
)
var (
errBad = errors.New("bad")
)
func makeReturnValue(fn reflect.Value) (func([]reflect.Value) reflect.Value, reflect.Type, error) {
typ := indirectTyp(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
}
// invalid if input args length is not one.
if typ.NumIn() != 1 {
return nil, typ, errBad
}
// invalid if that single input arg is not a typeof context.Context.
if !isContext(typ.In(0)) {
return nil, typ, errBad
}
outTyp := typ.Out(0)
zeroOutVal := reflect.New(outTyp).Elem()
bf := func(ctxValue []reflect.Value) reflect.Value {
// []reflect.Value{reflect.ValueOf(ctx)}
results := fn.Call(ctxValue) // ctxValue is like that because of; read makeHandler.
if len(results) == 0 {
return zeroOutVal
}
v := results[0]
if !v.IsValid() {
return zeroOutVal
}
return v
}
return bf, outTyp, nil
}

View File

@ -1,107 +0,0 @@
package binder
import "reflect"
func isContext(inTyp reflect.Type) bool {
return inTyp.String() == "context.Context" // I couldn't find another way; context/context.go is not exported.
}
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(typ reflect.Type) bool {
return typ.Kind() == reflect.Func
}
/*
// no f. this, it's too complicated and it will be harder to maintain later on:
func isSliceAndExpectedItem(got reflect.Type, in []reflect.Type, currentBindersIdx int) bool {
kind := got.Kind()
// if got result is slice or array.
return (kind == reflect.Slice || kind == reflect.Array) &&
// if has expected next input.
len(in)-1 > currentBindersIdx &&
// if the current input's type is not the same as got (if it's not a slice of that types or anything else).
equalTypes(got, in[currentBindersIdx])
}
*/
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 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 service and it's filled from the lookupFields' caller.
AnyValue reflect.Value
}
func lookupFields(typ reflect.Type, parentIndex int) (fields []field) {
for i, n := 0, typ.NumField(); i < n; i++ {
f := typ.Field(i)
if f.Type.Kind() == reflect.Struct && !structFieldIgnored(f) {
fields = append(fields, lookupFields(f.Type, i)...)
continue
}
index := []int{i}
if parentIndex >= 0 {
index = append([]int{parentIndex}, index...)
}
field := field{
Type: f.Type,
Name: f.Name,
Index: index,
}
fields = append(fields, field)
}
return
}

View File

@ -1,50 +0,0 @@
package binder
import (
"reflect"
)
type StructBinding struct {
Field StructValue
Func FuncResultValue
}
func (b *StructBinding) AddSource(dest reflect.Value, source ...reflect.Value) {
typ := indirectTyp(dest.Type()) //indirectTyp(reflect.TypeOf(dest))
if typ.Kind() != reflect.Struct {
return
}
fields := lookupFields(typ, -1)
for _, f := range fields {
for _, s := range source {
if s.Type().Kind() == reflect.Func {
returnValue, outType, err := makeReturnValue(s)
if err != nil {
continue
}
gotTyp = outType
service.ReturnValue = returnValue
}
gotTyp := s.Type()
v := StructValue{
Type: gotTyp,
Value: s,
FieldIndex: f.Index,
}
if equalTypes(gotTyp, f.Type) {
service.Type = gotTyp
_serv = append(_serv, &service)
fmt.Printf("[2] Bind In=%s->%s for struct field[%d]\n", f.Type, gotTyp.String(), f.Index)
break
}
}
}
fmt.Printf("[2] Bind %d for %s\n", len(_serv), typ.String())
*serv = _serv
return
}

View File

@ -1,173 +0,0 @@
package mvc2
import (
"reflect"
)
// InputBinder is the result of `MakeBinder`.
// It contains the binder wrapped information, like the
// type that is responsible to bind
// and a function which will accept a context and returns a value of something.
type InputBinder struct {
BinderType binderType
BindType reflect.Type
BindFunc func(ctx []reflect.Value) reflect.Value
}
// key = the func input argument index, value is the responsible input binder.
type bindersMap map[int]*InputBinder
// joinBindersMap joins the "m2" to m1 and returns the result, it's the same "m1" map.
// if "m2" is not nil and "m2" is not nil then it loops the "m2"'s keys and sets the values
// to the "m1", if "m2" is not and not empty nil but m1 is nil then "m1" = "m2".
// The result may be nil if the "m1" and "m2" are nil or "m2" is empty and "m1" is nil.
func joinBindersMap(m1, m2 bindersMap) bindersMap {
if m2 != nil && len(m2) > 0 {
if m1 == nil {
m1 = m2
} else {
for k, v := range m2 {
m1[k] = v
}
}
}
return m1
}
// getBindersForInput returns a map of the responsible binders for the "expected" types,
// which are the expected input parameters' types,
// based on the available "binders" collection.
//
// It returns a map which its key is the index of the "expected" which
// a valid binder for that in's type found,
// the value is the pointer of the responsible `InputBinder`.
//
// Check of "a nothing responsible for those expected types"
// should be done using the `len(m) == 0`.
func getBindersForInput(binders []*InputBinder, expected ...reflect.Type) (m bindersMap) {
for idx, in := range expected {
if idx == 0 && isContext(in) {
// if the first is context then set it directly here.
m = make(bindersMap)
m[0] = &InputBinder{
BindType: contextTyp,
BindFunc: func(ctxValues []reflect.Value) reflect.Value {
return ctxValues[0]
},
}
continue
}
for _, b := range binders {
if equalTypes(b.BindType, in) {
if m == nil {
m = make(bindersMap)
}
// fmt.Printf("set index: %d to type: %s where input type is: %s\n", idx, b.BindType.String(), in.String())
m[idx] = b
break
}
}
}
return m
}
// MustMakeFuncInputBinder calls the `MakeFuncInputBinder` and returns its first result, see its docs.
// It panics on error.
func MustMakeFuncInputBinder(binder interface{}) *InputBinder {
b, err := MakeFuncInputBinder(binder)
if err != nil {
panic(err)
}
return b
}
type binderType uint32
const (
functionType binderType = iota
serviceType
invalidType
)
func resolveBinderType(binder interface{}) binderType {
if binder == nil {
return invalidType
}
return resolveBinderTypeFromKind(reflect.TypeOf(binder).Kind())
}
func resolveBinderTypeFromKind(k reflect.Kind) binderType {
switch k {
case reflect.Func:
return functionType
case reflect.Struct, reflect.Interface, reflect.Ptr, reflect.Slice, reflect.Array:
return serviceType
}
return invalidType
}
// MakeFuncInputBinder takes a binder function or a struct which contains a "Bind"
// function and returns an `InputBinder`, which Iris uses to
// resolve and set the input parameters when a handler is executed.
//
// The "binder" can have the following form:
// `func(iris.Context) UserViewModel`.
//
// The return type of the "binder" should be a value instance, not a pointer, for your own protection.
// The binder function should return only one value and
// it can accept only one input argument, the Iris' Context (`context.Context` or `iris.Context`).
func MakeFuncInputBinder(binder interface{}) (*InputBinder, error) {
v := reflect.ValueOf(binder)
return makeFuncInputBinder(v)
}
func makeFuncInputBinder(fn reflect.Value) (*InputBinder, error) {
typ := indirectTyp(fn.Type())
// invalid if not a func.
if typ.Kind() != reflect.Func {
return nil, errBad
}
// invalid if not returns one single value.
if typ.NumOut() != 1 {
return nil, errBad
}
// invalid if input args length is not one.
if typ.NumIn() != 1 {
return nil, errBad
}
// invalid if that single input arg is not a typeof context.Context.
if !isContext(typ.In(0)) {
return nil, errBad
}
outTyp := typ.Out(0)
zeroOutVal := reflect.New(outTyp).Elem()
bf := func(ctxValue []reflect.Value) reflect.Value {
// []reflect.Value{reflect.ValueOf(ctx)}
results := fn.Call(ctxValue) // ctxValue is like that because of; read makeHandler.
if len(results) == 0 {
return zeroOutVal
}
v := results[0]
if !v.IsValid() {
return zeroOutVal
}
return v
}
return &InputBinder{
BinderType: functionType,
BindType: outTyp,
BindFunc: bf,
}, nil
}

View File

@ -1,81 +0,0 @@
package mvc2
import (
"reflect"
)
type serviceFieldBinder struct {
Index []int
Binder *InputBinder
}
func getServicesBinderForStruct(binders []*InputBinder, typ reflect.Type) func(elem reflect.Value) {
fields := lookupFields(typ, -1)
var validBinders []*serviceFieldBinder
for _, b := range binders {
for _, f := range fields {
if b.BinderType != serviceType {
continue
}
if equalTypes(b.BindType, f.Type) {
validBinders = append(validBinders,
&serviceFieldBinder{Index: f.Index, Binder: b})
}
}
}
if len(validBinders) == 0 {
return func(_ reflect.Value) {}
}
return func(elem reflect.Value) {
for _, b := range validBinders {
elem.FieldByIndex(b.Index).Set(b.Binder.BindFunc(nil))
}
}
}
// MustMakeServiceInputBinder calls the `MakeServiceInputBinder` and returns its first result, see its docs.
// It panics on error.
func MustMakeServiceInputBinder(service interface{}) *InputBinder {
s, err := MakeServiceInputBinder(service)
if err != nil {
panic(err)
}
return s
}
// MakeServiceInputBinder uses a difference/or strange approach,
// we make the services as bind functions
// in order to keep the rest of the code simpler, however we have
// a performance penalty when calling the function instead
// of just put the responsible service to the certain handler's input argument.
func MakeServiceInputBinder(service interface{}) (*InputBinder, error) {
if service == nil {
return nil, errNil
}
var (
val = reflect.ValueOf(service)
typ = val.Type()
)
if !goodVal(val) {
return nil, errBad
}
if indirectTyp(typ).Kind() != reflect.Struct {
// if the pointer's struct is not a struct then return err bad.
return nil, errBad
}
return &InputBinder{
BinderType: serviceType,
BindType: typ,
BindFunc: func(_ []reflect.Value) reflect.Value {
return val
},
}, nil
}

View File

@ -1,46 +0,0 @@
package mvc2
import (
"reflect"
"testing"
)
type (
testService interface {
say(string)
}
testServiceImpl struct {
prefix string
}
)
func (s *testServiceImpl) say(message string) string {
return s.prefix + ": " + message
}
func TestMakeServiceInputBinder(t *testing.T) {
expectedService := &testServiceImpl{"say"}
b := MustMakeServiceInputBinder(expectedService)
// in
var (
intType = reflect.TypeOf(1)
availableBinders = []*InputBinder{b}
)
// 1
testCheck(t, "test1", true, testGetBindersForInput(t, availableBinders,
[]interface{}{expectedService}, reflect.TypeOf(expectedService)))
// 2
testCheck(t, "test2-fail", false, testGetBindersForInput(t, availableBinders,
[]interface{}{42}))
// 3
testCheck(t, "test3-fail", false, testGetBindersForInput(t, availableBinders,
[]interface{}{42}, intType))
// 4
testCheck(t, "test4-fail", false, testGetBindersForInput(t, availableBinders,
[]interface{}{42}))
// 5 - check if nothing passed, so no valid binders at all.
testCheck(t, "test5", true, testGetBindersForInput(t, availableBinders,
[]interface{}{}))
}

View File

@ -1,143 +0,0 @@
package mvc2
import (
"fmt"
"reflect"
"testing"
"github.com/kataras/iris/context"
)
type testUserStruct struct {
ID int64
Username string
}
func testBinderFunc(ctx context.Context) testUserStruct {
id, _ := ctx.Params().GetInt64("id")
username := ctx.Params().Get("username")
return testUserStruct{
ID: id,
Username: username,
}
}
func TestMakeFuncInputBinder(t *testing.T) {
testMakeFuncInputBinder(t, testBinderFunc)
}
func testMakeFuncInputBinder(t *testing.T, binder interface{}) {
b, err := MakeFuncInputBinder(binder)
if err != nil {
t.Fatalf("failed to make binder: %v", err)
}
if b == nil {
t.Fatalf("excepted non-nil *InputBinder but got nil")
}
if expected, got := reflect.TypeOf(testUserStruct{}), b.BindType; expected != got {
t.Fatalf("expected type of the binder's return value to be: %T but got: %T", expected, got)
}
expected := testUserStruct{
ID: 42,
Username: "kataras",
}
ctx := context.NewContext(nil)
ctx.Params().Set("id", fmt.Sprintf("%v", expected.ID))
ctx.Params().Set("username", expected.Username)
ctxValue := []reflect.Value{reflect.ValueOf(ctx)}
v := b.BindFunc(ctxValue)
if !v.CanInterface() {
t.Fatalf("result of binder func cannot be interfaced: %#+v", v)
}
got, ok := v.Interface().(testUserStruct)
if !ok {
t.Fatalf("result of binder func should be a type of 'testUserStruct' but got: %#+v", v.Interface())
}
if got != expected {
t.Fatalf("invalid result of binder func, expected: %v but got: %v", expected, got)
}
}
func testCheck(t *testing.T, testName string, shouldPass bool, errString string) {
if shouldPass && errString != "" {
t.Fatalf("[%s] %s", testName, errString)
}
if !shouldPass && errString == "" {
t.Fatalf("[%s] expected not to pass", testName)
}
}
// TestGetBindersForInput will test two available binders, one for int
// and other for a string,
// the first input will contains both of them in the same order,
// the second will contain both of them as well but with a different order,
// the third will contain only the int input and should fail,
// the forth one will contain only the string input and should fail,
// the fifth one will contain two integers and should fail,
// the last one will contain a struct and should fail,
// that no of othe available binders will support it,
// so no len of the result should be zero there.
func TestGetBindersForInput(t *testing.T) {
// binders
var (
stringBinder = MustMakeFuncInputBinder(func(ctx context.Context) string {
return "a string"
})
intBinder = MustMakeFuncInputBinder(func(ctx context.Context) int {
return 42
})
)
// in
var (
stringType = reflect.TypeOf("string")
intType = reflect.TypeOf(1)
)
// 1
testCheck(t, "test1", true, testGetBindersForInput(t, []*InputBinder{intBinder, stringBinder},
[]interface{}{"a string", 42}, stringType, intType))
availableBinders := []*InputBinder{stringBinder, intBinder} // different order than the fist test.
// 2
testCheck(t, "test2", true, testGetBindersForInput(t, availableBinders,
[]interface{}{"a string", 42}, stringType, intType))
// 3
testCheck(t, "test-3-fail", false, testGetBindersForInput(t, availableBinders,
[]interface{}{42}, stringType, intType))
// 4
testCheck(t, "test-4-fail", false, testGetBindersForInput(t, availableBinders,
[]interface{}{"a string"}, stringType, intType))
// 5
testCheck(t, "test-5-fail", false, testGetBindersForInput(t, availableBinders,
[]interface{}{42, 42}, stringType, intType))
// 6
testCheck(t, "test-6-fail", false, testGetBindersForInput(t, availableBinders,
[]interface{}{testUserStruct{}}, stringType, intType))
}
func testGetBindersForInput(t *testing.T, binders []*InputBinder, expectingResults []interface{}, in ...reflect.Type) (errString string) {
m := getBindersForInput(binders, in...)
if expected, got := len(expectingResults), len(m); expected != got {
return fmt.Sprintf("expected results length(%d) and valid binders length(%d) to be equal, so each input has one binder", expected, got)
}
ctxValue := []reflect.Value{reflect.ValueOf(context.NewContext(nil))}
for idx, expected := range expectingResults {
if m[idx] != nil {
v := m[idx].BindFunc(ctxValue)
if got := v.Interface(); got != expected {
return fmt.Sprintf("expected result[%d] to be: %v but got: %v", idx, expected, got)
}
} else {
t.Logf("m[%d] = nil on input = %v\n", idx, expected)
}
}
return ""
}

View File

@ -11,7 +11,6 @@ import (
"github.com/kataras/iris/core/router" "github.com/kataras/iris/core/router"
"github.com/kataras/iris/core/router/macro" "github.com/kataras/iris/core/router/macro"
"github.com/kataras/iris/core/router/macro/interpreter/ast" "github.com/kataras/iris/core/router/macro/interpreter/ast"
"github.com/kataras/iris/mvc/activator/methodfunc"
) )
type BaseController interface { type BaseController interface {
@ -72,38 +71,52 @@ type ControllerActivator struct {
Router router.Party Router router.Party
initRef BaseController // the BaseController as it's passed from the end-dev. initRef BaseController // the BaseController as it's passed from the end-dev.
Type reflect.Type // raw type of the BaseController (initRef).
// FullName it's the last package path segment + "." + the Name. // FullName it's the last package path segment + "." + the Name.
// i.e: if login-example/user/controller.go, the FullName is "user.Controller". // i.e: if login-example/user/controller.go, the FullName is "user.Controller".
FullName string FullName string
// key = the method's name. // the methods names that is already binded to a handler,
methods map[string]reflect.Method // the BeginRequest, EndRequest and OnActivate are reserved by the internal implementation.
reservedMethods []string
// services []field // input are always empty after the `activate`
// bindServices func(elem reflect.Value) // are used to build the bindings, and we need this field
s services // because we have 3 states (Engine.Input, OnActivate, Bind)
// that we can add or override binding values.
input []reflect.Value
// the bindings that comes from input (and Engine) and can be binded to the controller's(initRef) fields.
bindings *targetStruct
} }
func newControllerActivator(engine *Engine, router router.Party, controller BaseController) *ControllerActivator { var emptyMethod = reflect.Method{}
func newControllerActivator(router router.Party, controller BaseController, bindValues ...reflect.Value) *ControllerActivator {
c := &ControllerActivator{ c := &ControllerActivator{
Engine: engine,
Router: router, Router: router,
initRef: controller, initRef: controller,
reservedMethods: []string{
"BeginRequest",
"EndRequest",
"OnActivate",
},
// the following will make sure that if
// the controller's has set-ed pointer struct fields by the end-dev
// we will include them to the bindings.
// set bindings to the non-zero pointer fields' values that may be set-ed by
// the end-developer when declaring the controller,
// activate listeners needs them in order to know if something set-ed already or not,
// look `BindTypeExists`.
input: append(lookupNonZeroFieldsValues(reflect.ValueOf(controller)), bindValues...),
} }
c.analyze() c.analyze()
return c return c
} }
var reservedMethodNames = []string{ func (c *ControllerActivator) isReservedMethod(name string) bool {
"BeginRequest", for _, s := range c.reservedMethods {
"EndRequest",
"OnActivate",
}
func isReservedMethod(name string) bool {
for _, s := range reservedMethodNames {
if s == name { if s == name {
return true return true
} }
@ -113,55 +126,86 @@ func isReservedMethod(name string) bool {
} }
func (c *ControllerActivator) analyze() { func (c *ControllerActivator) analyze() {
// set full name. // set full name.
{
// first instance value, needed to validate
// the actual type of the controller field
// and to collect and save the instance's persistence fields'
// values later on.
val := reflect.Indirect(reflect.ValueOf(c.initRef))
ctrlName := val.Type().Name() // first instance value, needed to validate
pkgPath := val.Type().PkgPath() // the actual type of the controller field
fullName := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + ctrlName // and to collect and save the instance's persistence fields'
c.FullName = fullName // values later on.
} typ := reflect.TypeOf(c.initRef) // type with pointer
elemTyp := indirectTyp(typ)
// set all available, exported methods. ctrlName := elemTyp.Name()
{ pkgPath := elemTyp.PkgPath()
typ := reflect.TypeOf(c.initRef) // typ, with pointer fullName := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + ctrlName
n := typ.NumMethod() c.FullName = fullName
c.methods = make(map[string]reflect.Method, n) c.Type = typ
for i := 0; i < n; i++ {
m := typ.Method(i)
key := m.Name
if !isReservedMethod(key) { // register all available, exported methods to handlers if possible.
c.methods[key] = m n := typ.NumMethod()
} for i := 0; i < n; i++ {
m := typ.Method(i)
funcName := m.Name
if c.isReservedMethod(funcName) {
continue
} }
httpMethod, httpPath, err := parse(m)
if err != nil && err != errSkip {
err = fmt.Errorf("MVC: fail to parse the path and method for '%s.%s': %v", c.FullName, m.Name, err)
c.Router.GetReporter().AddErr(err)
continue
}
c.Handle(httpMethod, httpPath, funcName)
} }
// set field index with matching service binders, if any.
{
// typ := indirectTyp(reflect.TypeOf(c.initRef)) // element's typ.
c.s = getServicesFor(reflect.ValueOf(c.initRef), c.Engine.Input)
// c.bindServices = getServicesBinderForStruct(c.Engine.binders, typ)
}
c.analyzeAndRegisterMethods()
} }
// SetBindings will override any bindings with the new "values".
func (c *ControllerActivator) SetBindings(values ...reflect.Value) {
// set field index with matching binders, if any.
c.bindings = newTargetStruct(reflect.ValueOf(c.initRef), values...)
c.input = c.input[0:0]
}
// Bind binds values to this controller, if you want to share
// binding values between controllers use the Engine's `Bind` function instead.
func (c *ControllerActivator) Bind(values ...interface{}) {
for _, val := range values {
if v := reflect.ValueOf(val); goodVal(v) {
c.input = append(c.input, v)
}
}
}
// BindTypeExists returns true if a binder responsible to
// bind and return a type of "typ" is already registered to this controller.
func (c *ControllerActivator) BindTypeExists(typ reflect.Type) bool {
for _, in := range c.input {
if equalTypes(in.Type(), typ) {
return true
}
}
return false
}
func (c *ControllerActivator) activate() {
c.SetBindings(c.input...)
}
var emptyIn = []reflect.Value{}
func (c *ControllerActivator) Handle(method, path, funcName string, middleware ...context.Handler) error { func (c *ControllerActivator) Handle(method, path, funcName string, middleware ...context.Handler) error {
if method == "" || path == "" || funcName == "" || isReservedMethod(funcName) { if method == "" || path == "" || funcName == "" ||
c.isReservedMethod(funcName) {
// isReservedMethod -> if it's already registered // isReservedMethod -> if it's already registered
// by a previous Handle or analyze methods internally. // by a previous Handle or analyze methods internally.
return errSkip return errSkip
} }
m, ok := c.methods[funcName] m, ok := c.Type.MethodByName(funcName)
if !ok { if !ok {
err := fmt.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller", err := fmt.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller",
funcName, c.FullName) funcName, c.FullName)
@ -176,105 +220,84 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
return err return err
} }
fmt.Printf("===============%s.%s==============\n", c.FullName, funcName) // add this as a reserved method name in order to
funcIn := getInputArgsFromFunc(m.Type)[1:] // except the receiver, which is the controller pointer itself. // be sure that the same func will not be registered again, even if a custom .Handle later on.
c.reservedMethods = append(c.reservedMethods, funcName)
// get any binders for this func, if any, and // fmt.Printf("===============%s.%s==============\n", c.FullName, funcName)
// take param binders, we can bind them because we know the path here.
// binders := joinBindersMap(
// getBindersForInput(c.Engine.binders, funcIn...),
// getPathParamsBindersForInput(tmpl.Params, funcIn...))
s := getServicesFor(m.Func, getPathParamsForInput(tmpl.Params, funcIn...)) funcIn := getInputArgsFromFunc(m.Type) // except the receiver, which is the controller pointer itself.
// s.AddSource(indirectVal(reflect.ValueOf(c.initRef)), c.Engine.Input...)
typ := reflect.TypeOf(c.initRef) pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...)
elem := indirectTyp(typ) // the value, not the pointer. funcBindings := newTargetFunc(m.Func, pathParams...)
hasInputBinders := len(s) > 0
hasStructBinders := len(c.s) > 0
n := len(funcIn) + 1
// be, _ := typ.MethodByName("BeginRequest") elemTyp := indirectTyp(c.Type) // the element value, not the pointer.
// en, _ := typ.MethodByName("EndRequest")
// beginIndex, endIndex := be.Index, en.Index n := len(funcIn)
handler := func(ctx context.Context) { handler := func(ctx context.Context) {
// create a new controller instance of that type(>ptr). // create a new controller instance of that type(>ptr).
ctrl := reflect.New(elem) ctrl := reflect.New(elemTyp)
//ctrlAndCtxValues := []reflect.Value{ctrl, ctxValue[0]}
// ctrl.MethodByName("BeginRequest").Call(ctxValue)
//begin.Func.Call(ctrlAndCtxValues)
b := ctrl.Interface().(BaseController) // the Interface(). is faster than MethodByName or pre-selected methods. b := ctrl.Interface().(BaseController) // the Interface(). is faster than MethodByName or pre-selected methods.
// init the request. // init the request.
b.BeginRequest(ctx) b.BeginRequest(ctx)
//ctrl.Method(beginIndex).Call(ctxValue)
// if begin request stopped the execution. // if begin request stopped the execution.
if ctx.IsStopped() { if ctx.IsStopped() {
return return
} }
if hasStructBinders { if !c.bindings.Valid && !funcBindings.Valid {
elem := ctrl.Elem() DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
c.s.FillStructStaticValues(elem)
}
if !hasInputBinders {
methodfunc.DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
} else { } else {
in := make([]reflect.Value, n, n) ctxValue := reflect.ValueOf(ctx)
// in[0] = ctrl.Elem()
in[0] = ctrl if c.bindings.Valid {
s.FillFuncInput([]reflect.Value{reflect.ValueOf(ctx)}, &in) elem := ctrl.Elem()
methodfunc.DispatchFuncResult(ctx, m.Func.Call(in)) c.bindings.Fill(elem, ctxValue)
// in := make([]reflect.Value, n, n) if ctx.IsStopped() {
// ctxValues := []reflect.Value{reflect.ValueOf(ctx)} return
// for k, v := range binders { }
// in[k] = v.BindFunc(ctxValues)
// we do this in order to reduce in := make...
// if not func input binders, we execute the handler with empty input args.
if !funcBindings.Valid {
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 funcBindings.Valid {
in := make([]reflect.Value, n, n)
in[0] = ctrl
funcBindings.Fill(&in, ctxValue)
if ctx.IsStopped() {
return
}
DispatchFuncResult(ctx, m.Func.Call(in))
}
// if ctx.IsStopped() {
// return
// }
// }
// methodfunc.DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(in))
} }
// end the request, don't check for stopped because this does the actual writing // end the request, don't check for stopped because this does the actual writing
// if no response written already. // if no response written already.
b.EndRequest(ctx) b.EndRequest(ctx)
// ctrl.MethodByName("EndRequest").Call(ctxValue)
// end.Func.Call(ctrlAndCtxValues)
//ctrl.Method(endIndex).Call(ctxValue)
} }
// register the handler now. // register the handler now.
r := c.Router.Handle(method, path, append(middleware, handler)...) c.Router.Handle(method, path, append(middleware, handler)...).
// change the main handler's name in order to respect the controller's and give // change the main handler's name in order to respect the controller's and give
// a proper debug message. // a proper debug message.
r.MainHandlerName = fmt.Sprintf("%s.%s", c.FullName, funcName) MainHandlerName = fmt.Sprintf("%s.%s", c.FullName, funcName)
// add this as a reserved method name in order to
// be sure that the same func will not be registered again, even if a custom .Handle later on.
reservedMethodNames = append(reservedMethodNames, funcName)
return nil return nil
} }
func (c *ControllerActivator) analyzeAndRegisterMethods() {
for _, m := range c.methods {
funcName := m.Name
httpMethod, httpPath, err := parse(m)
if err != nil && err != errSkip {
err = fmt.Errorf("MVC: fail to parse the path and method for '%s.%s': %v", c.FullName, m.Name, err)
c.Router.GetReporter().AddErr(err)
continue
}
c.Handle(httpMethod, httpPath, funcName)
}
}
const ( const (
tokenBy = "By" tokenBy = "By"
tokenWildcard = "Wildcard" // i.e ByWildcard tokenWildcard = "Wildcard" // "ByWildcard".
) )
// word lexer, not characters. // word lexer, not characters.
@ -393,13 +416,15 @@ func methodTitle(httpMethod string) string {
var errSkip = errors.New("skip") var errSkip = errors.New("skip")
var allMethods = append(router.AllMethods[0:], []string{"ALL", "ANY"}...)
func (p *parser) parse() (method, path string, err error) { func (p *parser) parse() (method, path string, err error) {
funcArgPos := 0 funcArgPos := 0
path = "/" path = "/"
// take the first word and check for the method. // take the first word and check for the method.
w := p.lexer.next() w := p.lexer.next()
for _, httpMethod := range router.AllMethods { for _, httpMethod := range allMethods {
possibleMethodFuncName := methodTitle(httpMethod) possibleMethodFuncName := methodTitle(httpMethod)
if strings.Index(w, possibleMethodFuncName) == 0 { if strings.Index(w, possibleMethodFuncName) == 0 {
method = httpMethod method = httpMethod
@ -437,9 +462,9 @@ func (p *parser) parse() (method, path string, err error) {
continue continue
} }
// static path. // static path.
path += "/" + strings.ToLower(w) path += "/" + strings.ToLower(w)
} }
return return

View File

@ -1,34 +1,30 @@
package mvc2_test package mvc2_test
import ( import (
"fmt"
"testing" "testing"
"time"
"github.com/kataras/iris" "github.com/kataras/iris"
"github.com/kataras/iris/httptest" "github.com/kataras/iris/httptest"
// "github.com/kataras/iris/mvc"
// "github.com/kataras/iris/mvc/activator/methodfunc"
. "github.com/kataras/iris/mvc2" . "github.com/kataras/iris/mvc2"
) )
type testController struct { type testControllerHandle struct {
C C
Service TestService Service TestService
reqField string reqField string
} }
func (c *testController) Get() string { func (c *testControllerHandle) Get() string {
return "index" return "index"
} }
func (c *testController) BeginRequest(ctx iris.Context) { func (c *testControllerHandle) BeginRequest(ctx iris.Context) {
c.C.BeginRequest(ctx) c.C.BeginRequest(ctx)
c.reqField = ctx.URLParam("reqfield") c.reqField = ctx.URLParam("reqfield")
} }
func (c *testController) OnActivate(t *ControllerActivator) { // OnActivate(t *mvc.TController) { func (c *testControllerHandle) OnActivate(t *ControllerActivator) { // OnActivate(t *mvc.TController) {
// t.Handle("GET", "/", "Get") // t.Handle("GET", "/", "Get")
t.Handle("GET", "/histatic", "HiStatic") t.Handle("GET", "/histatic", "HiStatic")
t.Handle("GET", "/hiservice", "HiService") t.Handle("GET", "/hiservice", "HiService")
@ -36,31 +32,29 @@ func (c *testController) OnActivate(t *ControllerActivator) { // OnActivate(t *m
t.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy") t.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy")
} }
func (c *testController) HiStatic() string { func (c *testControllerHandle) HiStatic() string {
return c.reqField return c.reqField
} }
func (c *testController) HiService() string { func (c *testControllerHandle) HiService() string {
return c.Service.Say("hi") return c.Service.Say("hi")
} }
func (c *testController) HiParamBy(v string) string { func (c *testControllerHandle) HiParamBy(v string) string {
return v return v
} }
func (c *testController) HiParamEmptyInputBy() string { func (c *testControllerHandle) HiParamEmptyInputBy() string {
return "empty in but served with ctx.Params.Get('ps')=" + c.Ctx.Params().Get("ps") return "empty in but served with ctx.Params.Get('ps')=" + c.Ctx.Params().Get("ps")
} }
func TestControllerHandler(t *testing.T) { func TestControllerHandle(t *testing.T) {
app := iris.New() app := iris.New()
// app.Controller("/", new(testController), &TestServiceImpl{prefix: "service:"})
m := New()
m.Bind(&TestServiceImpl{prefix: "service:"}).Controller(app, new(testController))
e := httptest.New(t, app, httptest.LogLevel("debug"))
fmt.Printf("\n\n\n") m := New()
now := time.Now() m.Bind(&TestServiceImpl{prefix: "service:"}).Controller(app, new(testControllerHandle))
e := httptest.New(t, app)
// test the index, is not part of the current package's implementation but do it. // test the index, is not part of the current package's implementation but do it.
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("index") e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("index")
@ -84,7 +78,4 @@ func TestControllerHandler(t *testing.T) {
Body().Equal("value") Body().Equal("value")
e.GET("/hiparamempyinput/value").Expect().Status(httptest.StatusOK). e.GET("/hiparamempyinput/value").Expect().Status(httptest.StatusOK).
Body().Equal("empty in but served with ctx.Params.Get('ps')=value") Body().Equal("empty in but served with ctx.Params.Get('ps')=value")
endTime := time.Now().Sub(now)
fmt.Printf("end at %dns\n", endTime.Nanoseconds())
} }

446
mvc2/controller_test.go Normal file
View File

@ -0,0 +1,446 @@
// black-box testing
package mvc2_test
import (
"reflect"
"testing"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
"github.com/kataras/iris/httptest"
. "github.com/kataras/iris/mvc2"
)
type testController struct {
C
}
var writeMethod = func(c C) {
c.Ctx.Writef(c.Ctx.Method())
}
func (c *testController) Get() {
writeMethod(c.C)
}
func (c *testController) Post() {
writeMethod(c.C)
}
func (c *testController) Put() {
writeMethod(c.C)
}
func (c *testController) Delete() {
writeMethod(c.C)
}
func (c *testController) Connect() {
writeMethod(c.C)
}
func (c *testController) Head() {
writeMethod(c.C)
}
func (c *testController) Patch() {
writeMethod(c.C)
}
func (c *testController) Options() {
writeMethod(c.C)
}
func (c *testController) Trace() {
writeMethod(c.C)
}
type (
testControllerAll struct{ C }
testControllerAny struct{ C } // exactly the same as All.
)
func (c *testControllerAll) All() {
writeMethod(c.C)
}
func (c *testControllerAny) Any() {
writeMethod(c.C)
}
func TestControllerMethodFuncs(t *testing.T) {
app := iris.New()
m := New()
m.Controller(app, new(testController))
m.Controller(app.Party("/all"), new(testControllerAll))
m.Controller(app.Party("/any"), new(testControllerAny))
e := httptest.New(t, app)
for _, method := range router.AllMethods {
e.Request(method, "/").Expect().Status(iris.StatusOK).
Body().Equal(method)
e.Request(method, "/all").Expect().Status(iris.StatusOK).
Body().Equal(method)
e.Request(method, "/any").Expect().Status(iris.StatusOK).
Body().Equal(method)
}
}
type testControllerBeginAndEndRequestFunc struct {
C
Username string
}
// called before of every method (Get() or Post()).
//
// useful when more than one methods using the
// same request values or context's function calls.
func (c *testControllerBeginAndEndRequestFunc) BeginRequest(ctx context.Context) {
c.C.BeginRequest(ctx)
c.Username = ctx.Params().Get("username")
}
// called after every method (Get() or Post()).
func (c *testControllerBeginAndEndRequestFunc) EndRequest(ctx context.Context) {
ctx.Writef("done") // append "done" to the response
c.C.EndRequest(ctx)
}
func (c *testControllerBeginAndEndRequestFunc) Get() {
c.Ctx.Writef(c.Username)
}
func (c *testControllerBeginAndEndRequestFunc) Post() {
c.Ctx.Writef(c.Username)
}
func TestControllerBeginAndEndRequestFunc(t *testing.T) {
app := iris.New()
New().Controller(app.Party("/profile/{username}"), new(testControllerBeginAndEndRequestFunc))
e := httptest.New(t, app)
usernames := []string{
"kataras",
"makis",
"efi",
"rg",
"bill",
"whoisyourdaddy",
}
doneResponse := "done"
for _, username := range usernames {
e.GET("/profile/" + username).Expect().Status(iris.StatusOK).
Body().Equal(username + doneResponse)
e.POST("/profile/" + username).Expect().Status(iris.StatusOK).
Body().Equal(username + doneResponse)
}
}
func TestControllerBeginAndEndRequestFuncBindMiddleware(t *testing.T) {
app := iris.New()
usernames := map[string]bool{
"kataras": true,
"makis": false,
"efi": true,
"rg": false,
"bill": true,
"whoisyourdaddy": false,
}
middlewareCheck := func(ctx context.Context) {
for username, allow := range usernames {
if ctx.Params().Get("username") == username && allow {
ctx.Next()
return
}
}
ctx.StatusCode(iris.StatusForbidden)
ctx.Writef("forbidden")
}
New().Controller(app.Party("/profile/{username}", middlewareCheck),
new(testControllerBeginAndEndRequestFunc))
e := httptest.New(t, app)
doneResponse := "done"
for username, allow := range usernames {
getEx := e.GET("/profile/" + username).Expect()
if allow {
getEx.Status(iris.StatusOK).
Body().Equal(username + doneResponse)
} else {
getEx.Status(iris.StatusForbidden).Body().Equal("forbidden")
}
postEx := e.POST("/profile/" + username).Expect()
if allow {
postEx.Status(iris.StatusOK).
Body().Equal(username + doneResponse)
} else {
postEx.Status(iris.StatusForbidden).Body().Equal("forbidden")
}
}
}
type Model struct {
Username string
}
type testControllerEndRequestAwareness struct {
C
}
func (c *testControllerEndRequestAwareness) Get() {
username := c.Ctx.Params().Get("username")
c.Ctx.Values().Set(c.Ctx.Application().ConfigurationReadOnly().GetViewDataContextKey(),
map[string]interface{}{
"TestModel": Model{Username: username},
"myModel": Model{Username: username + "2"},
})
}
func writeModels(ctx context.Context, names ...string) {
if expected, got := len(names), len(ctx.GetViewData()); expected != got {
ctx.Writef("expected view data length: %d but got: %d for names: %s", expected, got, names)
return
}
for _, name := range names {
m, ok := ctx.GetViewData()[name]
if !ok {
ctx.Writef("fail load and set the %s", name)
return
}
model, ok := m.(Model)
if !ok {
ctx.Writef("fail to override the %s' name by the tag", name)
return
}
ctx.Writef(model.Username)
}
}
func (c *testControllerEndRequestAwareness) EndRequest(ctx context.Context) {
writeModels(ctx, "TestModel", "myModel")
c.C.EndRequest(ctx)
}
func TestControllerEndRequestAwareness(t *testing.T) {
app := iris.New()
New().Controller(app.Party("/era/{username}"), new(testControllerEndRequestAwareness))
e := httptest.New(t, app)
usernames := []string{
"kataras",
"makis",
}
for _, username := range usernames {
e.GET("/era/" + username).Expect().Status(iris.StatusOK).
Body().Equal(username + username + "2")
}
}
type testBindType struct {
title string
}
type testControllerBindStruct struct {
C
// should start with upper letter of course
TitlePointer *testBindType // should have the value of the "myTitlePtr" on test
TitleValue testBindType // should have the value of the "myTitleV" on test
Other string // just another type to check the field collection, should be empty
}
func (t *testControllerBindStruct) Get() {
t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
}
type testControllerBindDeep struct {
testControllerBindStruct
}
func (t *testControllerBindDeep) Get() {
// t.testControllerBindStruct.Get()
t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
}
func TestControllerBind(t *testing.T) {
app := iris.New()
// app.Logger().SetLevel("debug")
t1, t2 := "my pointer title", "val title"
// test bind pointer to pointer of the correct type
myTitlePtr := &testBindType{title: t1}
// test bind value to value of the correct type
myTitleV := testBindType{title: t2}
m := New()
m.Bind(myTitlePtr, myTitleV)
// or just app
m.Controller(app.Party("/"), new(testControllerBindStruct))
m.Controller(app.Party("/deep"), new(testControllerBindDeep))
e := httptest.New(t, app)
expected := t1 + t2
e.GET("/").Expect().Status(iris.StatusOK).
Body().Equal(expected)
e.GET("/deep").Expect().Status(iris.StatusOK).
Body().Equal(expected)
}
type testCtrl0 struct {
testCtrl00
}
func (c *testCtrl0) Get() string {
return c.Ctx.Params().Get("username")
}
func (c *testCtrl0) EndRequest(ctx context.Context) {
if c.TitlePointer == nil {
ctx.Writef("\nTitlePointer is nil!\n")
} else {
ctx.Writef(c.TitlePointer.title)
}
//should be the same as `.testCtrl000.testCtrl0000.EndRequest(ctx)`
c.testCtrl00.EndRequest(ctx)
}
type testCtrl00 struct {
testCtrl000
}
type testCtrl000 struct {
testCtrl0000
TitlePointer *testBindType
}
type testCtrl0000 struct {
C
}
func (c *testCtrl0000) EndRequest(ctx context.Context) {
ctx.Writef("finish")
}
func TestControllerInsideControllerRecursively(t *testing.T) {
var (
username = "gerasimos"
title = "mytitle"
expected = username + title + "finish"
)
app := iris.New()
New().Bind(&testBindType{title: title}).
Controller(app.Party("/user/{username}"), new(testCtrl0))
e := httptest.New(t, app)
e.GET("/user/" + username).Expect().
Status(iris.StatusOK).Body().Equal(expected)
}
type testControllerRelPathFromFunc struct{ C }
func (c *testControllerRelPathFromFunc) EndRequest(ctx context.Context) {
ctx.Writef("%s:%s", ctx.Method(), ctx.Path())
c.C.EndRequest(ctx)
}
func (c *testControllerRelPathFromFunc) Get() {}
func (c *testControllerRelPathFromFunc) GetBy(int64) {}
func (c *testControllerRelPathFromFunc) GetAnythingByWildcard(string) {}
func (c *testControllerRelPathFromFunc) GetLogin() {}
func (c *testControllerRelPathFromFunc) PostLogin() {}
func (c *testControllerRelPathFromFunc) GetAdminLogin() {}
func (c *testControllerRelPathFromFunc) PutSomethingIntoThis() {}
func (c *testControllerRelPathFromFunc) GetSomethingBy(bool) {}
func (c *testControllerRelPathFromFunc) GetSomethingByBy(string, int) {}
func (c *testControllerRelPathFromFunc) GetSomethingNewBy(string, int) {} // two input arguments, one By which is the latest word.
func (c *testControllerRelPathFromFunc) GetSomethingByElseThisBy(bool, int) {} // two input arguments
func TestControllerRelPathFromFunc(t *testing.T) {
app := iris.New()
New().Controller(app, new(testControllerRelPathFromFunc))
e := httptest.New(t, app)
e.GET("/").Expect().Status(iris.StatusOK).
Body().Equal("GET:/")
e.GET("/42").Expect().Status(iris.StatusOK).
Body().Equal("GET:/42")
e.GET("/something/true").Expect().Status(iris.StatusOK).
Body().Equal("GET:/something/true")
e.GET("/something/false").Expect().Status(iris.StatusOK).
Body().Equal("GET:/something/false")
e.GET("/something/truee").Expect().Status(iris.StatusNotFound)
e.GET("/something/falsee").Expect().Status(iris.StatusNotFound)
e.GET("/something/kataras/42").Expect().Status(iris.StatusOK).
Body().Equal("GET:/something/kataras/42")
e.GET("/something/new/kataras/42").Expect().Status(iris.StatusOK).
Body().Equal("GET:/something/new/kataras/42")
e.GET("/something/true/else/this/42").Expect().Status(iris.StatusOK).
Body().Equal("GET:/something/true/else/this/42")
e.GET("/login").Expect().Status(iris.StatusOK).
Body().Equal("GET:/login")
e.POST("/login").Expect().Status(iris.StatusOK).
Body().Equal("POST:/login")
e.GET("/admin/login").Expect().Status(iris.StatusOK).
Body().Equal("GET:/admin/login")
e.PUT("/something/into/this").Expect().Status(iris.StatusOK).
Body().Equal("PUT:/something/into/this")
e.GET("/42").Expect().Status(iris.StatusOK).
Body().Equal("GET:/42")
e.GET("/anything/here").Expect().Status(iris.StatusOK).
Body().Equal("GET:/anything/here")
}
type testControllerActivateListener struct {
C
TitlePointer *testBindType
}
func (c *testControllerActivateListener) OnActivate(ca *ControllerActivator) {
if !ca.BindTypeExists(reflect.TypeOf(&testBindType{})) {
ca.Bind(&testBindType{
title: "default title",
})
}
}
func (c *testControllerActivateListener) Get() string {
return c.TitlePointer.title
}
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.
title: "my title",
}).Controller(app.Party("/manual"), new(testControllerActivateListener))
// or
New().Controller(app.Party("/manual2"), &testControllerActivateListener{
TitlePointer: &testBindType{
title: "my title",
},
})
e := httptest.New(t, app)
e.GET("/").Expect().Status(iris.StatusOK).
Body().Equal("default title")
e.GET("/manual").Expect().Status(iris.StatusOK).
Body().Equal("my title")
e.GET("/manual2").Expect().Status(iris.StatusOK).
Body().Equal("my title")
}

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"reflect" "reflect"
"github.com/kataras/golog"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/core/router" "github.com/kataras/iris/core/router"
) )
@ -15,8 +16,6 @@ var (
) )
type Engine struct { type Engine struct {
binders []*InputBinder
Input []reflect.Value Input []reflect.Value
} }
@ -24,80 +23,53 @@ func New() *Engine {
return new(Engine) return new(Engine)
} }
func (e *Engine) Child() *Engine { func (e *Engine) Bind(values ...interface{}) *Engine {
child := New() for _, val := range values {
if v := reflect.ValueOf(val); goodVal(v) {
// copy the current parent's ctx func binders and services to this new child. e.Input = append(e.Input, v)
// if l := len(e.binders); l > 0 { }
// binders := make([]*InputBinder, l, l)
// copy(binders, e.binders)
// child.binders = binders
// }
if l := len(e.Input); l > 0 {
input := make([]reflect.Value, l, l)
copy(input, e.Input)
child.Input = input
}
return child
}
func (e *Engine) Bind(binders ...interface{}) *Engine {
for _, binder := range binders {
// typ := resolveBinderType(binder)
// var (
// b *InputBinder
// err error
// )
// if typ == functionType {
// b, err = MakeFuncInputBinder(binder)
// } else if typ == serviceType {
// b, err = MakeServiceInputBinder(binder)
// } else {
// err = errBad
// }
// if err != nil {
// continue
// }
// e.binders = append(e.binders, b)
e.Input = append(e.Input, reflect.ValueOf(binder))
} }
return e return e
} }
// BindTypeExists returns true if a binder responsible to func (e *Engine) Child() *Engine {
// bind and return a type of "typ" is already registered. child := New()
func (e *Engine) BindTypeExists(typ reflect.Type) bool {
// for _, b := range e.binders { // copy the current parent's ctx func binders and services to this new child.
// if equalTypes(b.BindType, typ) { if l := len(e.Input); l > 0 {
// return true input := make([]reflect.Value, l, l)
// } copy(input, e.Input)
// } child.Input = input
for _, in := range e.Input {
if equalTypes(in.Type(), typ) {
return true
}
} }
return false
return child
} }
func (e *Engine) Handler(handler interface{}) context.Handler { func (e *Engine) Handler(handler interface{}) context.Handler {
h, _ := MakeHandler(handler, e.binders) // it logs errors already, so on any error the "h" will be nil. h, err := MakeHandler(handler, e.Input...)
if err != nil {
golog.Errorf("mvc handler: %v", err)
}
return h return h
} }
type ActivateListener interface { func (e *Engine) Controller(router router.Party, controller BaseController, onActivate ...func(*ControllerActivator)) {
OnActivate(*ControllerActivator) ca := newControllerActivator(router, controller, e.Input...)
}
func (e *Engine) Controller(router router.Party, controller BaseController) { // give a priority to the "onActivate"
ca := newControllerActivator(e, router, controller) // callbacks, if any.
if al, ok := controller.(ActivateListener); ok { for _, cb := range onActivate {
al.OnActivate(ca) cb(ca)
} }
// check if controller has an "OnActivate" function
// which accepts the controller activator and call it.
if activateListener, ok := controller.(interface {
OnActivate(*ControllerActivator)
}); ok {
activateListener.OnActivate(ca)
}
ca.activate()
} }

View File

@ -8,7 +8,7 @@ import (
. "github.com/kataras/iris/mvc2" . "github.com/kataras/iris/mvc2"
) )
func TestMvcInAndHandler(t *testing.T) { func TestMvcEngineInAndHandler(t *testing.T) {
m := New().Bind(testBinderFuncUserStruct, testBinderService, testBinderFuncParam) m := New().Bind(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
var ( var (

View File

@ -394,18 +394,18 @@ func (r View) Dispatch(ctx context.Context) { // r as Response view.
// In order to respect any c.Ctx.ViewData that may called manually before; // In order to respect any c.Ctx.ViewData that may called manually before;
dataKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey() dataKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey()
if ctx.Values().Get(dataKey) == nil { if ctx.Values().Get(dataKey) == nil {
// if no c.Ctx.ViewData then it's empty do a // if no c.Ctx.ViewData set-ed before (the most common scenario) then do a
// pure set, it's faster. // simple set, it's faster.
ctx.Values().Set(dataKey, r.Data) ctx.Values().Set(dataKey, r.Data)
} else { } else {
// else check if r.Data is map or struct, if struct convert it to map, // else check if r.Data is map or struct, if struct convert it to map,
// do a range loop and set the data one by one. // do a range loop and modify the data one by one.
// context.Map is actually a map[string]interface{} but we have to make that check; // context.Map is actually a map[string]interface{} but we have to make that check:
if m, ok := r.Data.(map[string]interface{}); ok { if m, ok := r.Data.(map[string]interface{}); ok {
setViewData(ctx, m) setViewData(ctx, m)
} else if m, ok := r.Data.(context.Map); ok { } else if m, ok := r.Data.(context.Map); ok {
setViewData(ctx, m) setViewData(ctx, m)
} else if structs.IsStruct(r.Data) { } else if indirectVal(reflect.ValueOf(r.Data)).Kind() == reflect.Struct {
setViewData(ctx, structs.Map(r)) setViewData(ctx, structs.Map(r))
} }
} }

275
mvc2/func_result_test.go Normal file
View File

@ -0,0 +1,275 @@
package mvc2_test
import (
"errors"
"testing"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/httptest"
. "github.com/kataras/iris/mvc2"
)
// activator/methodfunc/func_caller.go.
// and activator/methodfunc/func_result_dispatcher.go
type testControllerMethodResult struct {
C
}
func (c *testControllerMethodResult) Get() Result {
return Response{
Text: "Hello World!",
}
}
func (c *testControllerMethodResult) GetWithStatus() Response { // or Result again, no problem.
return Response{
Text: "This page doesn't exist",
Code: iris.StatusNotFound,
}
}
type testCustomStruct struct {
Name string `json:"name" xml:"name"`
Age int `json:"age" xml:"age"`
}
func (c *testControllerMethodResult) GetJson() Result {
var err error
if c.Ctx.URLParamExists("err") {
err = errors.New("error here")
}
return Response{
Err: err, // if err != nil then it will fire the error's text with a BadRequest.
Object: testCustomStruct{Name: "Iris", Age: 2},
}
}
var things = []string{"thing 0", "thing 1", "thing 2"}
func (c *testControllerMethodResult) GetThingWithTryBy(index int) Result {
failure := Response{
Text: "thing does not exist",
Code: iris.StatusNotFound,
}
return Try(func() Result {
// if panic because of index exceed the slice
// then the "failure" response will be returned instead.
return Response{Text: things[index]}
}, failure)
}
func (c *testControllerMethodResult) GetThingWithTryDefaultBy(index int) Result {
return Try(func() Result {
// if panic because of index exceed the slice
// then the default failure response will be returned instead (400 bad request).
return Response{Text: things[index]}
})
}
func TestControllerMethodResult(t *testing.T) {
app := iris.New()
New().Controller(app, new(testControllerMethodResult))
e := httptest.New(t, app)
e.GET("/").Expect().Status(iris.StatusOK).
Body().Equal("Hello World!")
e.GET("/with/status").Expect().Status(iris.StatusNotFound).
Body().Equal("This page doesn't exist")
e.GET("/json").Expect().Status(iris.StatusOK).
JSON().Equal(iris.Map{
"name": "Iris",
"age": 2,
})
e.GET("/json").WithQuery("err", true).Expect().
Status(iris.StatusBadRequest).
Body().Equal("error here")
e.GET("/thing/with/try/1").Expect().
Status(iris.StatusOK).
Body().Equal("thing 1")
// failure because of index exceed the slice
e.GET("/thing/with/try/3").Expect().
Status(iris.StatusNotFound).
Body().Equal("thing does not exist")
e.GET("/thing/with/try/default/3").Expect().
Status(iris.StatusBadRequest).
Body().Equal("Bad Request")
}
type testControllerMethodResultTypes struct {
C
}
func (c *testControllerMethodResultTypes) GetText() string {
return "text"
}
func (c *testControllerMethodResultTypes) GetStatus() int {
return iris.StatusBadGateway
}
func (c *testControllerMethodResultTypes) GetTextWithStatusOk() (string, int) {
return "OK", iris.StatusOK
}
// tests should have output arguments mixed
func (c *testControllerMethodResultTypes) GetStatusWithTextNotOkBy(first string, second string) (int, string) {
return iris.StatusForbidden, "NOT_OK_" + first + second
}
func (c *testControllerMethodResultTypes) GetTextAndContentType() (string, string) {
return "<b>text</b>", "text/html"
}
type testControllerMethodCustomResult struct {
HTML string
}
// The only one required function to make that a custom Response dispatcher.
func (r testControllerMethodCustomResult) Dispatch(ctx context.Context) {
ctx.HTML(r.HTML)
}
func (c *testControllerMethodResultTypes) GetCustomResponse() testControllerMethodCustomResult {
return testControllerMethodCustomResult{"<b>text</b>"}
}
func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusOk() (testControllerMethodCustomResult, int) {
return testControllerMethodCustomResult{"<b>OK</b>"}, iris.StatusOK
}
func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusNotOk() (testControllerMethodCustomResult, int) {
return testControllerMethodCustomResult{"<b>internal server error</b>"}, iris.StatusInternalServerError
}
func (c *testControllerMethodResultTypes) GetCustomStruct() testCustomStruct {
return testCustomStruct{"Iris", 2}
}
func (c *testControllerMethodResultTypes) GetCustomStructWithStatusNotOk() (testCustomStruct, int) {
return testCustomStruct{"Iris", 2}, iris.StatusInternalServerError
}
func (c *testControllerMethodResultTypes) GetCustomStructWithContentType() (testCustomStruct, string) {
return testCustomStruct{"Iris", 2}, "text/xml"
}
func (c *testControllerMethodResultTypes) GetCustomStructWithError() (s testCustomStruct, err error) {
s = testCustomStruct{"Iris", 2}
if c.Ctx.URLParamExists("err") {
err = errors.New("omit return of testCustomStruct and fire error")
}
// it should send the testCustomStruct as JSON if error is nil
// otherwise it should fire the default error(BadRequest) with the error's text.
return
}
func TestControllerMethodResultTypes(t *testing.T) {
app := iris.New()
New().Controller(app, new(testControllerMethodResultTypes))
e := httptest.New(t, app, httptest.LogLevel("debug"))
e.GET("/text").Expect().Status(iris.StatusOK).
Body().Equal("text")
e.GET("/status").Expect().Status(iris.StatusBadGateway)
e.GET("/text/with/status/ok").Expect().Status(iris.StatusOK).
Body().Equal("OK")
e.GET("/status/with/text/not/ok/first/second").Expect().Status(iris.StatusForbidden).
Body().Equal("NOT_OK_firstsecond")
// Author's note: <-- if that fails means that the last binder called for both input args,
// see path_param_binder.go
e.GET("/text/and/content/type").Expect().Status(iris.StatusOK).
ContentType("text/html", "utf-8").
Body().Equal("<b>text</b>")
e.GET("/custom/response").Expect().Status(iris.StatusOK).
ContentType("text/html", "utf-8").
Body().Equal("<b>text</b>")
e.GET("/custom/response/with/status/ok").Expect().Status(iris.StatusOK).
ContentType("text/html", "utf-8").
Body().Equal("<b>OK</b>")
e.GET("/custom/response/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
ContentType("text/html", "utf-8").
Body().Equal("<b>internal server error</b>")
expectedResultFromCustomStruct := map[string]interface{}{
"name": "Iris",
"age": 2,
}
e.GET("/custom/struct").Expect().Status(iris.StatusOK).
JSON().Equal(expectedResultFromCustomStruct)
e.GET("/custom/struct/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
JSON().Equal(expectedResultFromCustomStruct)
e.GET("/custom/struct/with/content/type").Expect().Status(iris.StatusOK).
ContentType("text/xml", "utf-8")
e.GET("/custom/struct/with/error").Expect().Status(iris.StatusOK).
JSON().Equal(expectedResultFromCustomStruct)
e.GET("/custom/struct/with/error").WithQuery("err", true).Expect().
Status(iris.StatusBadRequest). // the default status code if error is not nil
// the content should be not JSON it should be the status code's text
// it will fire the error's text
Body().Equal("omit return of testCustomStruct and fire error")
}
type testControllerViewResultRespectCtxViewData struct {
T *testing.T
C
}
func (t *testControllerViewResultRespectCtxViewData) BeginRequest(ctx context.Context) {
t.C.BeginRequest(ctx)
ctx.ViewData("name_begin", "iris_begin")
}
func (t *testControllerViewResultRespectCtxViewData) EndRequest(ctx context.Context) {
t.C.EndRequest(ctx)
// check if data is not overridden by return View {Data: context.Map...}
dataWritten := ctx.GetViewData()
if dataWritten == nil {
t.T.Fatalf("view data is nil, both BeginRequest and Get failed to write the data")
return
}
if dataWritten["name_begin"] == nil {
t.T.Fatalf(`view data[name_begin] is nil,
BeginRequest's ctx.ViewData call have been overridden by Get's return View {Data: }.
Total view data: %v`, dataWritten)
}
if dataWritten["name"] == nil {
t.T.Fatalf("view data[name] is nil, Get's return View {Data: } didn't work. Total view data: %v", dataWritten)
}
}
func (t *testControllerViewResultRespectCtxViewData) Get() Result {
return View{
Name: "doesnt_exists.html",
Data: context.Map{"name": "iris"}, // we care about this only.
Code: iris.StatusInternalServerError,
}
}
func TestControllerViewResultRespectCtxViewData(t *testing.T) {
app := iris.New()
New().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) {
ca.Bind(t)
})
e := httptest.New(t, app)
e.GET("/").Expect().Status(iris.StatusInternalServerError)
}

View File

@ -3,10 +3,10 @@ package mvc2
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"runtime"
"github.com/kataras/golog" "github.com/kataras/golog"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/kataras/iris/mvc/activator/methodfunc"
) )
// checks if "handler" is context.Handler; func(context.Context). // checks if "handler" is context.Handler; func(context.Context).
@ -28,18 +28,13 @@ func validateHandler(handler interface{}) error {
return nil return nil
} }
var ( // MustMakeHandler calls the `MakeHandler` and panics on any error.
contextTyp = reflect.TypeOf(context.NewContext(nil)) func MustMakeHandler(handler interface{}, bindValues ...reflect.Value) context.Handler {
emptyIn = []reflect.Value{} h, err := MakeHandler(handler, bindValues...)
)
// MustMakeHandler calls the `MakeHandler` and returns its first resultthe low-level handler), see its docs.
// It panics on error.
func MustMakeHandler(handler interface{}, binders ...interface{}) context.Handler {
h, err := MakeHandler(handler, binders...)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return h return h
} }
@ -48,9 +43,8 @@ func MustMakeHandler(handler interface{}, binders ...interface{}) context.Handle
// custom structs, Result(View | Response) and anything that you already know that mvc implementation supports, // custom structs, Result(View | Response) and anything that you already know that mvc implementation supports,
// and returns a low-level `context/iris.Handler` which can be used anywhere in the Iris Application, // and returns a low-level `context/iris.Handler` which can be used anywhere in the Iris Application,
// as middleware or as simple route handler or party handler or subdomain handler-router. // as middleware or as simple route handler or party handler or subdomain handler-router.
func MakeHandler(handler interface{}, binders ...interface{}) (context.Handler, error) { func MakeHandler(handler interface{}, bindValues ...reflect.Value) (context.Handler, error) {
if err := validateHandler(handler); err != nil { if err := validateHandler(handler); err != nil {
golog.Errorf("mvc handler: %v", err)
return nil, err return nil, err
} }
@ -59,71 +53,39 @@ func MakeHandler(handler interface{}, binders ...interface{}) (context.Handler,
return h, nil return h, nil
} }
inputBinders := make([]reflect.Value, len(binders), len(binders)) fn := reflect.ValueOf(handler)
for i := range binders {
inputBinders[i] = reflect.ValueOf(binders[i])
}
return makeHandler(reflect.ValueOf(handler), inputBinders), nil
// typ := indirectTyp(reflect.TypeOf(handler))
// n := typ.NumIn()
// typIn := make([]reflect.Type, n, n)
// for i := 0; i < n; i++ {
// typIn[i] = typ.In(i)
// }
// m := getBindersForInput(binders, typIn...)
// if len(m) != n {
// err := fmt.Errorf("input arguments length(%d) of types(%s) and valid binders length(%d) are not equal", n, typIn, len(m))
// golog.Errorf("mvc handler: %v", err)
// return nil, err
// }
// return makeHandler(reflect.ValueOf(handler), m), nil
}
func makeHandler(fn reflect.Value, inputBinders []reflect.Value) context.Handler {
inLen := fn.Type().NumIn()
if inLen == 0 {
return func(ctx context.Context) {
methodfunc.DispatchFuncResult(ctx, fn.Call(emptyIn))
}
}
s := getServicesFor(fn, inputBinders)
if len(s) == 0 {
golog.Errorf("mvc handler: input arguments length(%d) and valid binders length(%d) are not equal", inLen, len(s))
return nil
}
n := fn.Type().NumIn() n := fn.Type().NumIn()
// contextIndex := -1
// if n > 0 {
// if isContext(fn.Type().In(0)) {
// contextIndex = 0
// }
// }
return func(ctx context.Context) {
ctxValue := []reflect.Value{reflect.ValueOf(ctx)}
in := make([]reflect.Value, n, n) if n == 0 {
// if contextIndex >= 0 { h := func(ctx context.Context) {
// in[contextIndex] = ctxValue[0] DispatchFuncResult(ctx, fn.Call(emptyIn))
// } }
// ctxValues := []reflect.Value{reflect.ValueOf(ctx)}
// for k, v := range m {
// in[k] = v.BindFunc(ctxValues)
// if ctx.IsStopped() {
// return
// }
// }
// methodfunc.DispatchFuncResult(ctx, fn.Call(in))
s.FillFuncInput(ctxValue, &in) return h, nil
methodfunc.DispatchFuncResult(ctx, fn.Call(in))
} }
s := newTargetFunc(fn, bindValues...)
if !s.Valid {
pc := fn.Pointer()
fpc := runtime.FuncForPC(pc)
callerFileName, callerLineNumber := fpc.FileLine(pc)
callerName := fpc.Name()
err := fmt.Errorf("input arguments length(%d) and valid binders length(%d) are not equal for typeof '%s' which is defined at %s:%d by %s",
n, len(s.Inputs), fn.Type().String(), callerFileName, callerLineNumber, callerName)
return nil, err
}
h := func(ctx context.Context) {
in := make([]reflect.Value, n, n)
s.Fill(&in, reflect.ValueOf(ctx))
if ctx.IsStopped() {
return
}
DispatchFuncResult(ctx, fn.Call(in))
}
return h, nil
} }

View File

@ -1,271 +0,0 @@
package mvc2_test
// import (
// "errors"
// "testing"
// "github.com/kataras/iris"
// "github.com/kataras/iris/context"
// "github.com/kataras/iris/httptest"
// "github.com/kataras/iris/mvc2"
// )
// // activator/methodfunc/func_caller.go.
// // and activator/methodfunc/func_result_dispatcher.go
// type testControllerMethodResult struct {
// mvc2.C
// }
// func (c *testControllerMethodResult) Get() mvc2.Result {
// return mvc2.Response{
// Text: "Hello World!",
// }
// }
// func (c *testControllerMethodResult) GetWithStatus() mvc2.Response { // or mvc.Result again, no problem.
// return mvc2.Response{
// Text: "This page doesn't exist",
// Code: iris.StatusNotFound,
// }
// }
// type testCustomStruct struct {
// Name string `json:"name" xml:"name"`
// Age int `json:"age" xml:"age"`
// }
// func (c *testControllerMethodResult) GetJson() mvc2.Result {
// var err error
// if c.Ctx.URLParamExists("err") {
// err = errors.New("error here")
// }
// return mvc2.Response{
// Err: err, // if err != nil then it will fire the error's text with a BadRequest.
// Object: testCustomStruct{Name: "Iris", Age: 2},
// }
// }
// var things = []string{"thing 0", "thing 1", "thing 2"}
// func (c *testControllerMethodResult) GetThingWithTryBy(index int) mvc2.Result {
// failure := mvc2.Response{
// Text: "thing does not exist",
// Code: iris.StatusNotFound,
// }
// return mvc2.Try(func() mvc2.Result {
// // if panic because of index exceed the slice
// // then the "failure" response will be returned instead.
// return mvc2.Response{Text: things[index]}
// }, failure)
// }
// func (c *testControllerMethodResult) GetThingWithTryDefaultBy(index int) mvc2.Result {
// return mvc2.Try(func() mvc2.Result {
// // if panic because of index exceed the slice
// // then the default failure response will be returned instead (400 bad request).
// return mvc2.Response{Text: things[index]}
// })
// }
// func TestControllerMethodResult(t *testing.T) {
// app := iris.New()
// app.Controller("/", new(testControllerMethodResult))
// e := httptest.New(t, app)
// e.GET("/").Expect().Status(iris.StatusOK).
// Body().Equal("Hello World!")
// e.GET("/with/status").Expect().Status(iris.StatusNotFound).
// Body().Equal("This page doesn't exist")
// e.GET("/json").Expect().Status(iris.StatusOK).
// JSON().Equal(iris.Map{
// "name": "Iris",
// "age": 2,
// })
// e.GET("/json").WithQuery("err", true).Expect().
// Status(iris.StatusBadRequest).
// Body().Equal("error here")
// e.GET("/thing/with/try/1").Expect().
// Status(iris.StatusOK).
// Body().Equal("thing 1")
// // failure because of index exceed the slice
// e.GET("/thing/with/try/3").Expect().
// Status(iris.StatusNotFound).
// Body().Equal("thing does not exist")
// e.GET("/thing/with/try/default/3").Expect().
// Status(iris.StatusBadRequest).
// Body().Equal("Bad Request")
// }
// type testControllerMethodResultTypes struct {
// mvc2.C
// }
// func (c *testControllerMethodResultTypes) GetText() string {
// return "text"
// }
// func (c *testControllerMethodResultTypes) GetStatus() int {
// return iris.StatusBadGateway
// }
// func (c *testControllerMethodResultTypes) GetTextWithStatusOk() (string, int) {
// return "OK", iris.StatusOK
// }
// // tests should have output arguments mixed
// func (c *testControllerMethodResultTypes) GetStatusWithTextNotOkBy(first string, second string) (int, string) {
// return iris.StatusForbidden, "NOT_OK_" + first + second
// }
// func (c *testControllerMethodResultTypes) GetTextAndContentType() (string, string) {
// return "<b>text</b>", "text/html"
// }
// type testControllerMethodCustomResult struct {
// HTML string
// }
// // The only one required function to make that a custom Response dispatcher.
// func (r testControllerMethodCustomResult) Dispatch(ctx context.Context) {
// ctx.HTML(r.HTML)
// }
// func (c *testControllerMethodResultTypes) GetCustomResponse() testControllerMethodCustomResult {
// return testControllerMethodCustomResult{"<b>text</b>"}
// }
// func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusOk() (testControllerMethodCustomResult, int) {
// return testControllerMethodCustomResult{"<b>OK</b>"}, iris.StatusOK
// }
// func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusNotOk() (testControllerMethodCustomResult, int) {
// return testControllerMethodCustomResult{"<b>internal server error</b>"}, iris.StatusInternalServerError
// }
// func (c *testControllerMethodResultTypes) GetCustomStruct() testCustomStruct {
// return testCustomStruct{"Iris", 2}
// }
// func (c *testControllerMethodResultTypes) GetCustomStructWithStatusNotOk() (testCustomStruct, int) {
// return testCustomStruct{"Iris", 2}, iris.StatusInternalServerError
// }
// func (c *testControllerMethodResultTypes) GetCustomStructWithContentType() (testCustomStruct, string) {
// return testCustomStruct{"Iris", 2}, "text/xml"
// }
// func (c *testControllerMethodResultTypes) GetCustomStructWithError() (s testCustomStruct, err error) {
// s = testCustomStruct{"Iris", 2}
// if c.Ctx.URLParamExists("err") {
// err = errors.New("omit return of testCustomStruct and fire error")
// }
// // it should send the testCustomStruct as JSON if error is nil
// // otherwise it should fire the default error(BadRequest) with the error's text.
// return
// }
// func TestControllerMethodResultTypes(t *testing.T) {
// app := iris.New()
// app.Controller("/", new(testControllerMethodResultTypes))
// e := httptest.New(t, app)
// e.GET("/text").Expect().Status(iris.StatusOK).
// Body().Equal("text")
// e.GET("/status").Expect().Status(iris.StatusBadGateway)
// e.GET("/text/with/status/ok").Expect().Status(iris.StatusOK).
// Body().Equal("OK")
// e.GET("/status/with/text/not/ok/first/second").Expect().Status(iris.StatusForbidden).
// Body().Equal("NOT_OK_firstsecond")
// e.GET("/text/and/content/type").Expect().Status(iris.StatusOK).
// ContentType("text/html", "utf-8").
// Body().Equal("<b>text</b>")
// e.GET("/custom/response").Expect().Status(iris.StatusOK).
// ContentType("text/html", "utf-8").
// Body().Equal("<b>text</b>")
// e.GET("/custom/response/with/status/ok").Expect().Status(iris.StatusOK).
// ContentType("text/html", "utf-8").
// Body().Equal("<b>OK</b>")
// e.GET("/custom/response/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
// ContentType("text/html", "utf-8").
// Body().Equal("<b>internal server error</b>")
// expectedResultFromCustomStruct := map[string]interface{}{
// "name": "Iris",
// "age": 2,
// }
// e.GET("/custom/struct").Expect().Status(iris.StatusOK).
// JSON().Equal(expectedResultFromCustomStruct)
// e.GET("/custom/struct/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
// JSON().Equal(expectedResultFromCustomStruct)
// e.GET("/custom/struct/with/content/type").Expect().Status(iris.StatusOK).
// ContentType("text/xml", "utf-8")
// e.GET("/custom/struct/with/error").Expect().Status(iris.StatusOK).
// JSON().Equal(expectedResultFromCustomStruct)
// e.GET("/custom/struct/with/error").WithQuery("err", true).Expect().
// Status(iris.StatusBadRequest). // the default status code if error is not nil
// // the content should be not JSON it should be the status code's text
// // it will fire the error's text
// Body().Equal("omit return of testCustomStruct and fire error")
// }
// type testControllerViewResultRespectCtxViewData struct {
// T *testing.T
// mvc2.C
// }
// func (t *testControllerViewResultRespectCtxViewData) BeginRequest(ctx context.Context) {
// t.C.BeginRequest(ctx)
// ctx.ViewData("name_begin", "iris_begin")
// }
// func (t *testControllerViewResultRespectCtxViewData) EndRequest(ctx context.Context) {
// t.C.EndRequest(ctx)
// // check if data is not overridden by return mvc.View {Data: context.Map...}
// dataWritten := ctx.GetViewData()
// if dataWritten == nil {
// t.T.Fatalf("view data is nil, both BeginRequest and Get failed to write the data")
// return
// }
// if dataWritten["name_begin"] == nil {
// t.T.Fatalf(`view data[name_begin] is nil,
// BeginRequest's ctx.ViewData call have been overridden by Get's return mvc.View {Data: }.
// Total view data: %v`, dataWritten)
// }
// if dataWritten["name"] == nil {
// t.T.Fatalf("view data[name] is nil, Get's return mvc.View {Data: } didn't work. Total view data: %v", dataWritten)
// }
// }
// func (t *testControllerViewResultRespectCtxViewData) Get() mvc2.Result {
// return mvc2.View{
// Name: "doesnt_exists.html",
// Data: context.Map{"name": "iris"}, // we care about this only.
// Code: iris.StatusInternalServerError,
// }
// }
// func TestControllerViewResultRespectCtxViewData(t *testing.T) {
// app := iris.New()
// app.Controller("/", new(testControllerViewResultRespectCtxViewData), t)
// e := httptest.New(t, app)
// e.GET("/").Expect().Status(iris.StatusInternalServerError)
// }

View File

@ -4,6 +4,7 @@ package mvc2_test
import ( import (
"fmt" "fmt"
"reflect"
"testing" "testing"
"github.com/kataras/iris" "github.com/kataras/iris"
@ -69,19 +70,10 @@ var (
) )
func TestMakeHandler(t *testing.T) { func TestMakeHandler(t *testing.T) {
// binders := []*InputBinder{
// // #1
// MustMakeFuncInputBinder(testBinderFuncUserStruct),
// // #2
// MustMakeServiceInputBinder(testBinderService),
// // #3
// MustMakeFuncInputBinder(testBinderFuncParam),
// }
var ( var (
h1 = MustMakeHandler(testConsumeUserHandler, testBinderFuncUserStruct) h1 = MustMakeHandler(testConsumeUserHandler, reflect.ValueOf(testBinderFuncUserStruct))
h2 = MustMakeHandler(testConsumeServiceHandler, testBinderService) h2 = MustMakeHandler(testConsumeServiceHandler, reflect.ValueOf(testBinderService))
h3 = MustMakeHandler(testConsumeParamHandler, testBinderFuncParam) h3 = MustMakeHandler(testConsumeParamHandler, reflect.ValueOf(testBinderFuncParam))
) )
testAppWithMvcHandlers(t, h1, h2, h3) testAppWithMvcHandlers(t, h1, h2, h3)

View File

@ -1,7 +1,6 @@
package mvc2 package mvc2
import ( import (
"fmt"
"reflect" "reflect"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
@ -10,14 +9,7 @@ import (
"github.com/kataras/iris/core/router/macro/interpreter/ast" "github.com/kataras/iris/core/router/macro/interpreter/ast"
) )
func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type { // for methods inside a controller.
n := funcTyp.NumIn()
funcIn := make([]reflect.Type, n, n)
for i := 0; i < n; i++ {
funcIn[i] = funcTyp.In(i)
}
return funcIn
}
func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) (values []reflect.Value) { func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) (values []reflect.Value) {
if len(funcIn) == 0 || len(params) == 0 { if len(funcIn) == 0 || len(params) == 0 {
@ -30,72 +22,49 @@ func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type)
in := funcIn[funcInIdx] in := funcIn[funcInIdx]
paramType := p.Type paramType := p.Type
paramName := p.Name paramName := p.Name
// fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String())
// fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String()) if paramType.Assignable(in.Kind()) {
if p.Type.Assignable(in.Kind()) { // fmt.Printf("path_param_binder.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String())
values = append(values, makeFuncParamGetter(paramType, paramName))
// b = append(b, &InputBinder{
// BindType: in, // or p.Type.Kind, should be the same.
// BindFunc: func(ctx []reflect.Value) reflect.Value {
// // I don't like this ctx[0].Interface(0)
// // it will be slow, and silly because we have ctx already
// // before the bindings at serve-time, so we will create
// // a func for each one of the param types, they are just 4 so
// // it worths some dublications.
// return getParamValueFromType(ctx[0].Interface(), paramType, paramName)
// },
// })
var fn interface{}
if paramType == ast.ParamTypeInt {
fn = func(ctx context.Context) int {
v, _ := ctx.Params().GetInt(paramName)
return v
}
} else if paramType == ast.ParamTypeLong {
fn = func(ctx context.Context) int64 {
v, _ := ctx.Params().GetInt64(paramName)
return v
}
} else if paramType == ast.ParamTypeBoolean {
fn = func(ctx context.Context) bool {
v, _ := ctx.Params().GetBool(paramName)
return v
}
} else {
// string, path...
fn = func(ctx context.Context) string {
return ctx.Params().Get(paramName)
}
}
fmt.Printf("binder_in_path_param.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String())
values = append(values, reflect.ValueOf(fn))
// inputBinder, err := MakeFuncInputBinder(fn)
// if err != nil {
// fmt.Printf("err on make func binder: %v\n", err.Error())
// continue
// }
// if m == nil {
// m = make(bindersMap, 0)
// }
// // fmt.Printf("set param input binder for func arg index: %d\n", funcInIdx)
// m[funcInIdx] = inputBinder
} }
funcInIdx++ funcInIdx++
} }
return return
// return m
} }
func makeFuncParamGetter(paramType ast.ParamType, paramName string) reflect.Value {
var fn interface{}
switch paramType {
case ast.ParamTypeInt:
fn = func(ctx context.Context) int {
v, _ := ctx.Params().GetInt(paramName)
return v
}
case ast.ParamTypeLong:
fn = func(ctx context.Context) int64 {
v, _ := ctx.Params().GetInt64(paramName)
return v
}
case ast.ParamTypeBoolean:
fn = func(ctx context.Context) bool {
v, _ := ctx.Params().GetBool(paramName)
return v
}
default:
// string, path...
fn = func(ctx context.Context) string {
return ctx.Params().Get(paramName)
}
}
return reflect.ValueOf(fn)
}
// for raw handlers, independent of a controller.
// PathParams is the context's named path parameters, see `PathParamsBinder` too. // PathParams is the context's named path parameters, see `PathParamsBinder` too.
type PathParams = context.RequestParams type PathParams = context.RequestParams

View File

@ -1,6 +1,13 @@
package mvc2 package mvc2
import "reflect" import (
"reflect"
"github.com/kataras/iris/context"
"github.com/kataras/pkg/zerocheck"
)
var contextTyp = reflect.TypeOf(context.NewContext(nil))
func isContext(inTyp reflect.Type) bool { func isContext(inTyp reflect.Type) bool {
return inTyp.String() == "context.Context" // I couldn't find another way; context/context.go is not exported. return inTyp.String() == "context.Context" // I couldn't find another way; context/context.go is not exported.
@ -29,23 +36,12 @@ func goodVal(v reflect.Value) bool {
return v.IsValid() return v.IsValid()
} }
func isFunc(typ reflect.Type) bool { func isFunc(kindable interface {
return typ.Kind() == reflect.Func Kind() reflect.Kind
}) bool {
return kindable.Kind() == reflect.Func
} }
/*
// no f. this, it's too complicated and it will be harder to maintain later on:
func isSliceAndExpectedItem(got reflect.Type, in []reflect.Type, currentBindersIdx int) bool {
kind := got.Kind()
// if got result is slice or array.
return (kind == reflect.Slice || kind == reflect.Array) &&
// if has expected next input.
len(in)-1 > currentBindersIdx &&
// if the current input's type is not the same as got (if it's not a slice of that types or anything else).
equalTypes(got, in[currentBindersIdx])
}
*/
func equalTypes(got reflect.Type, expected reflect.Type) bool { func equalTypes(got reflect.Type, expected reflect.Type) bool {
if got == expected { if got == expected {
return true return true
@ -59,8 +55,16 @@ func equalTypes(got reflect.Type, expected reflect.Type) bool {
return false return false
} }
// for controller only. func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
n := funcTyp.NumIn()
funcIn := make([]reflect.Type, n, n)
for i := 0; i < n; i++ {
funcIn[i] = funcTyp.In(i)
}
return funcIn
}
// for controller's fields only.
func structFieldIgnored(f reflect.StructField) bool { func structFieldIgnored(f reflect.StructField) bool {
if !f.Anonymous { if !f.Anonymous {
return true // if not anonymous(embedded), ignore it. return true // if not anonymous(embedded), ignore it.
@ -76,22 +80,28 @@ type field struct {
Name string // the actual name Name string // the actual name
// this could be empty, but in our cases it's not, // this could be empty, but in our cases it's not,
// it's filled with the service and it's filled from the lookupFields' caller. // 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 AnyValue reflect.Value
} }
func lookupFields(typ reflect.Type, parentIndex int) (fields []field) { func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) {
for i, n := 0, typ.NumField(); i < n; i++ { if elemTyp.Kind() != reflect.Struct {
f := typ.Field(i) return
}
if f.Type.Kind() == reflect.Struct && !structFieldIgnored(f) { for i, n := 0, elemTyp.NumField(); i < n; i++ {
fields = append(fields, lookupFields(f.Type, i)...) f := elemTyp.Field(i)
if indirectTyp(f.Type).Kind() == reflect.Struct &&
!structFieldIgnored(f) {
fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...)
continue continue
} }
index := []int{i} index := []int{i}
if parentIndex >= 0 { if len(parentIndex) > 0 {
index = append([]int{parentIndex}, index...) index = append(parentIndex, i)
} }
field := field{ field := field{
@ -105,3 +115,16 @@ func lookupFields(typ reflect.Type, parentIndex int) (fields []field) {
return 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

@ -1,206 +0,0 @@
package mvc2
import (
"fmt"
"reflect"
)
type service struct {
Type reflect.Type
Value reflect.Value
StructFieldIndex []int
// for func input.
ReturnValue func(ctx []reflect.Value) reflect.Value
FuncInputIndex int
FuncInputContextIndex int
}
type services []*service
func (serv *services) AddSource(dest reflect.Value, source ...reflect.Value) {
fmt.Println("--------------AddSource------------")
if len(source) == 0 {
return
}
typ := indirectTyp(dest.Type()) //indirectTyp(reflect.TypeOf(dest))
_serv := *serv
if typ.Kind() == reflect.Func {
n := typ.NumIn()
for i := 0; i < n; i++ {
inTyp := typ.In(i)
if isContext(inTyp) {
_serv = append(_serv, &service{FuncInputContextIndex: i})
continue
}
for _, s := range source {
gotTyp := s.Type()
service := service{
Type: gotTyp,
Value: s,
FuncInputIndex: i,
FuncInputContextIndex: -1,
}
if s.Type().Kind() == reflect.Func {
fmt.Printf("Source is Func\n")
returnValue, outType, err := makeReturnValue(s)
if err != nil {
fmt.Printf("Err on makeReturnValue: %v\n", err)
continue
}
gotTyp = outType
service.ReturnValue = returnValue
}
fmt.Printf("Types: In=%s vs Got=%s\n", inTyp.String(), gotTyp.String())
if equalTypes(gotTyp, inTyp) {
service.Type = gotTyp
fmt.Printf("Bind In=%s->%s for func\n", inTyp.String(), gotTyp.String())
_serv = append(_serv, &service)
break
}
}
}
fmt.Printf("[1] Bind %d for %s\n", len(_serv), typ.String())
*serv = _serv
return
}
if typ.Kind() == reflect.Struct {
fields := lookupFields(typ, -1)
for _, f := range fields {
for _, s := range source {
gotTyp := s.Type()
service := service{
Type: gotTyp,
Value: s,
StructFieldIndex: f.Index,
FuncInputContextIndex: -1,
}
if s.Type().Kind() == reflect.Func {
returnValue, outType, err := makeReturnValue(s)
if err != nil {
continue
}
gotTyp = outType
service.ReturnValue = returnValue
}
if equalTypes(gotTyp, f.Type) {
service.Type = gotTyp
_serv = append(_serv, &service)
fmt.Printf("[2] Bind In=%s->%s for struct field[%d]\n", f.Type, gotTyp.String(), f.Index)
break
}
}
}
fmt.Printf("[2] Bind %d for %s\n", len(_serv), typ.String())
*serv = _serv
return
}
}
func (serv services) FillStructStaticValues(elem reflect.Value) {
if len(serv) == 0 {
return
}
for _, s := range serv {
if len(s.StructFieldIndex) > 0 {
// fmt.Printf("FillStructStaticValues for index: %d\n", s.StructFieldIndex)
elem.FieldByIndex(s.StructFieldIndex).Set(s.Value)
}
}
}
func (serv services) FillStructDynamicValues(elem reflect.Value, ctx []reflect.Value) {
if len(serv) == 0 {
return
}
for _, s := range serv {
if len(s.StructFieldIndex) > 0 {
elem.FieldByIndex(s.StructFieldIndex).Set(s.ReturnValue(ctx))
}
}
}
func (serv services) FillFuncInput(ctx []reflect.Value, destIn *[]reflect.Value) {
if len(serv) == 0 {
return
}
in := *destIn
for _, s := range serv {
if s.ReturnValue != nil {
in[s.FuncInputIndex] = s.ReturnValue(ctx)
continue
}
in[s.FuncInputIndex] = s.Value
if s.FuncInputContextIndex >= 0 {
in[s.FuncInputContextIndex] = ctx[0]
}
}
*destIn = in
}
func makeReturnValue(fn reflect.Value) (func([]reflect.Value) reflect.Value, reflect.Type, error) {
typ := indirectTyp(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
}
// invalid if input args length is not one.
if typ.NumIn() != 1 {
return nil, typ, errBad
}
// invalid if that single input arg is not a typeof context.Context.
if !isContext(typ.In(0)) {
return nil, typ, errBad
}
outTyp := typ.Out(0)
zeroOutVal := reflect.New(outTyp).Elem()
bf := func(ctxValue []reflect.Value) reflect.Value {
// []reflect.Value{reflect.ValueOf(ctx)}
results := fn.Call(ctxValue) // ctxValue is like that because of; read makeHandler.
if len(results) == 0 {
return zeroOutVal
}
v := results[0]
if !v.IsValid() {
return zeroOutVal
}
return v
}
return bf, outTyp, nil
}
func getServicesFor(dest reflect.Value, source []reflect.Value) (s services) {
s.AddSource(dest, source...)
return s
}

View File

@ -1,11 +1,11 @@
package mvc2 package mvc2
import ( import (
"github.com/kataras/iris/context"
"github.com/kataras/iris/sessions"
"reflect" "reflect"
"github.com/kataras/golog" "github.com/kataras/golog"
"github.com/kataras/iris/context"
"github.com/kataras/iris/sessions"
) )
var defaultManager = sessions.New(sessions.Config{}) var defaultManager = sessions.New(sessions.Config{})
@ -25,8 +25,8 @@ type SessionController struct {
// It makes sure that its "Manager" field is filled // It makes sure that its "Manager" field is filled
// even if the caller didn't provide any sessions manager via the `app.Controller` function. // even if the caller didn't provide any sessions manager via the `app.Controller` function.
func (s *SessionController) OnActivate(ca *ControllerActivator) { func (s *SessionController) OnActivate(ca *ControllerActivator) {
if !ca.Engine.BindTypeExists(reflect.TypeOf(defaultManager)) { if !ca.BindTypeExists(reflect.TypeOf(defaultManager)) {
ca.Engine.Bind(defaultManager) ca.Bind(defaultManager)
golog.Warnf(`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field, golog.Warnf(`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. therefore this controller is using the default sessions manager instead.
Please refer to the documentation to learn how you can provide the session manager`) Please refer to the documentation to learn how you can provide the session manager`)