mirror of
https://github.com/kataras/iris.git
synced 2025-02-09 02:34:55 +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.
|
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
|
```go
|
||||||
package main
|
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.
|
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
|
ParamTypePath
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidKind will return true if at least one param type is supported
|
func (pt ParamType) String() string {
|
||||||
// for this std kind.
|
for k, v := range paramTypes {
|
||||||
func ValidKind(k reflect.Kind) bool {
|
if v == pt {
|
||||||
switch k {
|
return k
|
||||||
case reflect.String:
|
}
|
||||||
fallthrough
|
|
||||||
case reflect.Int:
|
|
||||||
fallthrough
|
|
||||||
case reflect.Int64:
|
|
||||||
fallthrough
|
|
||||||
case reflect.Bool:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return "unexpected"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not because for a single reason
|
// Not because for a single reason
|
||||||
|
@ -96,6 +89,23 @@ func (pt ParamType) Kind() reflect.Kind {
|
||||||
return reflect.Invalid // 0
|
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
|
// Assignable returns true if the "k" standard type
|
||||||
// is assignabled to this ParamType.
|
// is assignabled to this ParamType.
|
||||||
func (pt ParamType) Assignable(k reflect.Kind) bool {
|
func (pt ParamType) Assignable(k reflect.Kind) bool {
|
||||||
|
@ -133,6 +143,30 @@ func LookupParamType(ident string) ParamType {
|
||||||
return ParamTypeUnExpected
|
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
|
// ParamStatement is a struct
|
||||||
// which holds all the necessary information about a macro parameter.
|
// which holds all the necessary information about a macro parameter.
|
||||||
// It holds its type (string, int, alphabetical, file, path),
|
// It holds its type (string, int, alphabetical, file, path),
|
||||||
|
|
|
@ -2,6 +2,7 @@ package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
|
"github.com/kataras/iris/core/errors"
|
||||||
"github.com/kataras/iris/core/router/macro"
|
"github.com/kataras/iris/core/router/macro"
|
||||||
"github.com/kataras/iris/mvc/activator"
|
"github.com/kataras/iris/mvc/activator"
|
||||||
)
|
)
|
||||||
|
@ -14,6 +15,8 @@ import (
|
||||||
//
|
//
|
||||||
// Look the "APIBuilder" for its implementation.
|
// Look the "APIBuilder" for its implementation.
|
||||||
type Party interface {
|
type Party interface {
|
||||||
|
// GetReporter returns the reporter for adding errors
|
||||||
|
GetReporter() *errors.Reporter
|
||||||
// Macros returns the macro map which is responsible
|
// Macros returns the macro map which is responsible
|
||||||
// to register custom macro functions for all routes.
|
// 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.
|
// Handlers are the main route's handlers, executed by order.
|
||||||
// Cannot be empty.
|
// Cannot be empty.
|
||||||
Handlers context.Handlers
|
Handlers context.Handlers
|
||||||
mainHandlerName string
|
MainHandlerName string
|
||||||
// temp storage, they're appended to the Handlers on build.
|
// temp storage, they're appended to the Handlers on build.
|
||||||
// Execution happens after Begin and main Handler(s), can be empty.
|
// Execution happens after Begin and main Handler(s), can be empty.
|
||||||
doneHandlers context.Handlers
|
doneHandlers context.Handlers
|
||||||
|
@ -61,7 +61,7 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string,
|
||||||
tmpl: tmpl,
|
tmpl: tmpl,
|
||||||
Path: path,
|
Path: path,
|
||||||
Handlers: handlers,
|
Handlers: handlers,
|
||||||
mainHandlerName: mainHandlerName,
|
MainHandlerName: mainHandlerName,
|
||||||
FormattedPath: formattedPath,
|
FormattedPath: formattedPath,
|
||||||
}
|
}
|
||||||
return route, nil
|
return route, nil
|
||||||
|
@ -214,12 +214,12 @@ func (r Route) Trace() string {
|
||||||
}
|
}
|
||||||
printfmt += fmt.Sprintf(" %s ", r.Tmpl().Src)
|
printfmt += fmt.Sprintf(" %s ", r.Tmpl().Src)
|
||||||
if l := len(r.Handlers); l > 1 {
|
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 {
|
} 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 {
|
// if l := len(r.Handlers); l > 0 {
|
||||||
// printfmt += fmt.Sprintf(" and %d more", l)
|
// 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.
|
// ListenAndServe function of the `net/http` package.
|
||||||
app.Run(iris.Raw(&http.Server{Addr:":8080"}).ListenAndServe)
|
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:
|
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
|
package mvc2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "reflect"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
// "github.com/kataras/golog"
|
"github.com/kataras/iris/context"
|
||||||
// "github.com/kataras/iris/context"
|
"github.com/kataras/iris/core/router"
|
||||||
// // "github.com/kataras/iris/core/router"
|
"github.com/kataras/iris/core/router/macro"
|
||||||
// "github.com/kataras/iris/mvc/activator"
|
"github.com/kataras/iris/core/router/macro/interpreter/ast"
|
||||||
// "github.com/kataras/iris/mvc/activator/methodfunc"
|
"github.com/kataras/iris/mvc/activator/methodfunc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// no, we will not make any changes to the controller's implementation
|
type BaseController interface {
|
||||||
// let's no re-write the godlike code I wrote two months ago
|
BeginRequest(context.Context)
|
||||||
// , just improve it by implementing the only one missing feature:
|
EndRequest(context.Context)
|
||||||
// bind/map/handle custom controller's functions to a custom router path
|
}
|
||||||
// like regexed.
|
|
||||||
|
// 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.
|
// func(c *ExampleController) Get() string |
|
||||||
// type BaseController interface {
|
// (string, string) |
|
||||||
// BeginRequest(ctx context.Context)
|
// (string, int) |
|
||||||
// EndRequest(ctx context.Context)
|
// 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 {
|
var _ BaseController = &C{}
|
||||||
// // Init(r router.Party)
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // type activator struct {
|
// BeginRequest starts the request by initializing the `Context` field.
|
||||||
// // Router router.Party
|
func (c *C) BeginRequest(ctx context.Context) { c.Ctx = ctx }
|
||||||
// // container *Mvc
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// 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
|
initRef BaseController // the BaseController as it's passed from the end-dev.
|
||||||
// // 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.
|
|
||||||
|
|
||||||
// cVal := reflect.ValueOf(controller)
|
// FullName it's the last package path segment + "." + the Name.
|
||||||
// elemTyp := reflect.TypeOf(controller) // with the pointer.
|
// i.e: if login-example/user/controller.go, the FullName is "user.Controller".
|
||||||
// m, exists := elemTyp.MethodByName(funcName)
|
FullName string
|
||||||
// if !exists {
|
|
||||||
// golog.Errorf("mvc controller handler: function '%s' doesn't exist inside the '%s' controller",
|
|
||||||
// funcName, elemTyp.String())
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn := cVal.MethodByName(funcName)
|
// key = the method's name.
|
||||||
// if !fn.IsValid() {
|
methods map[string]reflect.Method
|
||||||
// golog.Errorf("mvc controller handler: function '%s' inside the '%s' controller has not a valid value",
|
|
||||||
// funcName, elemTyp.String())
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// info, ok := methodfunc.FetchFuncInfo(m)
|
// services []field
|
||||||
// if !ok {
|
// bindServices func(elem reflect.Value)
|
||||||
// golog.Errorf("mvc controller handler: could not resolve the func info from '%s'", funcName)
|
s services
|
||||||
// return nil
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// methodFunc, err := methodfunc.ResolveMethodFunc(info)
|
func newControllerActivator(engine *Engine, router router.Party, controller BaseController) *ControllerActivator {
|
||||||
// if err != nil {
|
c := &ControllerActivator{
|
||||||
// golog.Errorf("mvc controller handler: %v", err)
|
Engine: engine,
|
||||||
// return nil
|
Router: router,
|
||||||
// }
|
initRef: controller,
|
||||||
|
}
|
||||||
|
|
||||||
// m := New()
|
c.analyze()
|
||||||
// m.In(controller) // bind the controller itself?
|
return c
|
||||||
// /// 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{} {
|
|
||||||
|
|
||||||
// // // 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 false
|
||||||
// return func(ctx context.Context) {
|
}
|
||||||
// controller.BeginRequest(ctx)
|
|
||||||
// h(ctx)
|
func (c *ControllerActivator) analyze() {
|
||||||
// controller.EndRequest(ctx)
|
|
||||||
// }
|
// 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
|
package mvc2_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/kataras/iris"
|
"github.com/kataras/iris"
|
||||||
"github.com/kataras/iris/httptest"
|
"github.com/kataras/iris/httptest"
|
||||||
"github.com/kataras/iris/mvc"
|
// "github.com/kataras/iris/mvc"
|
||||||
// "github.com/kataras/iris/mvc/activator/methodfunc"
|
// "github.com/kataras/iris/mvc/activator/methodfunc"
|
||||||
//. "github.com/kataras/iris/mvc2"
|
. "github.com/kataras/iris/mvc2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testController struct {
|
type testController struct {
|
||||||
mvc.C
|
C
|
||||||
Service *TestServiceImpl
|
Service TestService
|
||||||
|
|
||||||
reqField string
|
reqField string
|
||||||
}
|
}
|
||||||
|
@ -26,7 +28,8 @@ func (c *testController) BeginRequest(ctx iris.Context) {
|
||||||
c.reqField = ctx.URLParam("reqfield")
|
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", "/histatic", "HiStatic")
|
||||||
t.Handle("GET", "/hiservice", "HiService")
|
t.Handle("GET", "/hiservice", "HiService")
|
||||||
t.Handle("GET", "/hiparam/{ps:string}", "HiParamBy")
|
t.Handle("GET", "/hiparam/{ps:string}", "HiParamBy")
|
||||||
|
@ -51,9 +54,13 @@ func (c *testController) HiParamEmptyInputBy() string {
|
||||||
|
|
||||||
func TestControllerHandler(t *testing.T) {
|
func TestControllerHandler(t *testing.T) {
|
||||||
app := iris.New()
|
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"))
|
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.
|
// test the index, is not part of the current package's implementation but do it.
|
||||||
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("index")
|
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("index")
|
||||||
|
|
||||||
|
@ -78,4 +85,6 @@ func TestControllerHandler(t *testing.T) {
|
||||||
e.GET("/hiparamempyinput/value").Expect().Status(httptest.StatusOK).
|
e.GET("/hiparamempyinput/value").Expect().Status(httptest.StatusOK).
|
||||||
Body().Equal("empty in but served with ctx.Params.Get('ps')=value")
|
Body().Equal("empty in but served with ctx.Params.Get('ps')=value")
|
||||||
|
|
||||||
|
endTime := time.Now().Sub(now)
|
||||||
|
fmt.Printf("end at %dns\n", endTime.Nanoseconds())
|
||||||
}
|
}
|
||||||
|
|
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.
|
// MustMakeHandler calls the `MakeHandler` and returns its first resultthe low-level handler), see its docs.
|
||||||
// It panics on error.
|
// It panics on error.
|
||||||
func MustMakeHandler(handler interface{}, binders []*InputBinder) context.Handler {
|
func MustMakeHandler(handler interface{}, binders ...interface{}) context.Handler {
|
||||||
h, err := MakeHandler(handler, binders)
|
h, err := MakeHandler(handler, binders...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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,
|
// custom structs, Result(View | Response) and anything that you already know that mvc implementation supports,
|
||||||
// and returns a low-level `context/iris.Handler` which can be used anywhere in the Iris Application,
|
// and returns a low-level `context/iris.Handler` which can be used anywhere in the Iris Application,
|
||||||
// as middleware or as simple route handler or party handler or subdomain handler-router.
|
// as middleware or as simple route handler or party handler or subdomain handler-router.
|
||||||
func MakeHandler(handler interface{}, binders []*InputBinder) (context.Handler, error) {
|
func MakeHandler(handler interface{}, binders ...interface{}) (context.Handler, error) {
|
||||||
if err := validateHandler(handler); err != nil {
|
if err := validateHandler(handler); err != nil {
|
||||||
golog.Errorf("mvc handler: %v", err)
|
golog.Errorf("mvc handler: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -59,79 +59,71 @@ func MakeHandler(handler interface{}, binders []*InputBinder) (context.Handler,
|
||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
typ := indirectTyp(reflect.TypeOf(handler))
|
inputBinders := make([]reflect.Value, len(binders), len(binders))
|
||||||
n := typ.NumIn()
|
|
||||||
typIn := make([]reflect.Type, n, n)
|
for i := range binders {
|
||||||
for i := 0; i < n; i++ {
|
inputBinders[i] = reflect.ValueOf(binders[i])
|
||||||
typIn[i] = typ.In(i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m := getBindersForInput(binders, typIn...)
|
return makeHandler(reflect.ValueOf(handler), inputBinders), nil
|
||||||
|
|
||||||
/*
|
// typ := indirectTyp(reflect.TypeOf(handler))
|
||||||
// no f. this, it's too complicated and it will be harder to maintain later on:
|
// n := typ.NumIn()
|
||||||
// the only case that these are not equal is when
|
// typIn := make([]reflect.Type, n, n)
|
||||||
// binder returns a slice and input contains one or more inputs.
|
// for i := 0; i < n; i++ {
|
||||||
*/
|
// typIn[i] = typ.In(i)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
hasIn := len(m) > 0
|
// m := getBindersForInput(binders, typIn...)
|
||||||
fn := reflect.ValueOf(handler)
|
// 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
|
// return makeHandler(reflect.ValueOf(handler), m), nil
|
||||||
// for any output parameters.
|
}
|
||||||
if !hasIn {
|
|
||||||
|
func makeHandler(fn reflect.Value, inputBinders []reflect.Value) context.Handler {
|
||||||
|
inLen := fn.Type().NumIn()
|
||||||
|
|
||||||
|
if inLen == 0 {
|
||||||
return func(ctx context.Context) {
|
return func(ctx context.Context) {
|
||||||
methodfunc.DispatchFuncResult(ctx, fn.Call(emptyIn))
|
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) {
|
return func(ctx context.Context) {
|
||||||
// we could use other tricks for "in"
|
ctxValue := []reflect.Value{reflect.ValueOf(ctx)}
|
||||||
// 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.
|
|
||||||
in := make([]reflect.Value, n, n)
|
in := make([]reflect.Value, n, n)
|
||||||
ctxValues := []reflect.Value{reflect.ValueOf(ctx)}
|
// if contextIndex >= 0 {
|
||||||
for k, v := range m {
|
// in[contextIndex] = ctxValue[0]
|
||||||
in[k] = v.BindFunc(ctxValues)
|
// }
|
||||||
/*
|
// ctxValues := []reflect.Value{reflect.ValueOf(ctx)}
|
||||||
// no f. this, it's too complicated and it will be harder to maintain later on:
|
// for k, v := range m {
|
||||||
// now an additional check if it's array and has more inputs of the same type
|
// in[k] = v.BindFunc(ctxValues)
|
||||||
// and all these results to the expected inputs.
|
// if ctx.IsStopped() {
|
||||||
// n-1: if has more to set.
|
// return
|
||||||
result := v.BindFunc(ctxValues)
|
// }
|
||||||
if isSliceAndExpectedItem(result.Type(), in, k) {
|
// }
|
||||||
// if kind := result.Kind(); (kind == reflect.Slice || kind == reflect.Array) && n-1 > k {
|
// methodfunc.DispatchFuncResult(ctx, fn.Call(in))
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
in[k+1] = item
|
s.FillFuncInput(ctxValue, &in)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
in[k] = result
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
if ctx.IsStopped() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
methodfunc.DispatchFuncResult(ctx, fn.Call(in))
|
methodfunc.DispatchFuncResult(ctx, fn.Call(in))
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,19 +69,19 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMakeHandler(t *testing.T) {
|
func TestMakeHandler(t *testing.T) {
|
||||||
binders := []*InputBinder{
|
// binders := []*InputBinder{
|
||||||
// #1
|
// // #1
|
||||||
MustMakeFuncInputBinder(testBinderFuncUserStruct),
|
// MustMakeFuncInputBinder(testBinderFuncUserStruct),
|
||||||
// #2
|
// // #2
|
||||||
MustMakeServiceInputBinder(testBinderService),
|
// MustMakeServiceInputBinder(testBinderService),
|
||||||
// #3
|
// // #3
|
||||||
MustMakeFuncInputBinder(testBinderFuncParam),
|
// MustMakeFuncInputBinder(testBinderFuncParam),
|
||||||
}
|
// }
|
||||||
|
|
||||||
var (
|
var (
|
||||||
h1 = MustMakeHandler(testConsumeUserHandler, binders)
|
h1 = MustMakeHandler(testConsumeUserHandler, testBinderFuncUserStruct)
|
||||||
h2 = MustMakeHandler(testConsumeServiceHandler, binders)
|
h2 = MustMakeHandler(testConsumeServiceHandler, testBinderService)
|
||||||
h3 = MustMakeHandler(testConsumeParamHandler, binders)
|
h3 = MustMakeHandler(testConsumeParamHandler, testBinderFuncParam)
|
||||||
)
|
)
|
||||||
|
|
||||||
testAppWithMvcHandlers(t, h1, h2, h3)
|
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) {
|
func TestMvcInAndHandler(t *testing.T) {
|
||||||
m := New().In(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
|
m := New().Bind(testBinderFuncUserStruct, testBinderService, testBinderFuncParam)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
h1 = m.Handler(testConsumeUserHandler)
|
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
|
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
|
package mvc2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
// // Service is a `reflect.Value` value.
|
type service struct {
|
||||||
// // We keep that type here,
|
Type reflect.Type
|
||||||
// // if we ever need to change this type we will not have
|
Value reflect.Value
|
||||||
// // to refactor the whole mvc's codebase.
|
StructFieldIndex []int
|
||||||
// type Service struct {
|
|
||||||
// reflect.Value
|
|
||||||
// typ reflect.Type
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Valid checks if the service's Value's Value is valid for set or get.
|
// for func input.
|
||||||
// func (s Service) Valid() bool {
|
ReturnValue func(ctx []reflect.Value) reflect.Value
|
||||||
// return goodVal(s.Value)
|
FuncInputIndex int
|
||||||
// }
|
FuncInputContextIndex int
|
||||||
|
}
|
||||||
|
|
||||||
// // Equal returns if the
|
type services []*service
|
||||||
// func (s Service) Equal(other Service) bool {
|
|
||||||
// return equalTypes(s.typ, other.typ)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (s Service) String() string {
|
func (serv *services) AddSource(dest reflect.Value, source ...reflect.Value) {
|
||||||
// return s.Type().String()
|
fmt.Println("--------------AddSource------------")
|
||||||
// }
|
if len(source) == 0 {
|
||||||
|
return
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
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