made it work but looking for another approach

Former-commit-id: e61c4573543c57b8d6d4ef2583e40f52c391402f
This commit is contained in:
kataras 2017-12-04 05:06:03 +02:00
parent dd5de52f34
commit 7043f352d9
20 changed files with 855 additions and 792 deletions

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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),

View File

@ -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.
//

View File

@ -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
View File

@ -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:

View File

@ -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
}

View File

@ -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 ""
}

View File

@ -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
}

View File

@ -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())
}

View File

@ -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
}
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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}
}
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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{}{}))
}