diff --git a/.gitattributes b/.gitattributes
index 158482ee..574feb35 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -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
\ No newline at end of file
diff --git a/_examples/README.md b/_examples/README.md
old mode 100755
new mode 100644
diff --git a/context/context.go b/context/context.go
index a669f619..19ea0f9a 100644
--- a/context/context.go
+++ b/context/context.go
@@ -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
diff --git a/middleware/README.md b/middleware/README.md
old mode 100755
new mode 100644
diff --git a/mvc/session_controller.go b/mvc/session_controller.go
index 240a88be..bc403311 100644
--- a/mvc/session_controller.go
+++ b/mvc/session_controller.go
@@ -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`)
diff --git a/mvc2/bind.go b/mvc2/bind.go
new file mode 100644
index 00000000..d305dc50
--- /dev/null
+++ b/mvc2/bind.go
@@ -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
+}
diff --git a/mvc2/binder/binder/input.go b/mvc2/binder/binder/input.go
deleted file mode 100644
index 1e583530..00000000
--- a/mvc2/binder/binder/input.go
+++ /dev/null
@@ -1,4 +0,0 @@
-package binder
-
-type Input interface {
-}
diff --git a/mvc2/binder/binding.go b/mvc2/binder/binding.go
deleted file mode 100644
index 9fe0eed3..00000000
--- a/mvc2/binder/binding.go
+++ /dev/null
@@ -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
-}
diff --git a/mvc2/binder/func_input.go b/mvc2/binder/func_input.go
deleted file mode 100644
index 0587a0cc..00000000
--- a/mvc2/binder/func_input.go
+++ /dev/null
@@ -1 +0,0 @@
-package binder
diff --git a/mvc2/binder/func_result.go b/mvc2/binder/func_result.go
deleted file mode 100644
index cb8ba5fe..00000000
--- a/mvc2/binder/func_result.go
+++ /dev/null
@@ -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
-}
diff --git a/mvc2/binder/reflect.go b/mvc2/binder/reflect.go
deleted file mode 100644
index 20c75b9f..00000000
--- a/mvc2/binder/reflect.go
+++ /dev/null
@@ -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
-}
diff --git a/mvc2/binder/to_struct.go b/mvc2/binder/to_struct.go
deleted file mode 100644
index cb85d0d6..00000000
--- a/mvc2/binder/to_struct.go
+++ /dev/null
@@ -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
-}
diff --git a/mvc2/binder_in.go b/mvc2/binder_in.go
deleted file mode 100644
index acc8dee8..00000000
--- a/mvc2/binder_in.go
+++ /dev/null
@@ -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
-}
diff --git a/mvc2/binder_in_service.go b/mvc2/binder_in_service.go
deleted file mode 100644
index db4fde08..00000000
--- a/mvc2/binder_in_service.go
+++ /dev/null
@@ -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
-}
diff --git a/mvc2/binder_in_service_test.go b/mvc2/binder_in_service_test.go
deleted file mode 100644
index 88779dce..00000000
--- a/mvc2/binder_in_service_test.go
+++ /dev/null
@@ -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{}{}))
-
-}
diff --git a/mvc2/binder_in_test.go b/mvc2/binder_in_test.go
deleted file mode 100644
index 099a3578..00000000
--- a/mvc2/binder_in_test.go
+++ /dev/null
@@ -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 ""
-}
diff --git a/mvc2/controller.go b/mvc2/controller.go
index 9d7aad3d..452e68c8 100644
--- a/mvc2/controller.go
+++ b/mvc2/controller.go
@@ -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
diff --git a/mvc2/controller_handler_test.go b/mvc2/controller_handle_test.go
similarity index 70%
rename from mvc2/controller_handler_test.go
rename to mvc2/controller_handle_test.go
index 3a48f8b4..26b77f17 100644
--- a/mvc2/controller_handler_test.go
+++ b/mvc2/controller_handle_test.go
@@ -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())
}
diff --git a/mvc2/controller_test.go b/mvc2/controller_test.go
new file mode 100644
index 00000000..05afef30
--- /dev/null
+++ b/mvc2/controller_test.go
@@ -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")
+}
diff --git a/mvc2/engine.go b/mvc2/engine.go
index f40816e0..f421b26f 100644
--- a/mvc2/engine.go
+++ b/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()
}
diff --git a/mvc2/mvc_test.go b/mvc2/engine_handler_test.go
similarity index 89%
rename from mvc2/mvc_test.go
rename to mvc2/engine_handler_test.go
index e33f11b0..547de0c9 100644
--- a/mvc2/mvc_test.go
+++ b/mvc2/engine_handler_test.go
@@ -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 (
diff --git a/mvc2/handler_out.go b/mvc2/func_result.go
similarity index 97%
rename from mvc2/handler_out.go
rename to mvc2/func_result.go
index eb7999ad..d0331949 100644
--- a/mvc2/handler_out.go
+++ b/mvc2/func_result.go
@@ -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))
}
}
diff --git a/mvc2/func_result_test.go b/mvc2/func_result_test.go
new file mode 100644
index 00000000..5df3b072
--- /dev/null
+++ b/mvc2/func_result_test.go
@@ -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 "text", "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{"text"}
+}
+
+func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusOk() (testControllerMethodCustomResult, int) {
+ return testControllerMethodCustomResult{"OK"}, iris.StatusOK
+}
+
+func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusNotOk() (testControllerMethodCustomResult, int) {
+ return testControllerMethodCustomResult{"internal server error"}, 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("text")
+
+ e.GET("/custom/response").Expect().Status(iris.StatusOK).
+ ContentType("text/html", "utf-8").
+ Body().Equal("text")
+ e.GET("/custom/response/with/status/ok").Expect().Status(iris.StatusOK).
+ ContentType("text/html", "utf-8").
+ Body().Equal("OK")
+ e.GET("/custom/response/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
+ ContentType("text/html", "utf-8").
+ Body().Equal("internal server error")
+
+ 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)
+}
diff --git a/mvc2/handler.go b/mvc2/handler.go
index 85be1d52..e35ab7bd 100644
--- a/mvc2/handler.go
+++ b/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
+
}
diff --git a/mvc2/handler_out_test.go b/mvc2/handler_out_test.go
deleted file mode 100644
index bf5abda3..00000000
--- a/mvc2/handler_out_test.go
+++ /dev/null
@@ -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 "text", "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{"text"}
-// }
-
-// func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusOk() (testControllerMethodCustomResult, int) {
-// return testControllerMethodCustomResult{"OK"}, iris.StatusOK
-// }
-
-// func (c *testControllerMethodResultTypes) GetCustomResponseWithStatusNotOk() (testControllerMethodCustomResult, int) {
-// return testControllerMethodCustomResult{"internal server error"}, 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("text")
-
-// e.GET("/custom/response").Expect().Status(iris.StatusOK).
-// ContentType("text/html", "utf-8").
-// Body().Equal("text")
-// e.GET("/custom/response/with/status/ok").Expect().Status(iris.StatusOK).
-// ContentType("text/html", "utf-8").
-// Body().Equal("OK")
-// e.GET("/custom/response/with/status/not/ok").Expect().Status(iris.StatusInternalServerError).
-// ContentType("text/html", "utf-8").
-// Body().Equal("internal server error")
-
-// 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)
-// }
diff --git a/mvc2/handler_test.go b/mvc2/handler_test.go
index 358a38c1..ee9962fd 100644
--- a/mvc2/handler_test.go
+++ b/mvc2/handler_test.go
@@ -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)
diff --git a/mvc2/binder_in_path_param.go b/mvc2/path_param_binder.go
similarity index 51%
rename from mvc2/binder_in_path_param.go
rename to mvc2/path_param_binder.go
index 52196564..1018cfb7 100644
--- a/mvc2/binder_in_path_param.go
+++ b/mvc2/path_param_binder.go
@@ -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
diff --git a/mvc2/binder_in_path_param_test.go b/mvc2/path_param_binder_test.go
similarity index 100%
rename from mvc2/binder_in_path_param_test.go
rename to mvc2/path_param_binder_test.go
diff --git a/mvc2/reflect.go b/mvc2/reflect.go
index 69f533fb..4f6080e8 100644
--- a/mvc2/reflect.go
+++ b/mvc2/reflect.go
@@ -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
+}
diff --git a/mvc2/service.go b/mvc2/service.go
deleted file mode 100644
index 03ab07e5..00000000
--- a/mvc2/service.go
+++ /dev/null
@@ -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
-}
diff --git a/mvc2/session_controller.go b/mvc2/session_controller.go
index 5ea5f5c4..6ca5bce4 100644
--- a/mvc2/session_controller.go
+++ b/mvc2/session_controller.go
@@ -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`)