mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
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:
parent
e02da3d442
commit
ed79f0c3cd
20
.gitattributes
vendored
20
.gitattributes
vendored
|
@ -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
0
_examples/README.md
Executable file → Normal 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
0
middleware/README.md
Executable file → Normal 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
244
mvc2/bind.go
Normal 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
|
||||||
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
package binder
|
|
||||||
|
|
||||||
type Input interface {
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package binder
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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{}{}))
|
|
||||||
|
|
||||||
}
|
|
|
@ -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 ""
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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
446
mvc2/controller_test.go
Normal 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")
|
||||||
|
}
|
100
mvc2/engine.go
100
mvc2/engine.go
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (
|
|
@ -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
275
mvc2/func_result_test.go
Normal 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)
|
||||||
|
}
|
112
mvc2/handler.go
112
mvc2/handler.go
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
// }
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
206
mvc2/service.go
206
mvc2/service.go
|
@ -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
|
|
||||||
}
|
|
|
@ -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`)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user