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
vendor/* linguist-vendored
_examples/* linguist-documentation
_benchmarks/* linguist-documentation
# Set the default behavior, in case people don't have core.autocrlf set.
# if from windows:
# git config --global core.autocrlf true
# if from unix:
# git config --global core.autocrlf input
# https://help.github.com/articles/dealing-with-line-endings/#per-repository-settings
*.go linguist-language=Go
vendor/* linguist-vendored
_examples/* linguist-documentation
_benchmarks/* linguist-documentation
# Set the default behavior, in case people don't have core.autocrlf set.
# if from windows:
# git config --global core.autocrlf true
# if from unix:
# git config --global core.autocrlf input
# https://help.github.com/articles/dealing-with-line-endings/#per-repository-settings
* 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
// 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
// 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
// filename/path (string)
// gzipCompression (bool)
//
// You can define your own "Content-Type" header also, after this function call
// This function doesn't implement resuming (by range), use ctx.SendFile instead
// You can define your own "Content-Type" with `context#ContentType`, before this function call.
//
// 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
// 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
// and methods are not available here for the developer's safety.
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,
@ -857,7 +867,11 @@ type Map map[string]interface{}
// +------------------------------------------------------------+
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
// the original http.Request
request *http.Request
@ -865,10 +879,10 @@ type context struct {
currentRouteName string
// the local key-value storage
params RequestParams // url named parameters
values memstore.Store // generic storage, middleware communication
params RequestParams // url named parameters.
values memstore.Store // generic storage, middleware communication.
// the underline application app
// the underline application app.
app Application
// the route's 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.
// Worth to notice that this function returns an interface
// 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
// direct access to the current client's session via its `Session` field.
type SessionController struct {
Controller
C
Manager *sessions.Sessions
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
// and tries to initialize the current user's Session.
func (s *SessionController) BeginRequest(ctx context.Context) {
s.Controller.BeginRequest(ctx)
s.C.BeginRequest(ctx)
if s.Manager == nil {
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`)

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/macro"
"github.com/kataras/iris/core/router/macro/interpreter/ast"
"github.com/kataras/iris/mvc/activator/methodfunc"
)
type BaseController interface {
@ -72,38 +71,52 @@ type ControllerActivator struct {
Router router.Party
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.
// i.e: if login-example/user/controller.go, the FullName is "user.Controller".
FullName string
// key = the method's name.
methods map[string]reflect.Method
// the methods names that is already binded to a handler,
// the BeginRequest, EndRequest and OnActivate are reserved by the internal implementation.
reservedMethods []string
// services []field
// bindServices func(elem reflect.Value)
s services
// input are always empty after the `activate`
// are used to build the bindings, and we need this field
// 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{
Engine: engine,
Router: router,
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()
return c
}
var reservedMethodNames = []string{
"BeginRequest",
"EndRequest",
"OnActivate",
}
func isReservedMethod(name string) bool {
for _, s := range reservedMethodNames {
func (c *ControllerActivator) isReservedMethod(name string) bool {
for _, s := range c.reservedMethods {
if s == name {
return true
}
@ -113,55 +126,86 @@ func isReservedMethod(name string) bool {
}
func (c *ControllerActivator) analyze() {
// 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()
pkgPath := val.Type().PkgPath()
fullName := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + ctrlName
c.FullName = fullName
}
// 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.
typ := reflect.TypeOf(c.initRef) // type with pointer
elemTyp := indirectTyp(typ)
// set all available, exported methods.
{
typ := reflect.TypeOf(c.initRef) // typ, with pointer
n := typ.NumMethod()
c.methods = make(map[string]reflect.Method, n)
for i := 0; i < n; i++ {
m := typ.Method(i)
key := m.Name
ctrlName := elemTyp.Name()
pkgPath := elemTyp.PkgPath()
fullName := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + ctrlName
c.FullName = fullName
c.Type = typ
if !isReservedMethod(key) {
c.methods[key] = m
}
// register all available, exported methods to handlers if possible.
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 {
if method == "" || path == "" || funcName == "" || isReservedMethod(funcName) {
if method == "" || path == "" || funcName == "" ||
c.isReservedMethod(funcName) {
// isReservedMethod -> if it's already registered
// by a previous Handle or analyze methods internally.
return errSkip
}
m, ok := c.methods[funcName]
m, ok := c.Type.MethodByName(funcName)
if !ok {
err := fmt.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller",
funcName, c.FullName)
@ -176,105 +220,84 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
return err
}
fmt.Printf("===============%s.%s==============\n", c.FullName, funcName)
funcIn := getInputArgsFromFunc(m.Type)[1:] // except the receiver, which is the controller pointer itself.
// 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.
c.reservedMethods = append(c.reservedMethods, funcName)
// get any binders for this func, if any, and
// take param binders, we can bind them because we know the path here.
// binders := joinBindersMap(
// getBindersForInput(c.Engine.binders, funcIn...),
// getPathParamsBindersForInput(tmpl.Params, funcIn...))
// fmt.Printf("===============%s.%s==============\n", c.FullName, funcName)
s := getServicesFor(m.Func, getPathParamsForInput(tmpl.Params, funcIn...))
// s.AddSource(indirectVal(reflect.ValueOf(c.initRef)), c.Engine.Input...)
funcIn := getInputArgsFromFunc(m.Type) // except the receiver, which is the controller pointer itself.
typ := reflect.TypeOf(c.initRef)
elem := indirectTyp(typ) // the value, not the pointer.
hasInputBinders := len(s) > 0
hasStructBinders := len(c.s) > 0
n := len(funcIn) + 1
pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...)
funcBindings := newTargetFunc(m.Func, pathParams...)
// be, _ := typ.MethodByName("BeginRequest")
// en, _ := typ.MethodByName("EndRequest")
// beginIndex, endIndex := be.Index, en.Index
elemTyp := indirectTyp(c.Type) // the element value, not the pointer.
n := len(funcIn)
handler := func(ctx context.Context) {
// create a new controller instance of that type(>ptr).
ctrl := reflect.New(elem)
//ctrlAndCtxValues := []reflect.Value{ctrl, ctxValue[0]}
// ctrl.MethodByName("BeginRequest").Call(ctxValue)
//begin.Func.Call(ctrlAndCtxValues)
ctrl := reflect.New(elemTyp)
b := ctrl.Interface().(BaseController) // the Interface(). is faster than MethodByName or pre-selected methods.
// init the request.
b.BeginRequest(ctx)
//ctrl.Method(beginIndex).Call(ctxValue)
// if begin request stopped the execution.
if ctx.IsStopped() {
return
}
if hasStructBinders {
elem := ctrl.Elem()
c.s.FillStructStaticValues(elem)
}
if !hasInputBinders {
methodfunc.DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
if !c.bindings.Valid && !funcBindings.Valid {
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
} else {
in := make([]reflect.Value, n, n)
// in[0] = ctrl.Elem()
in[0] = ctrl
s.FillFuncInput([]reflect.Value{reflect.ValueOf(ctx)}, &in)
methodfunc.DispatchFuncResult(ctx, m.Func.Call(in))
// in := make([]reflect.Value, n, n)
// ctxValues := []reflect.Value{reflect.ValueOf(ctx)}
// for k, v := range binders {
// in[k] = v.BindFunc(ctxValues)
ctxValue := reflect.ValueOf(ctx)
if c.bindings.Valid {
elem := ctrl.Elem()
c.bindings.Fill(elem, ctxValue)
if ctx.IsStopped() {
return
}
// 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
// if no response written already.
b.EndRequest(ctx)
// ctrl.MethodByName("EndRequest").Call(ctxValue)
// end.Func.Call(ctrlAndCtxValues)
//ctrl.Method(endIndex).Call(ctxValue)
}
// register the handler now.
r := c.Router.Handle(method, path, append(middleware, handler)...)
// change the main handler's name in order to respect the controller's and give
// a proper debug message.
r.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)
c.Router.Handle(method, path, append(middleware, handler)...).
// change the main handler's name in order to respect the controller's and give
// a proper debug message.
MainHandlerName = fmt.Sprintf("%s.%s", c.FullName, funcName)
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 (
tokenBy = "By"
tokenWildcard = "Wildcard" // i.e ByWildcard
tokenWildcard = "Wildcard" // "ByWildcard".
)
// word lexer, not characters.
@ -393,13 +416,15 @@ func methodTitle(httpMethod string) string {
var errSkip = errors.New("skip")
var allMethods = append(router.AllMethods[0:], []string{"ALL", "ANY"}...)
func (p *parser) parse() (method, path string, err error) {
funcArgPos := 0
path = "/"
// take the first word and check for the method.
w := p.lexer.next()
for _, httpMethod := range router.AllMethods {
for _, httpMethod := range allMethods {
possibleMethodFuncName := methodTitle(httpMethod)
if strings.Index(w, possibleMethodFuncName) == 0 {
method = httpMethod
@ -437,9 +462,9 @@ func (p *parser) parse() (method, path string, err error) {
continue
}
// static path.
path += "/" + strings.ToLower(w)
}
return

View File

@ -1,34 +1,30 @@
package mvc2_test
import (
"fmt"
"testing"
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/httptest"
// "github.com/kataras/iris/mvc"
// "github.com/kataras/iris/mvc/activator/methodfunc"
. "github.com/kataras/iris/mvc2"
)
type testController struct {
type testControllerHandle struct {
C
Service TestService
reqField string
}
func (c *testController) Get() string {
func (c *testControllerHandle) Get() string {
return "index"
}
func (c *testController) BeginRequest(ctx iris.Context) {
func (c *testControllerHandle) BeginRequest(ctx iris.Context) {
c.C.BeginRequest(ctx)
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", "/histatic", "HiStatic")
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")
}
func (c *testController) HiStatic() string {
func (c *testControllerHandle) HiStatic() string {
return c.reqField
}
func (c *testController) HiService() string {
func (c *testControllerHandle) HiService() string {
return c.Service.Say("hi")
}
func (c *testController) HiParamBy(v string) string {
func (c *testControllerHandle) HiParamBy(v string) string {
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")
}
func TestControllerHandler(t *testing.T) {
func TestControllerHandle(t *testing.T) {
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")
now := time.Now()
m := New()
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.
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("index")
@ -84,7 +78,4 @@ func TestControllerHandler(t *testing.T) {
Body().Equal("value")
e.GET("/hiparamempyinput/value").Expect().Status(httptest.StatusOK).
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"
"reflect"
"github.com/kataras/golog"
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/router"
)
@ -15,8 +16,6 @@ var (
)
type Engine struct {
binders []*InputBinder
Input []reflect.Value
}
@ -24,80 +23,53 @@ func New() *Engine {
return new(Engine)
}
func (e *Engine) Child() *Engine {
child := New()
// copy the current parent's ctx func binders and services to this new child.
// 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))
func (e *Engine) Bind(values ...interface{}) *Engine {
for _, val := range values {
if v := reflect.ValueOf(val); goodVal(v) {
e.Input = append(e.Input, v)
}
}
return e
}
// BindTypeExists returns true if a binder responsible to
// bind and return a type of "typ" is already registered.
func (e *Engine) BindTypeExists(typ reflect.Type) bool {
// for _, b := range e.binders {
// if equalTypes(b.BindType, typ) {
// return true
// }
// }
for _, in := range e.Input {
if equalTypes(in.Type(), typ) {
return true
}
func (e *Engine) Child() *Engine {
child := New()
// copy the current parent's ctx func binders and services to this new child.
if l := len(e.Input); l > 0 {
input := make([]reflect.Value, l, l)
copy(input, e.Input)
child.Input = input
}
return false
return child
}
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
}
type ActivateListener interface {
OnActivate(*ControllerActivator)
}
func (e *Engine) Controller(router router.Party, controller BaseController, onActivate ...func(*ControllerActivator)) {
ca := newControllerActivator(router, controller, e.Input...)
func (e *Engine) Controller(router router.Party, controller BaseController) {
ca := newControllerActivator(e, router, controller)
if al, ok := controller.(ActivateListener); ok {
al.OnActivate(ca)
// give a priority to the "onActivate"
// callbacks, if any.
for _, cb := range onActivate {
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"
)
func TestMvcInAndHandler(t *testing.T) {
func TestMvcEngineInAndHandler(t *testing.T) {
m := New().Bind(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
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;
dataKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey()
if ctx.Values().Get(dataKey) == nil {
// if no c.Ctx.ViewData then it's empty do a
// pure set, it's faster.
// if no c.Ctx.ViewData set-ed before (the most common scenario) then do a
// simple set, it's faster.
ctx.Values().Set(dataKey, r.Data)
} else {
// 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.
// context.Map is actually a map[string]interface{} but we have to make that check;
// 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:
if m, ok := r.Data.(map[string]interface{}); ok {
setViewData(ctx, m)
} else if m, ok := r.Data.(context.Map); ok {
setViewData(ctx, m)
} else if structs.IsStruct(r.Data) {
} else if indirectVal(reflect.ValueOf(r.Data)).Kind() == reflect.Struct {
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 (
"fmt"
"reflect"
"runtime"
"github.com/kataras/golog"
"github.com/kataras/iris/context"
"github.com/kataras/iris/mvc/activator/methodfunc"
)
// checks if "handler" is context.Handler; func(context.Context).
@ -28,18 +28,13 @@ func validateHandler(handler interface{}) error {
return nil
}
var (
contextTyp = reflect.TypeOf(context.NewContext(nil))
emptyIn = []reflect.Value{}
)
// 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...)
// MustMakeHandler calls the `MakeHandler` and panics on any error.
func MustMakeHandler(handler interface{}, bindValues ...reflect.Value) context.Handler {
h, err := MakeHandler(handler, bindValues...)
if err != nil {
panic(err)
}
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,
// 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.
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 {
golog.Errorf("mvc handler: %v", err)
return nil, err
}
@ -59,71 +53,39 @@ func MakeHandler(handler interface{}, binders ...interface{}) (context.Handler,
return h, nil
}
inputBinders := make([]reflect.Value, len(binders), len(binders))
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
}
fn := reflect.ValueOf(handler)
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 contextIndex >= 0 {
// in[contextIndex] = ctxValue[0]
// }
// 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))
if n == 0 {
h := func(ctx context.Context) {
DispatchFuncResult(ctx, fn.Call(emptyIn))
}
s.FillFuncInput(ctxValue, &in)
methodfunc.DispatchFuncResult(ctx, fn.Call(in))
return h, nil
}
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 (
"fmt"
"reflect"
"testing"
"github.com/kataras/iris"
@ -69,19 +70,10 @@ var (
)
func TestMakeHandler(t *testing.T) {
// binders := []*InputBinder{
// // #1
// MustMakeFuncInputBinder(testBinderFuncUserStruct),
// // #2
// MustMakeServiceInputBinder(testBinderService),
// // #3
// MustMakeFuncInputBinder(testBinderFuncParam),
// }
var (
h1 = MustMakeHandler(testConsumeUserHandler, testBinderFuncUserStruct)
h2 = MustMakeHandler(testConsumeServiceHandler, testBinderService)
h3 = MustMakeHandler(testConsumeParamHandler, testBinderFuncParam)
h1 = MustMakeHandler(testConsumeUserHandler, reflect.ValueOf(testBinderFuncUserStruct))
h2 = MustMakeHandler(testConsumeServiceHandler, reflect.ValueOf(testBinderService))
h3 = MustMakeHandler(testConsumeParamHandler, reflect.ValueOf(testBinderFuncParam))
)
testAppWithMvcHandlers(t, h1, h2, h3)

View File

@ -1,7 +1,6 @@
package mvc2
import (
"fmt"
"reflect"
"github.com/kataras/iris/context"
@ -10,14 +9,7 @@ import (
"github.com/kataras/iris/core/router/macro/interpreter/ast"
)
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 methods inside a controller.
func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) (values []reflect.Value) {
if len(funcIn) == 0 || len(params) == 0 {
@ -30,72 +22,49 @@ func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type)
in := funcIn[funcInIdx]
paramType := p.Type
paramName := p.Name
// fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String())
if p.Type.Assignable(in.Kind()) {
// 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
// fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String())
if paramType.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))
}
funcInIdx++
}
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.
type PathParams = context.RequestParams

View File

@ -1,6 +1,13 @@
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 {
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()
}
func isFunc(typ reflect.Type) bool {
return typ.Kind() == reflect.Func
func isFunc(kindable interface {
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 {
if got == expected {
return true
@ -59,8 +55,16 @@ func equalTypes(got reflect.Type, expected reflect.Type) bool {
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 {
if !f.Anonymous {
return true // if not anonymous(embedded), ignore it.
@ -76,22 +80,28 @@ type field 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.
// it's filled with the bind object (as service which means as static value)
// and it's filled from the lookupFields' caller.
AnyValue reflect.Value
}
func lookupFields(typ reflect.Type, parentIndex int) (fields []field) {
for i, n := 0, typ.NumField(); i < n; i++ {
f := typ.Field(i)
func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) {
if elemTyp.Kind() != reflect.Struct {
return
}
if f.Type.Kind() == reflect.Struct && !structFieldIgnored(f) {
fields = append(fields, lookupFields(f.Type, i)...)
for i, n := 0, elemTyp.NumField(); i < n; i++ {
f := elemTyp.Field(i)
if indirectTyp(f.Type).Kind() == reflect.Struct &&
!structFieldIgnored(f) {
fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...)
continue
}
index := []int{i}
if parentIndex >= 0 {
index = append([]int{parentIndex}, index...)
if len(parentIndex) > 0 {
index = append(parentIndex, i)
}
field := field{
@ -105,3 +115,16 @@ func lookupFields(typ reflect.Type, parentIndex int) (fields []field) {
return
}
func lookupNonZeroFieldsValues(v reflect.Value) (bindValues []reflect.Value) {
elem := indirectVal(v)
fields := lookupFields(indirectTyp(v.Type()), nil)
for _, f := range fields {
if fieldVal := elem.FieldByIndex(f.Index); f.Type.Kind() == reflect.Ptr && !zerocheck.IsZero(fieldVal) {
bindValues = append(bindValues, fieldVal)
}
}
return
}

View File

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