diff --git a/mvc2/binder.go b/mvc2/binder.go index 1e0b9c00..c4b20648 100644 --- a/mvc2/binder.go +++ b/mvc2/binder.go @@ -10,6 +10,10 @@ import ( // 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 } @@ -27,6 +31,17 @@ func getBindersForInput(binders []*InputBinder, expected ...reflect.Type) map[in 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. if equalTypes(b.BindType, in) { diff --git a/mvc2/handler.go b/mvc2/handler.go index a40293c1..1b15f105 100644 --- a/mvc2/handler.go +++ b/mvc2/handler.go @@ -4,13 +4,21 @@ import ( "fmt" "reflect" + "github.com/kataras/golog" "github.com/kataras/iris/context" + "github.com/kataras/iris/mvc/activator/methodfunc" ) // checks if "handler" is context.Handler; func(context.Context). -func isContextHandler(handler interface{}) bool { - _, is := handler.(context.Handler) - return is +func isContextHandler(handler interface{}) (context.Handler, bool) { + h, is := handler.(context.Handler) + if !is { + fh, is := handler.(func(context.Context)) + if is { + return fh, is + } + } + return h, is } func validateHandler(handler interface{}) error { @@ -19,3 +27,58 @@ func validateHandler(handler interface{}) error { } return nil } + +var ( + contextTyp = reflect.TypeOf(context.NewContext(nil)) + emptyIn = []reflect.Value{} +) + +func makeHandler(handler interface{}, binders []*InputBinder) context.Handler { + if err := validateHandler(handler); err != nil { + golog.Errorf("mvc handler: %v", err) + return nil + } + + if h, is := isContextHandler(handler); is { + golog.Warnf("mvc handler: you could just use the low-level API to register a context handler instead") + return h + } + + typ := indirectTyp(reflect.TypeOf(handler)) + n := typ.NumIn() + typIn := make([]reflect.Type, n, n) + for i := 0; i < n; i++ { + typIn[i] = typ.In(i) + } + + m := getBindersForInput(binders, typIn...) + if len(m) != n { + golog.Errorf("mvc handler: input arguments length(%d) and valid binders length(%d) are not equal", n, len(m)) + return nil + } + + hasIn := len(m) > 0 + fn := reflect.ValueOf(handler) + + return func(ctx context.Context) { + if !hasIn { + methodfunc.DispatchFuncResult(ctx, fn.Call(emptyIn)) + return + } + + // 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. + in := make([]reflect.Value, n, n) + ctxValues := []reflect.Value{reflect.ValueOf(ctx)} + for k, v := range m { + in[k] = v.BindFunc(ctxValues) + } + methodfunc.DispatchFuncResult(ctx, fn.Call(in)) + } +} diff --git a/mvc2/handler_test.go b/mvc2/handler_test.go new file mode 100644 index 00000000..3564426c --- /dev/null +++ b/mvc2/handler_test.go @@ -0,0 +1,11 @@ +package mvc2 + +/* +TODO: + +Test that as we test the rest, with +a fake context, and after move again to +the mvc_test.go which will contain +the overall high-level (black-box) tests. + +*/ diff --git a/mvc2/mvc.go b/mvc2/mvc.go index c9f39118..814d6a7d 100644 --- a/mvc2/mvc.go +++ b/mvc2/mvc.go @@ -2,6 +2,8 @@ package mvc2 import ( "errors" + + "github.com/kataras/iris/context" ) var ( @@ -9,3 +11,39 @@ var ( errBad = errors.New("bad") errAlreadyExists = errors.New("already exists") ) + +type Mvc struct { + binders []*InputBinder +} + +func New() *Mvc { + return new(Mvc) +} + +func (m *Mvc) RegisterBinder(binders ...interface{}) error { + for _, binder := range binders { + b, err := MakeFuncInputBinder(binder) + if err != nil { + return err + } + m.binders = append(m.binders, b) + } + + return nil +} + +func (m *Mvc) RegisterService(services ...interface{}) error { + for _, service := range services { + b, err := MakeServiceInputBinder(service) + if err != nil { + return err + } + m.binders = append(m.binders, b) + } + + return nil +} + +func (m *Mvc) Handler(handler interface{}) context.Handler { + return makeHandler(handler, m.binders) +} diff --git a/mvc2/mvc_test.go b/mvc2/mvc_test.go new file mode 100644 index 00000000..2a611370 --- /dev/null +++ b/mvc2/mvc_test.go @@ -0,0 +1,30 @@ +package mvc2_test + +import ( + "testing" + + "github.com/kataras/iris" + "github.com/kataras/iris/httptest" + "github.com/kataras/iris/mvc2" +) + +var ( + lowLevelHandler = func(ctx iris.Context) { + ctx.Writef("low-level handler") + } +) + +func TestHandler(t *testing.T) { + app := iris.New() + m := mvc2.New() + + // should just return a context.Handler + // without performance penalties. + app.Get("/", m.Handler(lowLevelHandler)) + + e := httptest.New(t, app, httptest.LogLevel("debug")) + // 1 + e.GET("/").Expect().Status(httptest.StatusOK). + Body().Equal("low-level handler") + +}