mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
made it work but looking for another approach
Former-commit-id: e61c4573543c57b8d6d4ef2583e40f52c391402f
This commit is contained in:
parent
dd5de52f34
commit
7043f352d9
|
@ -206,7 +206,7 @@ func (c *HelloWorldController) GetWelcomeBy(name string, numTimes int) {
|
|||
}
|
||||
```
|
||||
|
||||
> The [_examples/mvc](_examples/mvc) and [mvc/controller_test.go](https://github.com/kataras/iris/blob/master/mvc/controller_test.go) files explain each feature with simple paradigms, they show how you can take advandage of the Iris MVC Binder, Iris MVC Models and many more...
|
||||
> The [_examples/mvc](_examples/mvc) and [mvc/controller_test.go](https://github.com/kataras/iris/blob/master/mvc/controller_test.go) files explain each feature with simple paradigms, they show how you can take advantage of the Iris MVC Binder, Iris MVC Models and many more...
|
||||
|
||||
Every `exported` func prefixed with an HTTP Method(`Get`, `Post`, `Put`, `Delete`...) in a controller is callable as an HTTP endpoint. In the sample above, all funcs writes a string to the response. Note the comments preceding each method.
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
UNIX and BSD hosts can take advandage of the reuse port feature
|
||||
UNIX and BSD hosts can take advantage of the reuse port feature
|
||||
|
||||
```go
|
||||
package main
|
||||
|
|
|
@ -175,7 +175,7 @@ func (c *HelloWorldController) Any() {} handles All method requests
|
|||
*/
|
||||
```
|
||||
|
||||
> The [_examples/mvc](https://github.com/kataras/iris/tree/master/_examples/mvc) and [mvc/controller_test.go](https://github.com/kataras/iris/blob/master/mvc/controller_test.go) files explain each feature with simple paradigms, they show how you can take advandage of the Iris MVC Binder, Iris MVC Models and many more...
|
||||
> The [_examples/mvc](https://github.com/kataras/iris/tree/master/_examples/mvc) and [mvc/controller_test.go](https://github.com/kataras/iris/blob/master/mvc/controller_test.go) files explain each feature with simple paradigms, they show how you can take advantage of the Iris MVC Binder, Iris MVC Models and many more...
|
||||
|
||||
Every `exported` func prefixed with an HTTP Method(`Get`, `Post`, `Put`, `Delete`...) in a controller is callable as an HTTP endpoint. In the sample above, all funcs writes a string to the response. Note the comments preceding each method.
|
||||
|
||||
|
|
|
@ -51,21 +51,14 @@ const (
|
|||
ParamTypePath
|
||||
)
|
||||
|
||||
// ValidKind will return true if at least one param type is supported
|
||||
// for this std kind.
|
||||
func ValidKind(k reflect.Kind) bool {
|
||||
switch k {
|
||||
case reflect.String:
|
||||
fallthrough
|
||||
case reflect.Int:
|
||||
fallthrough
|
||||
case reflect.Int64:
|
||||
fallthrough
|
||||
case reflect.Bool:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
func (pt ParamType) String() string {
|
||||
for k, v := range paramTypes {
|
||||
if v == pt {
|
||||
return k
|
||||
}
|
||||
}
|
||||
|
||||
return "unexpected"
|
||||
}
|
||||
|
||||
// Not because for a single reason
|
||||
|
@ -96,6 +89,23 @@ func (pt ParamType) Kind() reflect.Kind {
|
|||
return reflect.Invalid // 0
|
||||
}
|
||||
|
||||
// ValidKind will return true if at least one param type is supported
|
||||
// for this std kind.
|
||||
func ValidKind(k reflect.Kind) bool {
|
||||
switch k {
|
||||
case reflect.String:
|
||||
fallthrough
|
||||
case reflect.Int:
|
||||
fallthrough
|
||||
case reflect.Int64:
|
||||
fallthrough
|
||||
case reflect.Bool:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Assignable returns true if the "k" standard type
|
||||
// is assignabled to this ParamType.
|
||||
func (pt ParamType) Assignable(k reflect.Kind) bool {
|
||||
|
@ -133,6 +143,30 @@ func LookupParamType(ident string) ParamType {
|
|||
return ParamTypeUnExpected
|
||||
}
|
||||
|
||||
// LookupParamTypeFromStd accepts the string representation of a standard go type.
|
||||
// It returns a ParamType, but it may differs for example
|
||||
// the alphabetical, file, path and string are all string go types, so
|
||||
// make sure that caller resolves these types before this call.
|
||||
//
|
||||
// string matches to string
|
||||
// int matches to int
|
||||
// int64 matches to long
|
||||
// bool matches to boolean
|
||||
func LookupParamTypeFromStd(goType string) ParamType {
|
||||
switch goType {
|
||||
case "string":
|
||||
return ParamTypeString
|
||||
case "int":
|
||||
return ParamTypeInt
|
||||
case "int64":
|
||||
return ParamTypeLong
|
||||
case "bool":
|
||||
return ParamTypeBoolean
|
||||
default:
|
||||
return ParamTypeUnExpected
|
||||
}
|
||||
}
|
||||
|
||||
// ParamStatement is a struct
|
||||
// which holds all the necessary information about a macro parameter.
|
||||
// It holds its type (string, int, alphabetical, file, path),
|
||||
|
|
|
@ -2,6 +2,7 @@ package router
|
|||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/errors"
|
||||
"github.com/kataras/iris/core/router/macro"
|
||||
"github.com/kataras/iris/mvc/activator"
|
||||
)
|
||||
|
@ -14,6 +15,8 @@ import (
|
|||
//
|
||||
// Look the "APIBuilder" for its implementation.
|
||||
type Party interface {
|
||||
// GetReporter returns the reporter for adding errors
|
||||
GetReporter() *errors.Reporter
|
||||
// Macros returns the macro map which is responsible
|
||||
// to register custom macro functions for all routes.
|
||||
//
|
||||
|
|
|
@ -23,7 +23,7 @@ type Route struct {
|
|||
// Handlers are the main route's handlers, executed by order.
|
||||
// Cannot be empty.
|
||||
Handlers context.Handlers
|
||||
mainHandlerName string
|
||||
MainHandlerName string
|
||||
// temp storage, they're appended to the Handlers on build.
|
||||
// Execution happens after Begin and main Handler(s), can be empty.
|
||||
doneHandlers context.Handlers
|
||||
|
@ -61,7 +61,7 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string,
|
|||
tmpl: tmpl,
|
||||
Path: path,
|
||||
Handlers: handlers,
|
||||
mainHandlerName: mainHandlerName,
|
||||
MainHandlerName: mainHandlerName,
|
||||
FormattedPath: formattedPath,
|
||||
}
|
||||
return route, nil
|
||||
|
@ -214,12 +214,12 @@ func (r Route) Trace() string {
|
|||
}
|
||||
printfmt += fmt.Sprintf(" %s ", r.Tmpl().Src)
|
||||
if l := len(r.Handlers); l > 1 {
|
||||
printfmt += fmt.Sprintf("-> %s() and %d more", r.mainHandlerName, l-1)
|
||||
printfmt += fmt.Sprintf("-> %s() and %d more", r.MainHandlerName, l-1)
|
||||
} else {
|
||||
printfmt += fmt.Sprintf("-> %s()", r.mainHandlerName)
|
||||
printfmt += fmt.Sprintf("-> %s()", r.MainHandlerName)
|
||||
}
|
||||
|
||||
// printfmt := fmt.Sprintf("%s: %s >> %s", r.Method, r.Subdomain+r.Tmpl().Src, r.mainHandlerName)
|
||||
// printfmt := fmt.Sprintf("%s: %s >> %s", r.Method, r.Subdomain+r.Tmpl().Src, r.MainHandlerName)
|
||||
// if l := len(r.Handlers); l > 0 {
|
||||
// printfmt += fmt.Sprintf(" and %d more", l)
|
||||
// }
|
||||
|
|
2
doc.go
2
doc.go
|
@ -222,7 +222,7 @@ Below you'll see some useful examples:
|
|||
// ListenAndServe function of the `net/http` package.
|
||||
app.Run(iris.Raw(&http.Server{Addr:":8080"}).ListenAndServe)
|
||||
|
||||
UNIX and BSD hosts can take advandage of the reuse port feature.
|
||||
UNIX and BSD hosts can take advantage of the reuse port feature.
|
||||
|
||||
Example code:
|
||||
|
||||
|
|
164
mvc2/binder.go
164
mvc2/binder.go
|
@ -1,164 +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 {
|
||||
BindType reflect.Type
|
||||
// ctx is slice because all binder functions called by
|
||||
// their `.Call` method which accepts a slice of reflect.Value,
|
||||
// so on the handler maker we will allocate a slice of a single ctx once
|
||||
// and used to all binders.
|
||||
BindFunc func(ctx []reflect.Value) reflect.Value
|
||||
}
|
||||
|
||||
// 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) map[int]*InputBinder {
|
||||
var m map[int]*InputBinder
|
||||
|
||||
for idx, in := range expected {
|
||||
if idx == 0 && isContext(in) {
|
||||
// if the first is context then set it directly here.
|
||||
m = make(map[int]*InputBinder)
|
||||
m[0] = &InputBinder{
|
||||
BindType: contextTyp,
|
||||
BindFunc: func(ctxValues []reflect.Value) reflect.Value {
|
||||
return ctxValues[0]
|
||||
},
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for _, b := range binders {
|
||||
// if same type or the result of binder implements the expected in's type.
|
||||
/*
|
||||
// no f. this, it's too complicated and it will be harder to maintain later on:
|
||||
// if has slice we can't know the returning len from now
|
||||
// so the expected input length and the len(m) are impossible to guess.
|
||||
if isSliceAndExpectedItem(b.BindType, expected, idx) {
|
||||
hasSlice = true
|
||||
m[idx] = b
|
||||
continue
|
||||
}
|
||||
*/
|
||||
if equalTypes(b.BindType, in) {
|
||||
if m == nil {
|
||||
m = make(map[int]*InputBinder)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
switch indirectTyp(reflect.TypeOf(binder)).Kind() {
|
||||
case reflect.Func:
|
||||
return functionType
|
||||
case reflect.Struct:
|
||||
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{
|
||||
BindType: outTyp,
|
||||
BindFunc: bf,
|
||||
}, nil
|
||||
}
|
|
@ -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 ""
|
||||
}
|
|
@ -1,96 +1,491 @@
|
|||
package mvc2
|
||||
|
||||
import (
|
||||
// "reflect"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
// "github.com/kataras/golog"
|
||||
// "github.com/kataras/iris/context"
|
||||
// // "github.com/kataras/iris/core/router"
|
||||
// "github.com/kataras/iris/mvc/activator"
|
||||
// "github.com/kataras/iris/mvc/activator/methodfunc"
|
||||
"github.com/kataras/iris/context"
|
||||
"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"
|
||||
)
|
||||
|
||||
// no, we will not make any changes to the controller's implementation
|
||||
// let's no re-write the godlike code I wrote two months ago
|
||||
// , just improve it by implementing the only one missing feature:
|
||||
// bind/map/handle custom controller's functions to a custom router path
|
||||
// like regexed.
|
||||
type BaseController interface {
|
||||
BeginRequest(context.Context)
|
||||
EndRequest(context.Context)
|
||||
}
|
||||
|
||||
// C is the basic BaseController type that can be used as an embedded anonymous field
|
||||
// to custom end-dev controllers.
|
||||
//
|
||||
// // BaseController is the interface that all controllers should implement.
|
||||
// type BaseController interface {
|
||||
// BeginRequest(ctx context.Context)
|
||||
// EndRequest(ctx context.Context)
|
||||
// }
|
||||
// func(c *ExampleController) Get() string |
|
||||
// (string, string) |
|
||||
// (string, int) |
|
||||
// int |
|
||||
// (int, string |
|
||||
// (string, error) |
|
||||
// bool |
|
||||
// (any, bool) |
|
||||
// error |
|
||||
// (int, error) |
|
||||
// (customStruct, error) |
|
||||
// customStruct |
|
||||
// (customStruct, int) |
|
||||
// (customStruct, string) |
|
||||
// Result or (Result, error)
|
||||
// where Get is an HTTP Method func.
|
||||
//
|
||||
// Look `core/router#APIBuilder#Controller` method too.
|
||||
//
|
||||
// It completes the `activator.BaseController` interface.
|
||||
//
|
||||
// Example at: https://github.com/kataras/iris/tree/master/_examples/mvc/overview/web/controllers.
|
||||
// Example usage at: https://github.com/kataras/iris/blob/master/mvc/method_result_test.go#L17.
|
||||
type C struct {
|
||||
// The current context.Context.
|
||||
//
|
||||
// we have to name it for two reasons:
|
||||
// 1: can't ignore these via reflection, it doesn't give an option to
|
||||
// see if the functions is derived from another type.
|
||||
// 2: end-developer may want to use some method functions
|
||||
// or any fields that could be conflict with the context's.
|
||||
Ctx context.Context
|
||||
}
|
||||
|
||||
// // type ControllerInitializer interface {
|
||||
// // Init(r router.Party)
|
||||
// // }
|
||||
var _ BaseController = &C{}
|
||||
|
||||
// // type activator struct {
|
||||
// // Router router.Party
|
||||
// // container *Mvc
|
||||
// // }
|
||||
// BeginRequest starts the request by initializing the `Context` field.
|
||||
func (c *C) BeginRequest(ctx context.Context) { c.Ctx = ctx }
|
||||
|
||||
// func registerController(m *Mvc, r router.Party, c BaseController) {
|
||||
// EndRequest does nothing, is here to complete the `BaseController` interface.
|
||||
func (c *C) EndRequest(ctx context.Context) {}
|
||||
|
||||
// }
|
||||
type ControllerActivator struct {
|
||||
Engine *Engine
|
||||
// the router is used on the `Activate` and can be used by end-dev on the `OnActivate`
|
||||
// to register any custom controller's functions as handlers but we will need it here
|
||||
// in order to not create a new type like `ActivationPayload` for the `OnActivate`.
|
||||
Router router.Party
|
||||
|
||||
// // ControllerHandler is responsible to dynamically bind a controller's functions
|
||||
// // to the controller's http mechanism, can be used on the controller's `OnActivate` event.
|
||||
// func ControllerHandler(controller activator.BaseController, funcName string) context.Handler {
|
||||
// // we use funcName instead of an interface{} which can be safely binded with something like:
|
||||
// // myController.HandleThis because we want to make sure that the end-developer
|
||||
// // will make use a function of that controller that owns it because if not then
|
||||
// // the BeginRequest and EndRequest will be called from other handler and also
|
||||
// // the first input argument, which should be the controller itself may not be binded
|
||||
// // to the current controller, all that are solved if end-dev knows what to do
|
||||
// // but we can't bet on it.
|
||||
initRef BaseController // the BaseController as it's passed from the end-dev.
|
||||
|
||||
// cVal := reflect.ValueOf(controller)
|
||||
// elemTyp := reflect.TypeOf(controller) // with the pointer.
|
||||
// m, exists := elemTyp.MethodByName(funcName)
|
||||
// if !exists {
|
||||
// golog.Errorf("mvc controller handler: function '%s' doesn't exist inside the '%s' controller",
|
||||
// funcName, elemTyp.String())
|
||||
// return nil
|
||||
// }
|
||||
// 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
|
||||
|
||||
// fn := cVal.MethodByName(funcName)
|
||||
// if !fn.IsValid() {
|
||||
// golog.Errorf("mvc controller handler: function '%s' inside the '%s' controller has not a valid value",
|
||||
// funcName, elemTyp.String())
|
||||
// return nil
|
||||
// }
|
||||
// key = the method's name.
|
||||
methods map[string]reflect.Method
|
||||
|
||||
// info, ok := methodfunc.FetchFuncInfo(m)
|
||||
// if !ok {
|
||||
// golog.Errorf("mvc controller handler: could not resolve the func info from '%s'", funcName)
|
||||
// return nil
|
||||
// }
|
||||
// services []field
|
||||
// bindServices func(elem reflect.Value)
|
||||
s services
|
||||
}
|
||||
|
||||
// methodFunc, err := methodfunc.ResolveMethodFunc(info)
|
||||
// if err != nil {
|
||||
// golog.Errorf("mvc controller handler: %v", err)
|
||||
// return nil
|
||||
// }
|
||||
func newControllerActivator(engine *Engine, router router.Party, controller BaseController) *ControllerActivator {
|
||||
c := &ControllerActivator{
|
||||
Engine: engine,
|
||||
Router: router,
|
||||
initRef: controller,
|
||||
}
|
||||
|
||||
// m := New()
|
||||
// m.In(controller) // bind the controller itself?
|
||||
// /// TODO: first we must enable interface{} to be used as 'servetime input binder'
|
||||
// // because it will try to match the type and add to its input if the
|
||||
// // func input is that, and this binder will be available to every handler after that,
|
||||
// // so it will be included to its 'in'.
|
||||
// // MakeFuncInputBinder(func(ctx context.Context) interface{} {
|
||||
c.analyze()
|
||||
return c
|
||||
}
|
||||
|
||||
// // // job here.
|
||||
var reservedMethodNames = []string{
|
||||
"BeginRequest",
|
||||
"EndRequest",
|
||||
"OnActivate",
|
||||
}
|
||||
|
||||
// // return controller
|
||||
// // })
|
||||
func isReservedMethod(name string) bool {
|
||||
for _, s := range reservedMethodNames {
|
||||
if s == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// h := m.Handler(fn.Interface())
|
||||
// return func(ctx context.Context) {
|
||||
// controller.BeginRequest(ctx)
|
||||
// h(ctx)
|
||||
// controller.EndRequest(ctx)
|
||||
// }
|
||||
// }
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
if !isReservedMethod(key) {
|
||||
c.methods[key] = m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
func (c *ControllerActivator) Handle(method, path, funcName string, middleware ...context.Handler) error {
|
||||
if method == "" || path == "" || funcName == "" || isReservedMethod(funcName) {
|
||||
// isReservedMethod -> if it's already registered
|
||||
// by a previous Handle or analyze methods internally.
|
||||
return errSkip
|
||||
}
|
||||
|
||||
m, ok := c.methods[funcName]
|
||||
if !ok {
|
||||
err := fmt.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller",
|
||||
funcName, c.FullName)
|
||||
c.Router.GetReporter().AddErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := macro.Parse(path, c.Router.Macros())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("MVC: fail to parse the path for '%s.%s': %v", c.FullName, funcName, err)
|
||||
c.Router.GetReporter().AddErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("===============%s.%s==============\n", c.FullName, funcName)
|
||||
funcIn := getInputArgsFromFunc(m.Type)[1:] // except the receiver, which is the controller pointer itself.
|
||||
|
||||
// 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...))
|
||||
|
||||
s := getServicesFor(m.Func, getPathParamsForInput(tmpl.Params, funcIn...))
|
||||
// s.AddSource(indirectVal(reflect.ValueOf(c.initRef)), c.Engine.Input...)
|
||||
|
||||
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
|
||||
|
||||
// be, _ := typ.MethodByName("BeginRequest")
|
||||
// en, _ := typ.MethodByName("EndRequest")
|
||||
// beginIndex, endIndex := be.Index, en.Index
|
||||
|
||||
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)
|
||||
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))
|
||||
} 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)
|
||||
|
||||
// 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)
|
||||
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
|
||||
)
|
||||
|
||||
// word lexer, not characters.
|
||||
type lexer struct {
|
||||
words []string
|
||||
cur int
|
||||
}
|
||||
|
||||
func newLexer(s string) *lexer {
|
||||
l := new(lexer)
|
||||
l.reset(s)
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *lexer) reset(s string) {
|
||||
l.cur = -1
|
||||
var words []string
|
||||
if s != "" {
|
||||
end := len(s)
|
||||
start := -1
|
||||
|
||||
for i, n := 0, end; i < n; i++ {
|
||||
c := rune(s[i])
|
||||
if unicode.IsUpper(c) {
|
||||
// it doesn't count the last uppercase
|
||||
if start != -1 {
|
||||
end = i
|
||||
words = append(words, s[start:end])
|
||||
}
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
end = i + 1
|
||||
}
|
||||
|
||||
if end > 0 && len(s) >= end {
|
||||
words = append(words, s[start:end])
|
||||
}
|
||||
}
|
||||
|
||||
l.words = words
|
||||
}
|
||||
|
||||
func (l *lexer) next() (w string) {
|
||||
cur := l.cur + 1
|
||||
|
||||
if w = l.peek(cur); w != "" {
|
||||
l.cur++
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *lexer) skip() {
|
||||
if cur := l.cur + 1; cur < len(l.words) {
|
||||
l.cur = cur
|
||||
} else {
|
||||
l.cur = len(l.words) - 1
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) peek(idx int) string {
|
||||
if idx < len(l.words) {
|
||||
return l.words[idx]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (l *lexer) peekNext() (w string) {
|
||||
return l.peek(l.cur + 1)
|
||||
}
|
||||
|
||||
func (l *lexer) peekPrev() (w string) {
|
||||
if l.cur > 0 {
|
||||
cur := l.cur - 1
|
||||
w = l.words[cur]
|
||||
}
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
var posWords = map[int]string{
|
||||
0: "",
|
||||
1: "first",
|
||||
2: "second",
|
||||
3: "third",
|
||||
4: "forth",
|
||||
5: "five",
|
||||
6: "sixth",
|
||||
7: "seventh",
|
||||
8: "eighth",
|
||||
9: "ninth",
|
||||
}
|
||||
|
||||
func genParamKey(argIdx int) string {
|
||||
return "param" + posWords[argIdx] // paramfirst, paramsecond...
|
||||
}
|
||||
|
||||
type parser struct {
|
||||
lexer *lexer
|
||||
fn reflect.Method
|
||||
}
|
||||
|
||||
func parse(fn reflect.Method) (method, path string, err error) {
|
||||
p := &parser{
|
||||
fn: fn,
|
||||
lexer: newLexer(fn.Name),
|
||||
}
|
||||
return p.parse()
|
||||
}
|
||||
|
||||
func methodTitle(httpMethod string) string {
|
||||
httpMethodFuncName := strings.Title(strings.ToLower(httpMethod))
|
||||
return httpMethodFuncName
|
||||
}
|
||||
|
||||
var errSkip = errors.New("skip")
|
||||
|
||||
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 {
|
||||
possibleMethodFuncName := methodTitle(httpMethod)
|
||||
if strings.Index(w, possibleMethodFuncName) == 0 {
|
||||
method = httpMethod
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if method == "" {
|
||||
// this is not a valid method to parse, we just skip it,
|
||||
// it may be used for end-dev's use cases.
|
||||
return "", "", errSkip
|
||||
}
|
||||
|
||||
for {
|
||||
w := p.lexer.next()
|
||||
if w == "" {
|
||||
break
|
||||
}
|
||||
|
||||
if w == tokenBy {
|
||||
funcArgPos++ // starting with 1 because in typ.NumIn() the first is the struct receiver.
|
||||
|
||||
// No need for these:
|
||||
// ByBy will act like /{param:type}/{param:type} as users expected
|
||||
// if func input arguments are there, else act By like normal path /by.
|
||||
//
|
||||
// if p.lexer.peekPrev() == tokenBy || typ.NumIn() == 1 { // ByBy, then act this second By like a path
|
||||
// a.relPath += "/" + strings.ToLower(w)
|
||||
// continue
|
||||
// }
|
||||
|
||||
if path, err = p.parsePathParam(path, w, funcArgPos); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// static path.
|
||||
path += "/" + strings.ToLower(w)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parser) parsePathParam(path string, w string, funcArgPos int) (string, error) {
|
||||
typ := p.fn.Type
|
||||
|
||||
if typ.NumIn() <= funcArgPos {
|
||||
|
||||
// By found but input arguments are not there, so act like /by path without restricts.
|
||||
path += "/" + strings.ToLower(w)
|
||||
return path, nil
|
||||
}
|
||||
|
||||
var (
|
||||
paramKey = genParamKey(funcArgPos) // paramfirst, paramsecond...
|
||||
paramType = ast.ParamTypeString // default string
|
||||
)
|
||||
|
||||
// string, int...
|
||||
goType := typ.In(funcArgPos).Name()
|
||||
nextWord := p.lexer.peekNext()
|
||||
|
||||
if nextWord == tokenWildcard {
|
||||
p.lexer.skip() // skip the Wildcard word.
|
||||
paramType = ast.ParamTypePath
|
||||
} else if pType := ast.LookupParamTypeFromStd(goType); pType != ast.ParamTypeUnExpected {
|
||||
// it's not wildcard, so check base on our available macro types.
|
||||
paramType = pType
|
||||
} else {
|
||||
return "", errors.New("invalid syntax for " + p.fn.Name)
|
||||
}
|
||||
|
||||
// /{paramfirst:path}, /{paramfirst:long}...
|
||||
path += fmt.Sprintf("/{%s:%s}", paramKey, paramType.String())
|
||||
|
||||
if nextWord == "" && typ.NumIn() > funcArgPos+1 {
|
||||
// By is the latest word but func is expected
|
||||
// more path parameters values, i.e:
|
||||
// GetBy(name string, age int)
|
||||
// The caller (parse) doesn't need to know
|
||||
// about the incremental funcArgPos because
|
||||
// it will not need it.
|
||||
return p.parsePathParam(path, nextWord, funcArgPos+1)
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
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"
|
||||
// "github.com/kataras/iris/mvc/activator/methodfunc"
|
||||
//. "github.com/kataras/iris/mvc2"
|
||||
. "github.com/kataras/iris/mvc2"
|
||||
)
|
||||
|
||||
type testController struct {
|
||||
mvc.C
|
||||
Service *TestServiceImpl
|
||||
C
|
||||
Service TestService
|
||||
|
||||
reqField string
|
||||
}
|
||||
|
@ -26,7 +28,8 @@ func (c *testController) BeginRequest(ctx iris.Context) {
|
|||
c.reqField = ctx.URLParam("reqfield")
|
||||
}
|
||||
|
||||
func (c *testController) OnActivate(t *mvc.TController) {
|
||||
func (c *testController) OnActivate(t *ControllerActivator) { // OnActivate(t *mvc.TController) {
|
||||
// t.Handle("GET", "/", "Get")
|
||||
t.Handle("GET", "/histatic", "HiStatic")
|
||||
t.Handle("GET", "/hiservice", "HiService")
|
||||
t.Handle("GET", "/hiparam/{ps:string}", "HiParamBy")
|
||||
|
@ -51,9 +54,13 @@ func (c *testController) HiParamEmptyInputBy() string {
|
|||
|
||||
func TestControllerHandler(t *testing.T) {
|
||||
app := iris.New()
|
||||
app.Controller("/", new(testController), &TestServiceImpl{prefix: "service:"})
|
||||
// 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()
|
||||
// test the index, is not part of the current package's implementation but do it.
|
||||
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("index")
|
||||
|
||||
|
@ -78,4 +85,6 @@ func TestControllerHandler(t *testing.T) {
|
|||
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())
|
||||
}
|
||||
|
|
120
mvc2/handler.go
120
mvc2/handler.go
|
@ -35,8 +35,8 @@ var (
|
|||
|
||||
// MustMakeHandler calls the `MakeHandler` and returns its first resultthe low-level handler), see its docs.
|
||||
// It panics on error.
|
||||
func MustMakeHandler(handler interface{}, binders []*InputBinder) context.Handler {
|
||||
h, err := MakeHandler(handler, binders)
|
||||
func MustMakeHandler(handler interface{}, binders ...interface{}) context.Handler {
|
||||
h, err := MakeHandler(handler, binders...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ func MustMakeHandler(handler interface{}, binders []*InputBinder) 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 []*InputBinder) (context.Handler, error) {
|
||||
func MakeHandler(handler interface{}, binders ...interface{}) (context.Handler, error) {
|
||||
if err := validateHandler(handler); err != nil {
|
||||
golog.Errorf("mvc handler: %v", err)
|
||||
return nil, err
|
||||
|
@ -59,79 +59,71 @@ func MakeHandler(handler interface{}, binders []*InputBinder) (context.Handler,
|
|||
return h, 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)
|
||||
inputBinders := make([]reflect.Value, len(binders), len(binders))
|
||||
|
||||
for i := range binders {
|
||||
inputBinders[i] = reflect.ValueOf(binders[i])
|
||||
}
|
||||
|
||||
m := getBindersForInput(binders, typIn...)
|
||||
return makeHandler(reflect.ValueOf(handler), inputBinders), nil
|
||||
|
||||
/*
|
||||
// no f. this, it's too complicated and it will be harder to maintain later on:
|
||||
// the only case that these are not equal is when
|
||||
// binder returns a slice and input contains one or more inputs.
|
||||
*/
|
||||
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
|
||||
}
|
||||
// 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)
|
||||
// }
|
||||
|
||||
hasIn := len(m) > 0
|
||||
fn := reflect.ValueOf(handler)
|
||||
// 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
|
||||
// }
|
||||
|
||||
// if has no input to bind then execute the "handler" using the mvc style
|
||||
// for any output parameters.
|
||||
if !hasIn {
|
||||
// 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))
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
s := getServicesFor(fn, inputBinders)
|
||||
if len(s) == 0 {
|
||||
golog.Errorf("mvc handler: input arguments length(%d) and valid binders length(%d) are not equal", inLen, len(s))
|
||||
return nil
|
||||
}
|
||||
|
||||
n := fn.Type().NumIn()
|
||||
// contextIndex := -1
|
||||
// if n > 0 {
|
||||
// if isContext(fn.Type().In(0)) {
|
||||
// contextIndex = 0
|
||||
// }
|
||||
// }
|
||||
return func(ctx context.Context) {
|
||||
// we could use other tricks for "in"
|
||||
// here but let's stick to that which is clearly
|
||||
// that it doesn't keep any previous state
|
||||
// and it allocates exactly what we need,
|
||||
// so we can set via index instead of append.
|
||||
// The other method we could use is to
|
||||
// declare the in on the build state (before the return)
|
||||
// and use in[0:0] with append later on.
|
||||
ctxValue := []reflect.Value{reflect.ValueOf(ctx)}
|
||||
|
||||
in := make([]reflect.Value, n, n)
|
||||
ctxValues := []reflect.Value{reflect.ValueOf(ctx)}
|
||||
for k, v := range m {
|
||||
in[k] = v.BindFunc(ctxValues)
|
||||
/*
|
||||
// no f. this, it's too complicated and it will be harder to maintain later on:
|
||||
// now an additional check if it's array and has more inputs of the same type
|
||||
// and all these results to the expected inputs.
|
||||
// n-1: if has more to set.
|
||||
result := v.BindFunc(ctxValues)
|
||||
if isSliceAndExpectedItem(result.Type(), in, k) {
|
||||
// if kind := result.Kind(); (kind == reflect.Slice || kind == reflect.Array) && n-1 > k {
|
||||
prev := 0
|
||||
for j, nn := 1, result.Len(); j < nn; j++ {
|
||||
item := result.Slice(prev, j)
|
||||
prev++
|
||||
// remember; we already set the inputs type, so we know
|
||||
// what the function expected to have.
|
||||
if !equalTypes(item.Type(), in[k+1].Type()) {
|
||||
break
|
||||
}
|
||||
// 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))
|
||||
|
||||
in[k+1] = item
|
||||
}
|
||||
} else {
|
||||
in[k] = result
|
||||
}
|
||||
*/
|
||||
s.FillFuncInput(ctxValue, &in)
|
||||
|
||||
if ctx.IsStopped() {
|
||||
return
|
||||
}
|
||||
}
|
||||
methodfunc.DispatchFuncResult(ctx, fn.Call(in))
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,19 +69,19 @@ var (
|
|||
)
|
||||
|
||||
func TestMakeHandler(t *testing.T) {
|
||||
binders := []*InputBinder{
|
||||
// #1
|
||||
MustMakeFuncInputBinder(testBinderFuncUserStruct),
|
||||
// #2
|
||||
MustMakeServiceInputBinder(testBinderService),
|
||||
// #3
|
||||
MustMakeFuncInputBinder(testBinderFuncParam),
|
||||
}
|
||||
// binders := []*InputBinder{
|
||||
// // #1
|
||||
// MustMakeFuncInputBinder(testBinderFuncUserStruct),
|
||||
// // #2
|
||||
// MustMakeServiceInputBinder(testBinderService),
|
||||
// // #3
|
||||
// MustMakeFuncInputBinder(testBinderFuncParam),
|
||||
// }
|
||||
|
||||
var (
|
||||
h1 = MustMakeHandler(testConsumeUserHandler, binders)
|
||||
h2 = MustMakeHandler(testConsumeServiceHandler, binders)
|
||||
h3 = MustMakeHandler(testConsumeParamHandler, binders)
|
||||
h1 = MustMakeHandler(testConsumeUserHandler, testBinderFuncUserStruct)
|
||||
h2 = MustMakeHandler(testConsumeServiceHandler, testBinderService)
|
||||
h3 = MustMakeHandler(testConsumeParamHandler, testBinderFuncParam)
|
||||
)
|
||||
|
||||
testAppWithMvcHandlers(t, h1, h2, h3)
|
||||
|
|
68
mvc2/mvc.go
68
mvc2/mvc.go
|
@ -1,68 +0,0 @@
|
|||
package mvc2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
var (
|
||||
errNil = errors.New("nil")
|
||||
errBad = errors.New("bad")
|
||||
errAlreadyExists = errors.New("already exists")
|
||||
)
|
||||
|
||||
type Mvc struct {
|
||||
binders []*InputBinder
|
||||
}
|
||||
|
||||
func New() *Mvc {
|
||||
return new(Mvc)
|
||||
}
|
||||
|
||||
func (m *Mvc) Child() *Mvc {
|
||||
child := New()
|
||||
|
||||
// copy the current parent's ctx func binders and services to this new child.
|
||||
if len(m.binders) > 0 {
|
||||
binders := make([]*InputBinder, len(m.binders), len(m.binders))
|
||||
for i, v := range m.binders {
|
||||
binders[i] = v
|
||||
}
|
||||
child.binders = binders
|
||||
}
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
func (m *Mvc) In(binders ...interface{}) *Mvc {
|
||||
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
|
||||
}
|
||||
|
||||
m.binders = append(m.binders, b)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Mvc) Handler(handler interface{}) context.Handler {
|
||||
h, _ := MakeHandler(handler, m.binders) // it logs errors already, so on any error the "h" will be nil.
|
||||
return h
|
||||
}
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
func TestMvcInAndHandler(t *testing.T) {
|
||||
m := New().In(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
|
||||
m := New().Bind(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
|
||||
|
||||
var (
|
||||
h1 = m.Handler(testConsumeUserHandler)
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
package mvc2
|
||||
|
||||
import (
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/memstore"
|
||||
)
|
||||
|
||||
// PathParams is the context's named path parameters, see `PathParamsBinder` too.
|
||||
type PathParams = context.RequestParams
|
||||
|
||||
// PathParamsBinder is the binder which will bind the `PathParams` type value to the specific
|
||||
// handler's input argument, see `PathParams` as well.
|
||||
func PathParamsBinder(ctx context.Context) PathParams {
|
||||
return *ctx.Params()
|
||||
}
|
||||
|
||||
// PathParam describes a named path parameter, it's the result of the PathParamBinder and the expected
|
||||
// handler func's input argument's type, see `PathParamBinder` too.
|
||||
type PathParam struct {
|
||||
memstore.Entry
|
||||
Empty bool
|
||||
}
|
||||
|
||||
// PathParamBinder is the binder which binds a handler func's input argument to a named path parameter
|
||||
// based on its name, see `PathParam` as well.
|
||||
func PathParamBinder(name string) func(ctx context.Context) PathParam {
|
||||
return func(ctx context.Context) PathParam {
|
||||
e, found := ctx.Params().GetEntry(name)
|
||||
if !found {
|
||||
|
||||
// useless check here but it doesn't hurt,
|
||||
// useful only when white-box tests run.
|
||||
if ctx.Application() != nil {
|
||||
ctx.Application().Logger().Warnf(ctx.HandlerName()+": expected parameter name '%s' to be described in the route's path in order to be received by the `ParamBinder`, please fix it.\n The main handler will not be executed for your own protection.", name)
|
||||
}
|
||||
|
||||
ctx.StopExecution()
|
||||
return PathParam{
|
||||
Empty: true,
|
||||
}
|
||||
}
|
||||
return PathParam{e, false}
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package mvc2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris/context"
|
||||
)
|
||||
|
||||
func TestPathParamsBinder(t *testing.T) {
|
||||
m := New()
|
||||
m.In(PathParamsBinder)
|
||||
|
||||
got := ""
|
||||
|
||||
h := m.Handler(func(params PathParams) {
|
||||
got = params.Get("firstname") + params.Get("lastname")
|
||||
})
|
||||
|
||||
ctx := context.NewContext(nil)
|
||||
ctx.Params().Set("firstname", "Gerasimos")
|
||||
ctx.Params().Set("lastname", "Maropoulos")
|
||||
h(ctx)
|
||||
expected := "GerasimosMaropoulos"
|
||||
if got != expected {
|
||||
t.Fatalf("expected the params 'firstname' + 'lastname' to be '%s' but got '%s'", expected, got)
|
||||
}
|
||||
}
|
||||
func TestPathParamBinder(t *testing.T) {
|
||||
m := New()
|
||||
m.In(PathParamBinder("username"))
|
||||
|
||||
got := ""
|
||||
executed := false
|
||||
h := m.Handler(func(username PathParam) {
|
||||
// this should not be fired at all if "username" param wasn't found at all.
|
||||
// although router is responsible for that but the `ParamBinder` makes that check as well because
|
||||
// the end-developer may put a param as input argument on her/his function but
|
||||
// on its route's path didn't describe the path parameter,
|
||||
// the handler fires a warning and stops the execution for the invalid handler to protect the user.
|
||||
executed = true
|
||||
got = username.String()
|
||||
})
|
||||
|
||||
expectedUsername := "kataras"
|
||||
ctx := context.NewContext(nil)
|
||||
ctx.Params().Set("username", expectedUsername)
|
||||
h(ctx)
|
||||
|
||||
if got != expectedUsername {
|
||||
t.Fatalf("expected the param 'username' to be '%s' but got '%s'", expectedUsername, got)
|
||||
}
|
||||
|
||||
// test the non executed if param not found.
|
||||
executed = false
|
||||
got = ""
|
||||
|
||||
ctx2 := context.NewContext(nil)
|
||||
h(ctx2)
|
||||
|
||||
if got != "" {
|
||||
t.Fatalf("expected the param 'username' to be entirely empty but got '%s'", got)
|
||||
}
|
||||
if executed {
|
||||
t.Fatalf("expected the handler to not be executed")
|
||||
}
|
||||
}
|
|
@ -58,3 +58,50 @@ func equalTypes(got reflect.Type, expected reflect.Type) bool {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
274
mvc2/service.go
274
mvc2/service.go
|
@ -1,92 +1,206 @@
|
|||
package mvc2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// // Service is a `reflect.Value` value.
|
||||
// // We keep that type here,
|
||||
// // if we ever need to change this type we will not have
|
||||
// // to refactor the whole mvc's codebase.
|
||||
// type Service struct {
|
||||
// reflect.Value
|
||||
// typ reflect.Type
|
||||
// }
|
||||
type service struct {
|
||||
Type reflect.Type
|
||||
Value reflect.Value
|
||||
StructFieldIndex []int
|
||||
|
||||
// // Valid checks if the service's Value's Value is valid for set or get.
|
||||
// func (s Service) Valid() bool {
|
||||
// return goodVal(s.Value)
|
||||
// }
|
||||
// for func input.
|
||||
ReturnValue func(ctx []reflect.Value) reflect.Value
|
||||
FuncInputIndex int
|
||||
FuncInputContextIndex int
|
||||
}
|
||||
|
||||
// // Equal returns if the
|
||||
// func (s Service) Equal(other Service) bool {
|
||||
// return equalTypes(s.typ, other.typ)
|
||||
// }
|
||||
type services []*service
|
||||
|
||||
// func (s Service) String() string {
|
||||
// return s.Type().String()
|
||||
// }
|
||||
|
||||
// func wrapService(service interface{}) Service {
|
||||
// if s, ok := service.(Service); ok {
|
||||
// return s // if it's a Service already.
|
||||
// }
|
||||
// return Service{
|
||||
// Value: reflect.ValueOf(service),
|
||||
// typ: reflect.TypeOf(service),
|
||||
// }
|
||||
// }
|
||||
|
||||
// // WrapServices wrap a generic services into structured Service slice.
|
||||
// func WrapServices(services ...interface{}) []Service {
|
||||
// if l := len(services); l > 0 {
|
||||
// out := make([]Service, l, l)
|
||||
// for i, s := range services {
|
||||
// out[i] = wrapService(s)
|
||||
// }
|
||||
// return out
|
||||
// }
|
||||
// return 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)
|
||||
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
|
||||
}
|
||||
|
||||
// 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{
|
||||
BindType: typ,
|
||||
BindFunc: func(_ []reflect.Value) reflect.Value {
|
||||
return val
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
package mvc2
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type (
|
||||
testService interface {
|
||||
say(string)
|
||||
}
|
||||
testServiceImpl struct {
|
||||
prefix string
|
||||
}
|
||||
)
|
||||
|
||||
func (s *testServiceImpl) say(message string) string {
|
||||
return s.prefix + ": " + message
|
||||
}
|
||||
|
||||
func TestMakeServiceInputBinder(t *testing.T) {
|
||||
expectedService := &testServiceImpl{"say"}
|
||||
b := MustMakeServiceInputBinder(expectedService)
|
||||
// in
|
||||
var (
|
||||
intType = reflect.TypeOf(1)
|
||||
availableBinders = []*InputBinder{b}
|
||||
)
|
||||
|
||||
// 1
|
||||
testCheck(t, "test1", true, testGetBindersForInput(t, availableBinders,
|
||||
[]interface{}{expectedService}, reflect.TypeOf(expectedService)))
|
||||
// 2
|
||||
testCheck(t, "test2-fail", false, testGetBindersForInput(t, availableBinders,
|
||||
[]interface{}{42}))
|
||||
// 3
|
||||
testCheck(t, "test3-fail", false, testGetBindersForInput(t, availableBinders,
|
||||
[]interface{}{42}, intType))
|
||||
// 4
|
||||
testCheck(t, "test4-fail", false, testGetBindersForInput(t, availableBinders,
|
||||
[]interface{}{42}))
|
||||
// 5 - check if nothing passed, so no valid binders at all.
|
||||
testCheck(t, "test5", true, testGetBindersForInput(t, availableBinders,
|
||||
[]interface{}{}))
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user