mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +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
|
||||
vendor/* linguist-vendored
|
||||
_examples/* linguist-documentation
|
||||
_benchmarks/* linguist-documentation
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
# if from windows:
|
||||
# git config --global core.autocrlf true
|
||||
# if from unix:
|
||||
# git config --global core.autocrlf input
|
||||
# https://help.github.com/articles/dealing-with-line-endings/#per-repository-settings
|
||||
*.go linguist-language=Go
|
||||
vendor/* linguist-vendored
|
||||
_examples/* linguist-documentation
|
||||
_benchmarks/* linguist-documentation
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
# if from windows:
|
||||
# git config --global core.autocrlf true
|
||||
# if from unix:
|
||||
# git config --global core.autocrlf input
|
||||
# https://help.github.com/articles/dealing-with-line-endings/#per-repository-settings
|
||||
* text=auto
|
0
_examples/README.md
Executable file → Normal file
0
_examples/README.md
Executable file → Normal file
|
@ -697,18 +697,23 @@ type Context interface {
|
|||
// ServeContent serves content, headers are autoset
|
||||
// receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string)
|
||||
//
|
||||
// You can define your own "Content-Type" header also, after this function call
|
||||
// Doesn't implements resuming (by range), use ctx.SendFile instead
|
||||
//
|
||||
// You can define your own "Content-Type" with `context#ContentType`, before this function call.
|
||||
//
|
||||
// This function doesn't support resuming (by range),
|
||||
// use ctx.SendFile or router's `StaticWeb` instead.
|
||||
ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error
|
||||
// ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename)
|
||||
// ServeFile serves a file (to send a file, a zip for example to the client you should use the `SendFile` instead)
|
||||
// receives two parameters
|
||||
// filename/path (string)
|
||||
// gzipCompression (bool)
|
||||
//
|
||||
// You can define your own "Content-Type" header also, after this function call
|
||||
// This function doesn't implement resuming (by range), use ctx.SendFile instead
|
||||
// You can define your own "Content-Type" with `context#ContentType`, before this function call.
|
||||
//
|
||||
// Use it when you want to serve css/js/... files to the client, for bigger files and 'force-download' use the SendFile.
|
||||
// This function doesn't support resuming (by range),
|
||||
// use ctx.SendFile or router's `StaticWeb` instead.
|
||||
//
|
||||
// Use it when you want to serve dynamic files to the client.
|
||||
ServeFile(filename string, gzipCompression bool) error
|
||||
// SendFile sends file for force-download to the client
|
||||
//
|
||||
|
@ -806,6 +811,11 @@ type Context interface {
|
|||
// to be executed at serve-time. The full app's fields
|
||||
// and methods are not available here for the developer's safety.
|
||||
Application() Application
|
||||
|
||||
// String returns the string representation of this request.
|
||||
// Each context has a unique string representation, so this can be used
|
||||
// as an "ID" as well, if needed.
|
||||
String() string
|
||||
}
|
||||
|
||||
// Next calls all the next handler from the handlers chain,
|
||||
|
@ -857,7 +867,11 @@ type Map map[string]interface{}
|
|||
// +------------------------------------------------------------+
|
||||
|
||||
type context struct {
|
||||
// the http.ResponseWriter wrapped by custom writer
|
||||
// the unique id, it's empty until `String` function is called,
|
||||
// it's here to cache the random, unique context's id, although `String`
|
||||
// returns more than this.
|
||||
id string
|
||||
// the http.ResponseWriter wrapped by custom writer.
|
||||
writer ResponseWriter
|
||||
// the original http.Request
|
||||
request *http.Request
|
||||
|
@ -865,10 +879,10 @@ type context struct {
|
|||
currentRouteName string
|
||||
|
||||
// the local key-value storage
|
||||
params RequestParams // url named parameters
|
||||
values memstore.Store // generic storage, middleware communication
|
||||
params RequestParams // url named parameters.
|
||||
values memstore.Store // generic storage, middleware communication.
|
||||
|
||||
// the underline application app
|
||||
// the underline application app.
|
||||
app Application
|
||||
// the route's handlers
|
||||
handlers Handlers
|
||||
|
@ -2721,6 +2735,19 @@ func (ctx *context) Exec(method string, path string) {
|
|||
}
|
||||
}
|
||||
|
||||
// String returns the string representation of this request.
|
||||
// Each context has a unique string representation, so this can be used
|
||||
// as an "ID" as well, if needed.
|
||||
func (ctx *context) String() (s string) {
|
||||
if ctx.id == "" {
|
||||
// set the id here.
|
||||
|
||||
s = "..."
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Application returns the iris app instance which belongs to this context.
|
||||
// Worth to notice that this function returns an interface
|
||||
// of the Application, which contains methods that are safe
|
||||
|
|
0
middleware/README.md
Executable file → Normal file
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
|
||||
// direct access to the current client's session via its `Session` field.
|
||||
type SessionController struct {
|
||||
Controller
|
||||
C
|
||||
|
||||
Manager *sessions.Sessions
|
||||
Session *sessions.Session
|
||||
|
@ -36,7 +36,7 @@ Please refer to the documentation to learn how you can provide the session manag
|
|||
// BeginRequest calls the Controller's BeginRequest
|
||||
// and tries to initialize the current user's Session.
|
||||
func (s *SessionController) BeginRequest(ctx context.Context) {
|
||||
s.Controller.BeginRequest(ctx)
|
||||
s.C.BeginRequest(ctx)
|
||||
if s.Manager == nil {
|
||||
ctx.Application().Logger().Errorf(`MVC SessionController: sessions manager is nil, report this as a bug
|
||||
because the SessionController should predict this on its activation state and use a default one automatically`)
|
||||
|
|
244
mvc2/bind.go
Normal file
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/macro"
|
||||
"github.com/kataras/iris/core/router/macro/interpreter/ast"
|
||||
"github.com/kataras/iris/mvc/activator/methodfunc"
|
||||
)
|
||||
|
||||
type BaseController interface {
|
||||
|
@ -72,38 +71,52 @@ type ControllerActivator struct {
|
|||
Router router.Party
|
||||
|
||||
initRef BaseController // the BaseController as it's passed from the end-dev.
|
||||
|
||||
Type reflect.Type // raw type of the BaseController (initRef).
|
||||
// FullName it's the last package path segment + "." + the Name.
|
||||
// i.e: if login-example/user/controller.go, the FullName is "user.Controller".
|
||||
FullName string
|
||||
|
||||
// key = the method's name.
|
||||
methods map[string]reflect.Method
|
||||
// the methods names that is already binded to a handler,
|
||||
// the BeginRequest, EndRequest and OnActivate are reserved by the internal implementation.
|
||||
reservedMethods []string
|
||||
|
||||
// services []field
|
||||
// bindServices func(elem reflect.Value)
|
||||
s services
|
||||
// input are always empty after the `activate`
|
||||
// are used to build the bindings, and we need this field
|
||||
// because we have 3 states (Engine.Input, OnActivate, Bind)
|
||||
// that we can add or override binding values.
|
||||
input []reflect.Value
|
||||
|
||||
// the bindings that comes from input (and Engine) and can be binded to the controller's(initRef) fields.
|
||||
bindings *targetStruct
|
||||
}
|
||||
|
||||
func newControllerActivator(engine *Engine, router router.Party, controller BaseController) *ControllerActivator {
|
||||
var emptyMethod = reflect.Method{}
|
||||
|
||||
func newControllerActivator(router router.Party, controller BaseController, bindValues ...reflect.Value) *ControllerActivator {
|
||||
c := &ControllerActivator{
|
||||
Engine: engine,
|
||||
Router: router,
|
||||
initRef: controller,
|
||||
reservedMethods: []string{
|
||||
"BeginRequest",
|
||||
"EndRequest",
|
||||
"OnActivate",
|
||||
},
|
||||
// the following will make sure that if
|
||||
// the controller's has set-ed pointer struct fields by the end-dev
|
||||
// we will include them to the bindings.
|
||||
// set bindings to the non-zero pointer fields' values that may be set-ed by
|
||||
// the end-developer when declaring the controller,
|
||||
// activate listeners needs them in order to know if something set-ed already or not,
|
||||
// look `BindTypeExists`.
|
||||
input: append(lookupNonZeroFieldsValues(reflect.ValueOf(controller)), bindValues...),
|
||||
}
|
||||
|
||||
c.analyze()
|
||||
return c
|
||||
}
|
||||
|
||||
var reservedMethodNames = []string{
|
||||
"BeginRequest",
|
||||
"EndRequest",
|
||||
"OnActivate",
|
||||
}
|
||||
|
||||
func isReservedMethod(name string) bool {
|
||||
for _, s := range reservedMethodNames {
|
||||
func (c *ControllerActivator) isReservedMethod(name string) bool {
|
||||
for _, s := range c.reservedMethods {
|
||||
if s == name {
|
||||
return true
|
||||
}
|
||||
|
@ -113,55 +126,86 @@ func isReservedMethod(name string) bool {
|
|||
}
|
||||
|
||||
func (c *ControllerActivator) analyze() {
|
||||
|
||||
// set full name.
|
||||
{
|
||||
// first instance value, needed to validate
|
||||
// the actual type of the controller field
|
||||
// and to collect and save the instance's persistence fields'
|
||||
// values later on.
|
||||
val := reflect.Indirect(reflect.ValueOf(c.initRef))
|
||||
|
||||
ctrlName := val.Type().Name()
|
||||
pkgPath := val.Type().PkgPath()
|
||||
fullName := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + ctrlName
|
||||
c.FullName = fullName
|
||||
}
|
||||
// first instance value, needed to validate
|
||||
// the actual type of the controller field
|
||||
// and to collect and save the instance's persistence fields'
|
||||
// values later on.
|
||||
typ := reflect.TypeOf(c.initRef) // type with pointer
|
||||
elemTyp := indirectTyp(typ)
|
||||
|
||||
// set all available, exported methods.
|
||||
{
|
||||
typ := reflect.TypeOf(c.initRef) // typ, with pointer
|
||||
n := typ.NumMethod()
|
||||
c.methods = make(map[string]reflect.Method, n)
|
||||
for i := 0; i < n; i++ {
|
||||
m := typ.Method(i)
|
||||
key := m.Name
|
||||
ctrlName := elemTyp.Name()
|
||||
pkgPath := elemTyp.PkgPath()
|
||||
fullName := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + ctrlName
|
||||
c.FullName = fullName
|
||||
c.Type = typ
|
||||
|
||||
if !isReservedMethod(key) {
|
||||
c.methods[key] = m
|
||||
}
|
||||
// register all available, exported methods to handlers if possible.
|
||||
n := typ.NumMethod()
|
||||
for i := 0; i < n; i++ {
|
||||
m := typ.Method(i)
|
||||
funcName := m.Name
|
||||
|
||||
if c.isReservedMethod(funcName) {
|
||||
continue
|
||||
}
|
||||
|
||||
httpMethod, httpPath, err := parse(m)
|
||||
if err != nil && err != errSkip {
|
||||
err = fmt.Errorf("MVC: fail to parse the path and method for '%s.%s': %v", c.FullName, m.Name, err)
|
||||
c.Router.GetReporter().AddErr(err)
|
||||
continue
|
||||
}
|
||||
|
||||
c.Handle(httpMethod, httpPath, funcName)
|
||||
}
|
||||
|
||||
// set field index with matching service binders, if any.
|
||||
{
|
||||
// typ := indirectTyp(reflect.TypeOf(c.initRef)) // element's typ.
|
||||
|
||||
c.s = getServicesFor(reflect.ValueOf(c.initRef), c.Engine.Input)
|
||||
// c.bindServices = getServicesBinderForStruct(c.Engine.binders, typ)
|
||||
}
|
||||
|
||||
c.analyzeAndRegisterMethods()
|
||||
}
|
||||
|
||||
// SetBindings will override any bindings with the new "values".
|
||||
func (c *ControllerActivator) SetBindings(values ...reflect.Value) {
|
||||
// set field index with matching binders, if any.
|
||||
c.bindings = newTargetStruct(reflect.ValueOf(c.initRef), values...)
|
||||
c.input = c.input[0:0]
|
||||
}
|
||||
|
||||
// Bind binds values to this controller, if you want to share
|
||||
// binding values between controllers use the Engine's `Bind` function instead.
|
||||
func (c *ControllerActivator) Bind(values ...interface{}) {
|
||||
for _, val := range values {
|
||||
if v := reflect.ValueOf(val); goodVal(v) {
|
||||
c.input = append(c.input, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BindTypeExists returns true if a binder responsible to
|
||||
// bind and return a type of "typ" is already registered to this controller.
|
||||
func (c *ControllerActivator) BindTypeExists(typ reflect.Type) bool {
|
||||
for _, in := range c.input {
|
||||
if equalTypes(in.Type(), typ) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *ControllerActivator) activate() {
|
||||
c.SetBindings(c.input...)
|
||||
}
|
||||
|
||||
var emptyIn = []reflect.Value{}
|
||||
|
||||
func (c *ControllerActivator) Handle(method, path, funcName string, middleware ...context.Handler) error {
|
||||
if method == "" || path == "" || funcName == "" || isReservedMethod(funcName) {
|
||||
if method == "" || path == "" || funcName == "" ||
|
||||
c.isReservedMethod(funcName) {
|
||||
// isReservedMethod -> if it's already registered
|
||||
// by a previous Handle or analyze methods internally.
|
||||
return errSkip
|
||||
}
|
||||
|
||||
m, ok := c.methods[funcName]
|
||||
m, ok := c.Type.MethodByName(funcName)
|
||||
if !ok {
|
||||
err := fmt.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller",
|
||||
funcName, c.FullName)
|
||||
|
@ -176,105 +220,84 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
|
|||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("===============%s.%s==============\n", c.FullName, funcName)
|
||||
funcIn := getInputArgsFromFunc(m.Type)[1:] // except the receiver, which is the controller pointer itself.
|
||||
// add this as a reserved method name in order to
|
||||
// be sure that the same func will not be registered again, even if a custom .Handle later on.
|
||||
c.reservedMethods = append(c.reservedMethods, funcName)
|
||||
|
||||
// get any binders for this func, if any, and
|
||||
// take param binders, we can bind them because we know the path here.
|
||||
// binders := joinBindersMap(
|
||||
// getBindersForInput(c.Engine.binders, funcIn...),
|
||||
// getPathParamsBindersForInput(tmpl.Params, funcIn...))
|
||||
// fmt.Printf("===============%s.%s==============\n", c.FullName, funcName)
|
||||
|
||||
s := getServicesFor(m.Func, getPathParamsForInput(tmpl.Params, funcIn...))
|
||||
// s.AddSource(indirectVal(reflect.ValueOf(c.initRef)), c.Engine.Input...)
|
||||
funcIn := getInputArgsFromFunc(m.Type) // except the receiver, which is the controller pointer itself.
|
||||
|
||||
typ := reflect.TypeOf(c.initRef)
|
||||
elem := indirectTyp(typ) // the value, not the pointer.
|
||||
hasInputBinders := len(s) > 0
|
||||
hasStructBinders := len(c.s) > 0
|
||||
n := len(funcIn) + 1
|
||||
pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...)
|
||||
funcBindings := newTargetFunc(m.Func, pathParams...)
|
||||
|
||||
// be, _ := typ.MethodByName("BeginRequest")
|
||||
// en, _ := typ.MethodByName("EndRequest")
|
||||
// beginIndex, endIndex := be.Index, en.Index
|
||||
elemTyp := indirectTyp(c.Type) // the element value, not the pointer.
|
||||
|
||||
n := len(funcIn)
|
||||
|
||||
handler := func(ctx context.Context) {
|
||||
|
||||
// create a new controller instance of that type(>ptr).
|
||||
ctrl := reflect.New(elem)
|
||||
//ctrlAndCtxValues := []reflect.Value{ctrl, ctxValue[0]}
|
||||
// ctrl.MethodByName("BeginRequest").Call(ctxValue)
|
||||
//begin.Func.Call(ctrlAndCtxValues)
|
||||
ctrl := reflect.New(elemTyp)
|
||||
b := ctrl.Interface().(BaseController) // the Interface(). is faster than MethodByName or pre-selected methods.
|
||||
// init the request.
|
||||
b.BeginRequest(ctx)
|
||||
//ctrl.Method(beginIndex).Call(ctxValue)
|
||||
|
||||
// if begin request stopped the execution.
|
||||
if ctx.IsStopped() {
|
||||
return
|
||||
}
|
||||
|
||||
if hasStructBinders {
|
||||
elem := ctrl.Elem()
|
||||
c.s.FillStructStaticValues(elem)
|
||||
}
|
||||
|
||||
if !hasInputBinders {
|
||||
methodfunc.DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
|
||||
if !c.bindings.Valid && !funcBindings.Valid {
|
||||
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
|
||||
} else {
|
||||
in := make([]reflect.Value, n, n)
|
||||
// in[0] = ctrl.Elem()
|
||||
in[0] = ctrl
|
||||
s.FillFuncInput([]reflect.Value{reflect.ValueOf(ctx)}, &in)
|
||||
methodfunc.DispatchFuncResult(ctx, m.Func.Call(in))
|
||||
// in := make([]reflect.Value, n, n)
|
||||
// ctxValues := []reflect.Value{reflect.ValueOf(ctx)}
|
||||
// for k, v := range binders {
|
||||
// in[k] = v.BindFunc(ctxValues)
|
||||
ctxValue := reflect.ValueOf(ctx)
|
||||
|
||||
if c.bindings.Valid {
|
||||
elem := ctrl.Elem()
|
||||
c.bindings.Fill(elem, ctxValue)
|
||||
if ctx.IsStopped() {
|
||||
return
|
||||
}
|
||||
|
||||
// we do this in order to reduce in := make...
|
||||
// if not func input binders, we execute the handler with empty input args.
|
||||
if !funcBindings.Valid {
|
||||
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
|
||||
}
|
||||
}
|
||||
// otherwise, it has one or more valid input binders,
|
||||
// make the input and call the func using those.
|
||||
if funcBindings.Valid {
|
||||
in := make([]reflect.Value, n, n)
|
||||
in[0] = ctrl
|
||||
funcBindings.Fill(&in, ctxValue)
|
||||
if ctx.IsStopped() {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchFuncResult(ctx, m.Func.Call(in))
|
||||
}
|
||||
|
||||
// if ctx.IsStopped() {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// methodfunc.DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(in))
|
||||
}
|
||||
|
||||
// end the request, don't check for stopped because this does the actual writing
|
||||
// if no response written already.
|
||||
b.EndRequest(ctx)
|
||||
// ctrl.MethodByName("EndRequest").Call(ctxValue)
|
||||
// end.Func.Call(ctrlAndCtxValues)
|
||||
//ctrl.Method(endIndex).Call(ctxValue)
|
||||
}
|
||||
|
||||
// register the handler now.
|
||||
r := c.Router.Handle(method, path, append(middleware, handler)...)
|
||||
// change the main handler's name in order to respect the controller's and give
|
||||
// a proper debug message.
|
||||
r.MainHandlerName = fmt.Sprintf("%s.%s", c.FullName, funcName)
|
||||
// add this as a reserved method name in order to
|
||||
// be sure that the same func will not be registered again, even if a custom .Handle later on.
|
||||
reservedMethodNames = append(reservedMethodNames, funcName)
|
||||
c.Router.Handle(method, path, append(middleware, handler)...).
|
||||
// change the main handler's name in order to respect the controller's and give
|
||||
// a proper debug message.
|
||||
MainHandlerName = fmt.Sprintf("%s.%s", c.FullName, funcName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ControllerActivator) analyzeAndRegisterMethods() {
|
||||
for _, m := range c.methods {
|
||||
funcName := m.Name
|
||||
httpMethod, httpPath, err := parse(m)
|
||||
if err != nil && err != errSkip {
|
||||
err = fmt.Errorf("MVC: fail to parse the path and method for '%s.%s': %v", c.FullName, m.Name, err)
|
||||
c.Router.GetReporter().AddErr(err)
|
||||
continue
|
||||
}
|
||||
|
||||
c.Handle(httpMethod, httpPath, funcName)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
tokenBy = "By"
|
||||
tokenWildcard = "Wildcard" // i.e ByWildcard
|
||||
tokenWildcard = "Wildcard" // "ByWildcard".
|
||||
)
|
||||
|
||||
// word lexer, not characters.
|
||||
|
@ -393,13 +416,15 @@ func methodTitle(httpMethod string) string {
|
|||
|
||||
var errSkip = errors.New("skip")
|
||||
|
||||
var allMethods = append(router.AllMethods[0:], []string{"ALL", "ANY"}...)
|
||||
|
||||
func (p *parser) parse() (method, path string, err error) {
|
||||
funcArgPos := 0
|
||||
path = "/"
|
||||
// take the first word and check for the method.
|
||||
w := p.lexer.next()
|
||||
|
||||
for _, httpMethod := range router.AllMethods {
|
||||
for _, httpMethod := range allMethods {
|
||||
possibleMethodFuncName := methodTitle(httpMethod)
|
||||
if strings.Index(w, possibleMethodFuncName) == 0 {
|
||||
method = httpMethod
|
||||
|
@ -437,9 +462,9 @@ func (p *parser) parse() (method, path string, err error) {
|
|||
|
||||
continue
|
||||
}
|
||||
|
||||
// static path.
|
||||
path += "/" + strings.ToLower(w)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
@ -1,34 +1,30 @@
|
|||
package mvc2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/httptest"
|
||||
// "github.com/kataras/iris/mvc"
|
||||
// "github.com/kataras/iris/mvc/activator/methodfunc"
|
||||
. "github.com/kataras/iris/mvc2"
|
||||
)
|
||||
|
||||
type testController struct {
|
||||
type testControllerHandle struct {
|
||||
C
|
||||
Service TestService
|
||||
|
||||
reqField string
|
||||
}
|
||||
|
||||
func (c *testController) Get() string {
|
||||
func (c *testControllerHandle) Get() string {
|
||||
return "index"
|
||||
}
|
||||
|
||||
func (c *testController) BeginRequest(ctx iris.Context) {
|
||||
func (c *testControllerHandle) BeginRequest(ctx iris.Context) {
|
||||
c.C.BeginRequest(ctx)
|
||||
c.reqField = ctx.URLParam("reqfield")
|
||||
}
|
||||
|
||||
func (c *testController) OnActivate(t *ControllerActivator) { // OnActivate(t *mvc.TController) {
|
||||
func (c *testControllerHandle) OnActivate(t *ControllerActivator) { // OnActivate(t *mvc.TController) {
|
||||
// t.Handle("GET", "/", "Get")
|
||||
t.Handle("GET", "/histatic", "HiStatic")
|
||||
t.Handle("GET", "/hiservice", "HiService")
|
||||
|
@ -36,31 +32,29 @@ func (c *testController) OnActivate(t *ControllerActivator) { // OnActivate(t *m
|
|||
t.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy")
|
||||
}
|
||||
|
||||
func (c *testController) HiStatic() string {
|
||||
func (c *testControllerHandle) HiStatic() string {
|
||||
return c.reqField
|
||||
}
|
||||
|
||||
func (c *testController) HiService() string {
|
||||
func (c *testControllerHandle) HiService() string {
|
||||
return c.Service.Say("hi")
|
||||
}
|
||||
|
||||
func (c *testController) HiParamBy(v string) string {
|
||||
func (c *testControllerHandle) HiParamBy(v string) string {
|
||||
return v
|
||||
}
|
||||
|
||||
func (c *testController) HiParamEmptyInputBy() string {
|
||||
func (c *testControllerHandle) HiParamEmptyInputBy() string {
|
||||
return "empty in but served with ctx.Params.Get('ps')=" + c.Ctx.Params().Get("ps")
|
||||
}
|
||||
|
||||
func TestControllerHandler(t *testing.T) {
|
||||
func TestControllerHandle(t *testing.T) {
|
||||
app := iris.New()
|
||||
// app.Controller("/", new(testController), &TestServiceImpl{prefix: "service:"})
|
||||
m := New()
|
||||
m.Bind(&TestServiceImpl{prefix: "service:"}).Controller(app, new(testController))
|
||||
e := httptest.New(t, app, httptest.LogLevel("debug"))
|
||||
|
||||
fmt.Printf("\n\n\n")
|
||||
now := time.Now()
|
||||
m := New()
|
||||
m.Bind(&TestServiceImpl{prefix: "service:"}).Controller(app, new(testControllerHandle))
|
||||
e := httptest.New(t, app)
|
||||
|
||||
// test the index, is not part of the current package's implementation but do it.
|
||||
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("index")
|
||||
|
||||
|
@ -84,7 +78,4 @@ func TestControllerHandler(t *testing.T) {
|
|||
Body().Equal("value")
|
||||
e.GET("/hiparamempyinput/value").Expect().Status(httptest.StatusOK).
|
||||
Body().Equal("empty in but served with ctx.Params.Get('ps')=value")
|
||||
|
||||
endTime := time.Now().Sub(now)
|
||||
fmt.Printf("end at %dns\n", endTime.Nanoseconds())
|
||||
}
|
446
mvc2/controller_test.go
Normal file
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"
|
||||
"reflect"
|
||||
|
||||
"github.com/kataras/golog"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/router"
|
||||
)
|
||||
|
@ -15,8 +16,6 @@ var (
|
|||
)
|
||||
|
||||
type Engine struct {
|
||||
binders []*InputBinder
|
||||
|
||||
Input []reflect.Value
|
||||
}
|
||||
|
||||
|
@ -24,80 +23,53 @@ func New() *Engine {
|
|||
return new(Engine)
|
||||
}
|
||||
|
||||
func (e *Engine) Child() *Engine {
|
||||
child := New()
|
||||
|
||||
// copy the current parent's ctx func binders and services to this new child.
|
||||
// if l := len(e.binders); l > 0 {
|
||||
// binders := make([]*InputBinder, l, l)
|
||||
// copy(binders, e.binders)
|
||||
// child.binders = binders
|
||||
// }
|
||||
if l := len(e.Input); l > 0 {
|
||||
input := make([]reflect.Value, l, l)
|
||||
copy(input, e.Input)
|
||||
child.Input = input
|
||||
}
|
||||
return child
|
||||
}
|
||||
|
||||
func (e *Engine) Bind(binders ...interface{}) *Engine {
|
||||
for _, binder := range binders {
|
||||
// typ := resolveBinderType(binder)
|
||||
|
||||
// var (
|
||||
// b *InputBinder
|
||||
// err error
|
||||
// )
|
||||
|
||||
// if typ == functionType {
|
||||
// b, err = MakeFuncInputBinder(binder)
|
||||
// } else if typ == serviceType {
|
||||
// b, err = MakeServiceInputBinder(binder)
|
||||
// } else {
|
||||
// err = errBad
|
||||
// }
|
||||
|
||||
// if err != nil {
|
||||
// continue
|
||||
// }
|
||||
|
||||
// e.binders = append(e.binders, b)
|
||||
|
||||
e.Input = append(e.Input, reflect.ValueOf(binder))
|
||||
func (e *Engine) Bind(values ...interface{}) *Engine {
|
||||
for _, val := range values {
|
||||
if v := reflect.ValueOf(val); goodVal(v) {
|
||||
e.Input = append(e.Input, v)
|
||||
}
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// BindTypeExists returns true if a binder responsible to
|
||||
// bind and return a type of "typ" is already registered.
|
||||
func (e *Engine) BindTypeExists(typ reflect.Type) bool {
|
||||
// for _, b := range e.binders {
|
||||
// if equalTypes(b.BindType, typ) {
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
for _, in := range e.Input {
|
||||
if equalTypes(in.Type(), typ) {
|
||||
return true
|
||||
}
|
||||
func (e *Engine) Child() *Engine {
|
||||
child := New()
|
||||
|
||||
// copy the current parent's ctx func binders and services to this new child.
|
||||
if l := len(e.Input); l > 0 {
|
||||
input := make([]reflect.Value, l, l)
|
||||
copy(input, e.Input)
|
||||
child.Input = input
|
||||
}
|
||||
return false
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
func (e *Engine) Handler(handler interface{}) context.Handler {
|
||||
h, _ := MakeHandler(handler, e.binders) // it logs errors already, so on any error the "h" will be nil.
|
||||
h, err := MakeHandler(handler, e.Input...)
|
||||
if err != nil {
|
||||
golog.Errorf("mvc handler: %v", err)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
type ActivateListener interface {
|
||||
OnActivate(*ControllerActivator)
|
||||
}
|
||||
func (e *Engine) Controller(router router.Party, controller BaseController, onActivate ...func(*ControllerActivator)) {
|
||||
ca := newControllerActivator(router, controller, e.Input...)
|
||||
|
||||
func (e *Engine) Controller(router router.Party, controller BaseController) {
|
||||
ca := newControllerActivator(e, router, controller)
|
||||
if al, ok := controller.(ActivateListener); ok {
|
||||
al.OnActivate(ca)
|
||||
// give a priority to the "onActivate"
|
||||
// callbacks, if any.
|
||||
for _, cb := range onActivate {
|
||||
cb(ca)
|
||||
}
|
||||
|
||||
// check if controller has an "OnActivate" function
|
||||
// which accepts the controller activator and call it.
|
||||
if activateListener, ok := controller.(interface {
|
||||
OnActivate(*ControllerActivator)
|
||||
}); ok {
|
||||
activateListener.OnActivate(ca)
|
||||
}
|
||||
|
||||
ca.activate()
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
. "github.com/kataras/iris/mvc2"
|
||||
)
|
||||
|
||||
func TestMvcInAndHandler(t *testing.T) {
|
||||
func TestMvcEngineInAndHandler(t *testing.T) {
|
||||
m := New().Bind(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
|
||||
|
||||
var (
|
|
@ -394,18 +394,18 @@ func (r View) Dispatch(ctx context.Context) { // r as Response view.
|
|||
// In order to respect any c.Ctx.ViewData that may called manually before;
|
||||
dataKey := ctx.Application().ConfigurationReadOnly().GetViewDataContextKey()
|
||||
if ctx.Values().Get(dataKey) == nil {
|
||||
// if no c.Ctx.ViewData then it's empty do a
|
||||
// pure set, it's faster.
|
||||
// if no c.Ctx.ViewData set-ed before (the most common scenario) then do a
|
||||
// simple set, it's faster.
|
||||
ctx.Values().Set(dataKey, r.Data)
|
||||
} else {
|
||||
// else check if r.Data is map or struct, if struct convert it to map,
|
||||
// do a range loop and set the data one by one.
|
||||
// context.Map is actually a map[string]interface{} but we have to make that check;
|
||||
// do a range loop and modify the data one by one.
|
||||
// context.Map is actually a map[string]interface{} but we have to make that check:
|
||||
if m, ok := r.Data.(map[string]interface{}); ok {
|
||||
setViewData(ctx, m)
|
||||
} else if m, ok := r.Data.(context.Map); ok {
|
||||
setViewData(ctx, m)
|
||||
} else if structs.IsStruct(r.Data) {
|
||||
} else if indirectVal(reflect.ValueOf(r.Data)).Kind() == reflect.Struct {
|
||||
setViewData(ctx, structs.Map(r))
|
||||
}
|
||||
}
|
275
mvc2/func_result_test.go
Normal file
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 (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
|
||||
"github.com/kataras/golog"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/mvc/activator/methodfunc"
|
||||
)
|
||||
|
||||
// checks if "handler" is context.Handler; func(context.Context).
|
||||
|
@ -28,18 +28,13 @@ func validateHandler(handler interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
contextTyp = reflect.TypeOf(context.NewContext(nil))
|
||||
emptyIn = []reflect.Value{}
|
||||
)
|
||||
|
||||
// MustMakeHandler calls the `MakeHandler` and returns its first resultthe low-level handler), see its docs.
|
||||
// It panics on error.
|
||||
func MustMakeHandler(handler interface{}, binders ...interface{}) context.Handler {
|
||||
h, err := MakeHandler(handler, binders...)
|
||||
// MustMakeHandler calls the `MakeHandler` and panics on any error.
|
||||
func MustMakeHandler(handler interface{}, bindValues ...reflect.Value) context.Handler {
|
||||
h, err := MakeHandler(handler, bindValues...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
|
@ -48,9 +43,8 @@ func MustMakeHandler(handler interface{}, binders ...interface{}) context.Handle
|
|||
// custom structs, Result(View | Response) and anything that you already know that mvc implementation supports,
|
||||
// and returns a low-level `context/iris.Handler` which can be used anywhere in the Iris Application,
|
||||
// as middleware or as simple route handler or party handler or subdomain handler-router.
|
||||
func MakeHandler(handler interface{}, binders ...interface{}) (context.Handler, error) {
|
||||
func MakeHandler(handler interface{}, bindValues ...reflect.Value) (context.Handler, error) {
|
||||
if err := validateHandler(handler); err != nil {
|
||||
golog.Errorf("mvc handler: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -59,71 +53,39 @@ func MakeHandler(handler interface{}, binders ...interface{}) (context.Handler,
|
|||
return h, nil
|
||||
}
|
||||
|
||||
inputBinders := make([]reflect.Value, len(binders), len(binders))
|
||||
|
||||
for i := range binders {
|
||||
inputBinders[i] = reflect.ValueOf(binders[i])
|
||||
}
|
||||
|
||||
return makeHandler(reflect.ValueOf(handler), inputBinders), nil
|
||||
|
||||
// typ := indirectTyp(reflect.TypeOf(handler))
|
||||
// n := typ.NumIn()
|
||||
// typIn := make([]reflect.Type, n, n)
|
||||
// for i := 0; i < n; i++ {
|
||||
// typIn[i] = typ.In(i)
|
||||
// }
|
||||
|
||||
// m := getBindersForInput(binders, typIn...)
|
||||
// if len(m) != n {
|
||||
// err := fmt.Errorf("input arguments length(%d) of types(%s) and valid binders length(%d) are not equal", n, typIn, len(m))
|
||||
// golog.Errorf("mvc handler: %v", err)
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// return makeHandler(reflect.ValueOf(handler), m), nil
|
||||
}
|
||||
|
||||
func makeHandler(fn reflect.Value, inputBinders []reflect.Value) context.Handler {
|
||||
inLen := fn.Type().NumIn()
|
||||
|
||||
if inLen == 0 {
|
||||
return func(ctx context.Context) {
|
||||
methodfunc.DispatchFuncResult(ctx, fn.Call(emptyIn))
|
||||
}
|
||||
}
|
||||
|
||||
s := getServicesFor(fn, inputBinders)
|
||||
if len(s) == 0 {
|
||||
golog.Errorf("mvc handler: input arguments length(%d) and valid binders length(%d) are not equal", inLen, len(s))
|
||||
return nil
|
||||
}
|
||||
|
||||
fn := reflect.ValueOf(handler)
|
||||
n := fn.Type().NumIn()
|
||||
// contextIndex := -1
|
||||
// if n > 0 {
|
||||
// if isContext(fn.Type().In(0)) {
|
||||
// contextIndex = 0
|
||||
// }
|
||||
// }
|
||||
return func(ctx context.Context) {
|
||||
ctxValue := []reflect.Value{reflect.ValueOf(ctx)}
|
||||
|
||||
in := make([]reflect.Value, n, n)
|
||||
// if contextIndex >= 0 {
|
||||
// in[contextIndex] = ctxValue[0]
|
||||
// }
|
||||
// ctxValues := []reflect.Value{reflect.ValueOf(ctx)}
|
||||
// for k, v := range m {
|
||||
// in[k] = v.BindFunc(ctxValues)
|
||||
// if ctx.IsStopped() {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// methodfunc.DispatchFuncResult(ctx, fn.Call(in))
|
||||
if n == 0 {
|
||||
h := func(ctx context.Context) {
|
||||
DispatchFuncResult(ctx, fn.Call(emptyIn))
|
||||
}
|
||||
|
||||
s.FillFuncInput(ctxValue, &in)
|
||||
|
||||
methodfunc.DispatchFuncResult(ctx, fn.Call(in))
|
||||
return h, nil
|
||||
}
|
||||
|
||||
s := newTargetFunc(fn, bindValues...)
|
||||
if !s.Valid {
|
||||
pc := fn.Pointer()
|
||||
fpc := runtime.FuncForPC(pc)
|
||||
callerFileName, callerLineNumber := fpc.FileLine(pc)
|
||||
callerName := fpc.Name()
|
||||
|
||||
err := fmt.Errorf("input arguments length(%d) and valid binders length(%d) are not equal for typeof '%s' which is defined at %s:%d by %s",
|
||||
n, len(s.Inputs), fn.Type().String(), callerFileName, callerLineNumber, callerName)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := func(ctx context.Context) {
|
||||
in := make([]reflect.Value, n, n)
|
||||
|
||||
s.Fill(&in, reflect.ValueOf(ctx))
|
||||
if ctx.IsStopped() {
|
||||
return
|
||||
}
|
||||
DispatchFuncResult(ctx, fn.Call(in))
|
||||
}
|
||||
|
||||
return h, nil
|
||||
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
|
@ -69,19 +70,10 @@ var (
|
|||
)
|
||||
|
||||
func TestMakeHandler(t *testing.T) {
|
||||
// binders := []*InputBinder{
|
||||
// // #1
|
||||
// MustMakeFuncInputBinder(testBinderFuncUserStruct),
|
||||
// // #2
|
||||
// MustMakeServiceInputBinder(testBinderService),
|
||||
// // #3
|
||||
// MustMakeFuncInputBinder(testBinderFuncParam),
|
||||
// }
|
||||
|
||||
var (
|
||||
h1 = MustMakeHandler(testConsumeUserHandler, testBinderFuncUserStruct)
|
||||
h2 = MustMakeHandler(testConsumeServiceHandler, testBinderService)
|
||||
h3 = MustMakeHandler(testConsumeParamHandler, testBinderFuncParam)
|
||||
h1 = MustMakeHandler(testConsumeUserHandler, reflect.ValueOf(testBinderFuncUserStruct))
|
||||
h2 = MustMakeHandler(testConsumeServiceHandler, reflect.ValueOf(testBinderService))
|
||||
h3 = MustMakeHandler(testConsumeParamHandler, reflect.ValueOf(testBinderFuncParam))
|
||||
)
|
||||
|
||||
testAppWithMvcHandlers(t, h1, h2, h3)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package mvc2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
|
@ -10,14 +9,7 @@ import (
|
|||
"github.com/kataras/iris/core/router/macro/interpreter/ast"
|
||||
)
|
||||
|
||||
func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
|
||||
n := funcTyp.NumIn()
|
||||
funcIn := make([]reflect.Type, n, n)
|
||||
for i := 0; i < n; i++ {
|
||||
funcIn[i] = funcTyp.In(i)
|
||||
}
|
||||
return funcIn
|
||||
}
|
||||
// for methods inside a controller.
|
||||
|
||||
func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) (values []reflect.Value) {
|
||||
if len(funcIn) == 0 || len(params) == 0 {
|
||||
|
@ -30,72 +22,49 @@ func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type)
|
|||
in := funcIn[funcInIdx]
|
||||
paramType := p.Type
|
||||
paramName := p.Name
|
||||
|
||||
// fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String())
|
||||
if p.Type.Assignable(in.Kind()) {
|
||||
|
||||
// b = append(b, &InputBinder{
|
||||
// BindType: in, // or p.Type.Kind, should be the same.
|
||||
// BindFunc: func(ctx []reflect.Value) reflect.Value {
|
||||
// // I don't like this ctx[0].Interface(0)
|
||||
// // it will be slow, and silly because we have ctx already
|
||||
// // before the bindings at serve-time, so we will create
|
||||
// // a func for each one of the param types, they are just 4 so
|
||||
// // it worths some dublications.
|
||||
// return getParamValueFromType(ctx[0].Interface(), paramType, paramName)
|
||||
// },
|
||||
// })
|
||||
|
||||
var fn interface{}
|
||||
|
||||
if paramType == ast.ParamTypeInt {
|
||||
fn = func(ctx context.Context) int {
|
||||
v, _ := ctx.Params().GetInt(paramName)
|
||||
return v
|
||||
}
|
||||
} else if paramType == ast.ParamTypeLong {
|
||||
fn = func(ctx context.Context) int64 {
|
||||
v, _ := ctx.Params().GetInt64(paramName)
|
||||
return v
|
||||
}
|
||||
|
||||
} else if paramType == ast.ParamTypeBoolean {
|
||||
fn = func(ctx context.Context) bool {
|
||||
v, _ := ctx.Params().GetBool(paramName)
|
||||
return v
|
||||
}
|
||||
|
||||
} else {
|
||||
// string, path...
|
||||
fn = func(ctx context.Context) string {
|
||||
return ctx.Params().Get(paramName)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("binder_in_path_param.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String())
|
||||
values = append(values, reflect.ValueOf(fn))
|
||||
|
||||
// inputBinder, err := MakeFuncInputBinder(fn)
|
||||
// if err != nil {
|
||||
// fmt.Printf("err on make func binder: %v\n", err.Error())
|
||||
// continue
|
||||
// }
|
||||
|
||||
// if m == nil {
|
||||
// m = make(bindersMap, 0)
|
||||
// }
|
||||
|
||||
// // fmt.Printf("set param input binder for func arg index: %d\n", funcInIdx)
|
||||
// m[funcInIdx] = inputBinder
|
||||
// fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String())
|
||||
if paramType.Assignable(in.Kind()) {
|
||||
// fmt.Printf("path_param_binder.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String())
|
||||
values = append(values, makeFuncParamGetter(paramType, paramName))
|
||||
}
|
||||
|
||||
funcInIdx++
|
||||
}
|
||||
|
||||
return
|
||||
// return m
|
||||
}
|
||||
|
||||
func makeFuncParamGetter(paramType ast.ParamType, paramName string) reflect.Value {
|
||||
var fn interface{}
|
||||
|
||||
switch paramType {
|
||||
case ast.ParamTypeInt:
|
||||
fn = func(ctx context.Context) int {
|
||||
v, _ := ctx.Params().GetInt(paramName)
|
||||
return v
|
||||
}
|
||||
case ast.ParamTypeLong:
|
||||
fn = func(ctx context.Context) int64 {
|
||||
v, _ := ctx.Params().GetInt64(paramName)
|
||||
return v
|
||||
}
|
||||
case ast.ParamTypeBoolean:
|
||||
fn = func(ctx context.Context) bool {
|
||||
v, _ := ctx.Params().GetBool(paramName)
|
||||
return v
|
||||
}
|
||||
default:
|
||||
// string, path...
|
||||
fn = func(ctx context.Context) string {
|
||||
return ctx.Params().Get(paramName)
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.ValueOf(fn)
|
||||
}
|
||||
|
||||
// for raw handlers, independent of a controller.
|
||||
|
||||
// PathParams is the context's named path parameters, see `PathParamsBinder` too.
|
||||
type PathParams = context.RequestParams
|
||||
|
|
@ -1,6 +1,13 @@
|
|||
package mvc2
|
||||
|
||||
import "reflect"
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/pkg/zerocheck"
|
||||
)
|
||||
|
||||
var contextTyp = reflect.TypeOf(context.NewContext(nil))
|
||||
|
||||
func isContext(inTyp reflect.Type) bool {
|
||||
return inTyp.String() == "context.Context" // I couldn't find another way; context/context.go is not exported.
|
||||
|
@ -29,23 +36,12 @@ func goodVal(v reflect.Value) bool {
|
|||
return v.IsValid()
|
||||
}
|
||||
|
||||
func isFunc(typ reflect.Type) bool {
|
||||
return typ.Kind() == reflect.Func
|
||||
func isFunc(kindable interface {
|
||||
Kind() reflect.Kind
|
||||
}) bool {
|
||||
return kindable.Kind() == reflect.Func
|
||||
}
|
||||
|
||||
/*
|
||||
// no f. this, it's too complicated and it will be harder to maintain later on:
|
||||
func isSliceAndExpectedItem(got reflect.Type, in []reflect.Type, currentBindersIdx int) bool {
|
||||
kind := got.Kind()
|
||||
// if got result is slice or array.
|
||||
return (kind == reflect.Slice || kind == reflect.Array) &&
|
||||
// if has expected next input.
|
||||
len(in)-1 > currentBindersIdx &&
|
||||
// if the current input's type is not the same as got (if it's not a slice of that types or anything else).
|
||||
equalTypes(got, in[currentBindersIdx])
|
||||
}
|
||||
*/
|
||||
|
||||
func equalTypes(got reflect.Type, expected reflect.Type) bool {
|
||||
if got == expected {
|
||||
return true
|
||||
|
@ -59,8 +55,16 @@ func equalTypes(got reflect.Type, expected reflect.Type) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// for controller only.
|
||||
func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type {
|
||||
n := funcTyp.NumIn()
|
||||
funcIn := make([]reflect.Type, n, n)
|
||||
for i := 0; i < n; i++ {
|
||||
funcIn[i] = funcTyp.In(i)
|
||||
}
|
||||
return funcIn
|
||||
}
|
||||
|
||||
// for controller's fields only.
|
||||
func structFieldIgnored(f reflect.StructField) bool {
|
||||
if !f.Anonymous {
|
||||
return true // if not anonymous(embedded), ignore it.
|
||||
|
@ -76,22 +80,28 @@ type field struct {
|
|||
Name string // the actual name
|
||||
|
||||
// this could be empty, but in our cases it's not,
|
||||
// it's filled with the service and it's filled from the lookupFields' caller.
|
||||
// it's filled with the bind object (as service which means as static value)
|
||||
// and it's filled from the lookupFields' caller.
|
||||
AnyValue reflect.Value
|
||||
}
|
||||
|
||||
func lookupFields(typ reflect.Type, parentIndex int) (fields []field) {
|
||||
for i, n := 0, typ.NumField(); i < n; i++ {
|
||||
f := typ.Field(i)
|
||||
func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) {
|
||||
if elemTyp.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
|
||||
if f.Type.Kind() == reflect.Struct && !structFieldIgnored(f) {
|
||||
fields = append(fields, lookupFields(f.Type, i)...)
|
||||
for i, n := 0, elemTyp.NumField(); i < n; i++ {
|
||||
f := elemTyp.Field(i)
|
||||
|
||||
if indirectTyp(f.Type).Kind() == reflect.Struct &&
|
||||
!structFieldIgnored(f) {
|
||||
fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...)
|
||||
continue
|
||||
}
|
||||
|
||||
index := []int{i}
|
||||
if parentIndex >= 0 {
|
||||
index = append([]int{parentIndex}, index...)
|
||||
if len(parentIndex) > 0 {
|
||||
index = append(parentIndex, i)
|
||||
}
|
||||
|
||||
field := field{
|
||||
|
@ -105,3 +115,16 @@ func lookupFields(typ reflect.Type, parentIndex int) (fields []field) {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
func lookupNonZeroFieldsValues(v reflect.Value) (bindValues []reflect.Value) {
|
||||
elem := indirectVal(v)
|
||||
fields := lookupFields(indirectTyp(v.Type()), nil)
|
||||
for _, f := range fields {
|
||||
|
||||
if fieldVal := elem.FieldByIndex(f.Index); f.Type.Kind() == reflect.Ptr && !zerocheck.IsZero(fieldVal) {
|
||||
bindValues = append(bindValues, fieldVal)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
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
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/sessions"
|
||||
"reflect"
|
||||
|
||||
"github.com/kataras/golog"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/sessions"
|
||||
)
|
||||
|
||||
var defaultManager = sessions.New(sessions.Config{})
|
||||
|
@ -25,8 +25,8 @@ type SessionController struct {
|
|||
// It makes sure that its "Manager" field is filled
|
||||
// even if the caller didn't provide any sessions manager via the `app.Controller` function.
|
||||
func (s *SessionController) OnActivate(ca *ControllerActivator) {
|
||||
if !ca.Engine.BindTypeExists(reflect.TypeOf(defaultManager)) {
|
||||
ca.Engine.Bind(defaultManager)
|
||||
if !ca.BindTypeExists(reflect.TypeOf(defaultManager)) {
|
||||
ca.Bind(defaultManager)
|
||||
golog.Warnf(`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field,
|
||||
therefore this controller is using the default sessions manager instead.
|
||||
Please refer to the documentation to learn how you can provide the session manager`)
|
||||
|
|
Loading…
Reference in New Issue
Block a user