From 7d5b3d613ef6bb4af54644c9c374d996decbf0f9 Mon Sep 17 00:00:00 2001 From: Hossein Karimy Date: Fri, 24 Aug 2018 16:46:31 -0400 Subject: [PATCH 1/9] Multiple level of inheritance in Context --- router_serve.go | 15 +++++++++++++-- router_setup.go | 29 +++++++++++++++++++---------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/router_serve.go b/router_serve.go index 8f07909..c5f920a 100644 --- a/router_serve.go +++ b/router_serve.go @@ -220,13 +220,24 @@ func contextsFor(contexts []reflect.Value, routers []*Router) []reflect.Value { for i := 1; i < routersLen; i++ { var ctx reflect.Value + fmt.Println(contexts[i-1].String()) if routers[i].contextType == routers[i-1].contextType { ctx = contexts[i-1] } else { ctx = reflect.New(routers[i].contextType) + childCtx := ctx // set the first field to the parent - f := reflect.Indirect(ctx).Field(0) - f.Set(contexts[i-1]) + for { + f := reflect.Indirect(childCtx).Field(0) + if f.Type() != contexts[i-1].Type() && f.Kind() == reflect.Ptr { + childCtx = reflect.New(f.Type().Elem()) + f.Set(childCtx) + continue + } else { + f.Set(contexts[i-1]) + break + } + } } contexts = append(contexts, ctx) } diff --git a/router_setup.go b/router_setup.go index c248dfb..56fea45 100644 --- a/router_setup.go +++ b/router_setup.go @@ -1,6 +1,7 @@ package web import ( + "fmt" "reflect" "strings" ) @@ -257,16 +258,24 @@ func validateContext(ctx interface{}, parentCtxType reflect.Type) { panic("web: Context needs to be a struct type") } - if parentCtxType != nil && parentCtxType != ctxType { - if ctxType.NumField() == 0 { - panic("web: Context needs to have first field be a pointer to parent context") - } - - fldType := ctxType.Field(0).Type - - // Ensure fld is a pointer to parentCtxType - if fldType != reflect.PtrTo(parentCtxType) { - panic("web: Context needs to have first field be a pointer to parent context") + fldType := ctxType + return + if parentCtxType != nil { + for { + if fldType.Kind() == reflect.Ptr { + fldType = fldType.Elem() + } + fmt.Println(fldType.Name()) + // Ensure fld is a pointer to parentCtxType + if fldType == parentCtxType { + break + } + + if fldType.NumField() == 0 { + panic("web: Context needs to have first field be a pointer to parent context") + } + + fldType = fldType.Field(0).Type } } } From 1b5563c93cd892826a1ea3387007976ed21d56a1 Mon Sep 17 00:00:00 2001 From: Hossein Karimy Date: Fri, 24 Aug 2018 16:56:29 -0400 Subject: [PATCH 2/9] dd --- request.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request.go b/request.go index c4366de..09ea210 100644 --- a/request.go +++ b/request.go @@ -1,4 +1,4 @@ -package web +ssspackage web import ( "net/http" From deadd7bf50c26d54a76c3f506b5032e6fdd94a5a Mon Sep 17 00:00:00 2001 From: Hossein Karimy Date: Fri, 24 Aug 2018 17:02:30 -0400 Subject: [PATCH 3/9] Revert "dd" This reverts commit 1b5563c93cd892826a1ea3387007976ed21d56a1. --- request.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/request.go b/request.go index 09ea210..c4366de 100644 --- a/request.go +++ b/request.go @@ -1,4 +1,4 @@ -ssspackage web +package web import ( "net/http" From 133fe779f5e9ceadb73f1f6b5e2aa0ffbb5eda97 Mon Sep 17 00:00:00 2001 From: Hossein Karimy Date: Fri, 24 Aug 2018 17:21:27 -0400 Subject: [PATCH 4/9] validateContext --- router_setup.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/router_setup.go b/router_setup.go index 56fea45..45fc36c 100644 --- a/router_setup.go +++ b/router_setup.go @@ -1,7 +1,6 @@ package web import ( - "fmt" "reflect" "strings" ) @@ -259,14 +258,11 @@ func validateContext(ctx interface{}, parentCtxType reflect.Type) { } fldType := ctxType - return if parentCtxType != nil { for { if fldType.Kind() == reflect.Ptr { fldType = fldType.Elem() } - fmt.Println(fldType.Name()) - // Ensure fld is a pointer to parentCtxType if fldType == parentCtxType { break } @@ -274,7 +270,6 @@ func validateContext(ctx interface{}, parentCtxType reflect.Type) { if fldType.NumField() == 0 { panic("web: Context needs to have first field be a pointer to parent context") } - fldType = fldType.Field(0).Type } } From c84472c004767a120274c38e6f9b1b6ccc0bab6d Mon Sep 17 00:00:00 2001 From: Hossein Karimy Date: Fri, 24 Aug 2018 18:02:20 -0400 Subject: [PATCH 5/9] validateContext --- router_serve.go | 1 - router_setup.go | 24 +++++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/router_serve.go b/router_serve.go index c5f920a..2f1f8b5 100644 --- a/router_serve.go +++ b/router_serve.go @@ -220,7 +220,6 @@ func contextsFor(contexts []reflect.Value, routers []*Router) []reflect.Value { for i := 1; i < routersLen; i++ { var ctx reflect.Value - fmt.Println(contexts[i-1].String()) if routers[i].contextType == routers[i-1].contextType { ctx = contexts[i-1] } else { diff --git a/router_setup.go b/router_setup.go index 45fc36c..242efc9 100644 --- a/router_setup.go +++ b/router_setup.go @@ -253,24 +253,26 @@ func (r *Router) depth() int { func validateContext(ctx interface{}, parentCtxType reflect.Type) { ctxType := reflect.TypeOf(ctx) - if ctxType.Kind() != reflect.Struct { - panic("web: Context needs to be a struct type") - } - - fldType := ctxType if parentCtxType != nil { for { - if fldType.Kind() == reflect.Ptr { - fldType = fldType.Elem() + if ctxType.Kind() == reflect.Ptr { + ctxType = ctxType.Elem() } - if fldType == parentCtxType { + if ctxType.Kind() != reflect.Struct { + if ctxType == reflect.TypeOf(ctx) { + panic("web: Context needs to be a struct type\n " + ctxType.String()) + } else { + panic("web: Context needs to have first field be a pointer to parent context\n" + + "Main Context: " + parentCtxType.String() + " Given Context: " + reflect.TypeOf(ctx).String()) + } + } + if ctxType == parentCtxType { break } - - if fldType.NumField() == 0 { + if ctxType.NumField() == 0 { panic("web: Context needs to have first field be a pointer to parent context") } - fldType = fldType.Field(0).Type + ctxType = ctxType.Field(0).Type } } } From bc1e451a4a41c2b1f70bb2d1d862868000869e08 Mon Sep 17 00:00:00 2001 From: Hossein Karimy Date: Wed, 17 Oct 2018 15:16:53 -0400 Subject: [PATCH 6/9] Supporting Both inheriting directions for context (from Base to derived and derived to base) --- router_serve.go | 46 +++++++++++++++++++++++++++++--------------- router_setup.go | 51 +++++++++++++++++++++++++++++++------------------ 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/router_serve.go b/router_serve.go index 2f1f8b5..30e1aaa 100644 --- a/router_serve.go +++ b/router_serve.go @@ -32,7 +32,7 @@ func (rootRouter *Router) ServeHTTP(rw http.ResponseWriter, r *http.Request) { closure.Routers = make([]*Router, 1, rootRouter.maxChildrenDepth) closure.Routers[0] = rootRouter closure.Contexts = make([]reflect.Value, 1, rootRouter.maxChildrenDepth) - closure.Contexts[0] = reflect.New(rootRouter.contextType) + closure.Contexts[0] = reflect.New(rootRouter.contextType.Type) closure.currentMiddlewareLen = len(rootRouter.middleware) closure.RootRouter = rootRouter closure.Request.rootContext = closure.Contexts[0] @@ -220,21 +220,37 @@ func contextsFor(contexts []reflect.Value, routers []*Router) []reflect.Value { for i := 1; i < routersLen; i++ { var ctx reflect.Value - if routers[i].contextType == routers[i-1].contextType { + if routers[i].contextType.Type == routers[i-1].contextType.Type { ctx = contexts[i-1] } else { - ctx = reflect.New(routers[i].contextType) - childCtx := ctx + ctx = reflect.New(routers[i].contextType.Type) // set the first field to the parent - for { - f := reflect.Indirect(childCtx).Field(0) - if f.Type() != contexts[i-1].Type() && f.Kind() == reflect.Ptr { - childCtx = reflect.New(f.Type().Elem()) - f.Set(childCtx) - continue - } else { - f.Set(contexts[i-1]) - break + if routers[i].contextType.IsDerived { + childCtx := ctx + for { + f := reflect.Indirect(childCtx).Field(0) + if f.Type() != contexts[i-1].Type() && f.Kind() == reflect.Ptr { + childCtx = reflect.New(f.Type().Elem()) + f.Set(childCtx) + continue + } else { + f.Set(contexts[i-1]) + break + } + } + } else { + childCtx := contexts[i-1] + for { + f := reflect.Indirect(childCtx).Field(0) + if f.Type() == ctx.Type() { + if f.Kind() == reflect.Ptr { + f = f.Elem() + } + reflect.Indirect(ctx).Set(f) + break + } + childCtx = f + } } } @@ -263,9 +279,9 @@ func (rootRouter *Router) handlePanic(rw *appResponseWriter, req *Request, err i // Need to set context to the next context, UNLESS the context is the same type. curContextStruct := reflect.Indirect(context) - if targetRouter.contextType != curContextStruct.Type() { + if targetRouter.contextType.Type != curContextStruct.Type() { context = curContextStruct.Field(0) - if reflect.Indirect(context).Type() != targetRouter.contextType { + if reflect.Indirect(context).Type() != targetRouter.contextType.Type { panic("bug: shouldn't get here") } } diff --git a/router_setup.go b/router_setup.go index 242efc9..5793aa2 100644 --- a/router_setup.go +++ b/router_setup.go @@ -1,6 +1,7 @@ package web import ( + "errors" "reflect" "strings" ) @@ -19,6 +20,11 @@ const ( var httpMethods = []httpMethod{httpMethodGet, httpMethodPost, httpMethodPut, httpMethodDelete, httpMethodPatch, httpMethodHead, httpMethodOptions} +type ContextSt struct { + Type reflect.Type + IsDerived bool //true if it's drived from main route, false if main route is drived from it +} + // Router implements net/http's Handler interface and is what you attach middleware, routes/handlers, and subrouters to. type Router struct { // Hierarchy: @@ -27,7 +33,7 @@ type Router struct { maxChildrenDepth int // For each request we'll create one of these objects - contextType reflect.Type + contextType ContextSt // Eg, "/" or "/admin". Any routes added to this router will be prefixed with this. pathPrefix string @@ -89,10 +95,10 @@ var emptyInterfaceType = reflect.TypeOf((*interface{})(nil)).Elem() // whose purpose is to communicate type information. On each request, an instance of this // context type will be automatically allocated and sent to handlers. func New(ctx interface{}) *Router { - validateContext(ctx, nil) + // validateContext(ctx, nil) r := &Router{} - r.contextType = reflect.TypeOf(ctx) + r.contextType = ContextSt{Type: reflect.TypeOf(ctx)} r.pathPrefix = "/" r.maxChildrenDepth = 1 r.root = make(map[httpMethod]*pathNode) @@ -116,10 +122,10 @@ func NewWithPrefix(ctx interface{}, pathPrefix string) *Router { // embed a pointer to the previous context in the first slot. You can also pass // a pathPrefix that each route will have. If "" is passed, then no path prefix is applied. func (r *Router) Subrouter(ctx interface{}, pathPrefix string) *Router { - validateContext(ctx, r.contextType) // Create new router, link up hierarchy newRouter := &Router{parent: r} + newRouter.contextType = validateContext(ctx, r.contextType.Type) r.children = append(r.children, newRouter) // Increment maxChildrenDepth if this is the first child of the router @@ -131,7 +137,6 @@ func (r *Router) Subrouter(ctx interface{}, pathPrefix string) *Router { } } - newRouter.contextType = reflect.TypeOf(ctx) newRouter.pathPrefix = appendPath(r.pathPrefix, pathPrefix) newRouter.root = r.root @@ -141,7 +146,7 @@ func (r *Router) Subrouter(ctx interface{}, pathPrefix string) *Router { // Middleware adds the specified middleware tot he router and returns the router. func (r *Router) Middleware(fn interface{}) *Router { vfn := reflect.ValueOf(fn) - validateMiddleware(vfn, r.contextType) + validateMiddleware(vfn, r.contextType.Type) if vfn.Type().NumIn() == 3 { r.middleware = append(r.middleware, &middlewareHandler{Generic: true, GenericMiddleware: fn.(func(ResponseWriter, *Request, NextMiddlewareFunc))}) } else { @@ -154,7 +159,7 @@ func (r *Router) Middleware(fn interface{}) *Router { // Error sets the specified function as the error handler (when panics happen) and returns the router. func (r *Router) Error(fn interface{}) *Router { vfn := reflect.ValueOf(fn) - validateErrorHandler(vfn, r.contextType) + validateErrorHandler(vfn, r.contextType.Type) r.errorHandler = vfn return r } @@ -166,7 +171,7 @@ func (r *Router) NotFound(fn interface{}) *Router { panic("You can only set a NotFoundHandler on the root router.") } vfn := reflect.ValueOf(fn) - validateNotFoundHandler(vfn, r.contextType) + validateNotFoundHandler(vfn, r.contextType.Type) r.notFoundHandler = vfn return r } @@ -178,7 +183,7 @@ func (r *Router) OptionsHandler(fn interface{}) *Router { panic("You can only set an OptionsHandler on the root router.") } vfn := reflect.ValueOf(fn) - validateOptionsHandler(vfn, r.contextType) + validateOptionsHandler(vfn, r.contextType.Type) r.optionsHandler = vfn return r } @@ -220,7 +225,7 @@ func (r *Router) Options(path string, fn interface{}) *Router { func (r *Router) addRoute(method httpMethod, path string, fn interface{}) *Router { vfn := reflect.ValueOf(fn) - validateHandler(vfn, r.contextType) + validateHandler(vfn, r.contextType.Type) fullPath := appendPath(r.pathPrefix, path) route := &route{Method: method, Path: fullPath, Router: r} if vfn.Type().NumIn() == 2 { @@ -250,31 +255,39 @@ func (r *Router) depth() int { // // Panics unless validation is correct -func validateContext(ctx interface{}, parentCtxType reflect.Type) { - ctxType := reflect.TypeOf(ctx) - - if parentCtxType != nil { +func validateContext(ctx interface{}, parentCtxType reflect.Type) ContextSt { + doCheck := func(ctxType reflect.Type, parentCtxType reflect.Type) error { for { if ctxType.Kind() == reflect.Ptr { ctxType = ctxType.Elem() } if ctxType.Kind() != reflect.Struct { if ctxType == reflect.TypeOf(ctx) { - panic("web: Context needs to be a struct type\n " + ctxType.String()) - } else { - panic("web: Context needs to have first field be a pointer to parent context\n" + - "Main Context: " + parentCtxType.String() + " Given Context: " + reflect.TypeOf(ctx).String()) + return errors.New("web: Context needs to be a struct type\n " + ctxType.String()) } + return errors.New("web: Context needs to have first field be a pointer to parent context\n" + + "Main Context: " + parentCtxType.String() + " Given Context: " + reflect.TypeOf(ctx).String()) + } if ctxType == parentCtxType { break } if ctxType.NumField() == 0 { - panic("web: Context needs to have first field be a pointer to parent context") + return errors.New("web: Context needs to have first field be a pointer to parent context") } ctxType = ctxType.Field(0).Type } + return nil + } + + ctxType := reflect.TypeOf(ctx) + if err1 := doCheck(ctxType, parentCtxType); err1 != nil { + if err2 := doCheck(parentCtxType, ctxType); err2 != nil { + panic(err1) + } + return ContextSt{ctxType, false} } + return ContextSt{ctxType, true} } // Panics unless fn is a proper handler wrt ctxType From 9b3356e6e111818bc15b2136519d0e0f754514f6 Mon Sep 17 00:00:00 2001 From: Hossein Karimy Date: Fri, 2 Nov 2018 01:41:04 -0400 Subject: [PATCH 7/9] ValidateContext returns error fixed isValidHandler to have nested contexts --- router_setup.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/router_setup.go b/router_setup.go index 5793aa2..745cf8d 100644 --- a/router_setup.go +++ b/router_setup.go @@ -125,7 +125,11 @@ func (r *Router) Subrouter(ctx interface{}, pathPrefix string) *Router { // Create new router, link up hierarchy newRouter := &Router{parent: r} - newRouter.contextType = validateContext(ctx, r.contextType.Type) + contextType, err := validateContext(ctx, r.contextType.Type) + if err != nil { + panic(err) + } + newRouter.contextType = *contextType r.children = append(r.children, newRouter) // Increment maxChildrenDepth if this is the first child of the router @@ -255,7 +259,7 @@ func (r *Router) depth() int { // // Panics unless validation is correct -func validateContext(ctx interface{}, parentCtxType reflect.Type) ContextSt { +func validateContext(ctx interface{}, parentCtxType reflect.Type) (*ContextSt, error) { doCheck := func(ctxType reflect.Type, parentCtxType reflect.Type) error { for { if ctxType.Kind() == reflect.Ptr { @@ -283,11 +287,11 @@ func validateContext(ctx interface{}, parentCtxType reflect.Type) ContextSt { ctxType := reflect.TypeOf(ctx) if err1 := doCheck(ctxType, parentCtxType); err1 != nil { if err2 := doCheck(parentCtxType, ctxType); err2 != nil { - panic(err1) + return nil, err1 } - return ContextSt{ctxType, false} + return &ContextSt{ctxType, false}, nil } - return ContextSt{ctxType, true} + return &ContextSt{ctxType, true}, nil } // Panics unless fn is a proper handler wrt ctxType @@ -357,8 +361,10 @@ func isValidHandler(vfn reflect.Value, ctxType reflect.Type, types ...reflect.Ty } else if numIn == (typesLen + 1) { // context, types firstArgType := fnType.In(0) - if firstArgType != reflect.PtrTo(ctxType) && firstArgType != emptyInterfaceType { - return false + if firstArgType != emptyInterfaceType { + if _, err := validateContext(reflect.Indirect(reflect.New(firstArgType.Elem())).Interface(), ctxType); err != nil { + return false + } } typesStartIdx = 1 } else { From 6f5e67d5ec8c64a3d5b48a6a57882503f57eddcb Mon Sep 17 00:00:00 2001 From: Hossein Karimy Date: Fri, 9 Nov 2018 16:33:12 -0500 Subject: [PATCH 8/9] Passing the actual context to supper context instead of creating new one --- router_serve.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/router_serve.go b/router_serve.go index 30e1aaa..b91d33a 100644 --- a/router_serve.go +++ b/router_serve.go @@ -223,9 +223,10 @@ func contextsFor(contexts []reflect.Value, routers []*Router) []reflect.Value { if routers[i].contextType.Type == routers[i-1].contextType.Type { ctx = contexts[i-1] } else { - ctx = reflect.New(routers[i].contextType.Type) + ctxType := routers[i].contextType.Type // set the first field to the parent if routers[i].contextType.IsDerived { + ctx = reflect.New(ctxType) childCtx := ctx for { f := reflect.Indirect(childCtx).Field(0) @@ -240,13 +241,11 @@ func contextsFor(contexts []reflect.Value, routers []*Router) []reflect.Value { } } else { childCtx := contexts[i-1] + ctxType = reflect.PtrTo(ctxType) for { f := reflect.Indirect(childCtx).Field(0) - if f.Type() == ctx.Type() { - if f.Kind() == reflect.Ptr { - f = f.Elem() - } - reflect.Indirect(ctx).Set(f) + if f.Type() == ctxType { + ctx = f break } childCtx = f From 1bb26ebc2b7ec30158465d45e0fc6b0e04c1c609 Mon Sep 17 00:00:00 2001 From: Hossein Karimy Date: Wed, 26 Jun 2019 15:30:40 -0400 Subject: [PATCH 9/9] getting the correct context for the errorHandler --- router_serve.go | 70 +++++++++++++++++++++++++++---------------------- router_setup.go | 2 +- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/router_serve.go b/router_serve.go index b91d33a..5f77fee 100644 --- a/router_serve.go +++ b/router_serve.go @@ -226,31 +226,10 @@ func contextsFor(contexts []reflect.Value, routers []*Router) []reflect.Value { ctxType := routers[i].contextType.Type // set the first field to the parent if routers[i].contextType.IsDerived { - ctx = reflect.New(ctxType) - childCtx := ctx - for { - f := reflect.Indirect(childCtx).Field(0) - if f.Type() != contexts[i-1].Type() && f.Kind() == reflect.Ptr { - childCtx = reflect.New(f.Type().Elem()) - f.Set(childCtx) - continue - } else { - f.Set(contexts[i-1]) - break - } - } + ctx = createDrivedContext(contexts[i-1], ctxType) } else { - childCtx := contexts[i-1] ctxType = reflect.PtrTo(ctxType) - for { - f := reflect.Indirect(childCtx).Field(0) - if f.Type() == ctxType { - ctx = f - break - } - childCtx = f - - } + ctx = getMatchedParentContext(contexts[i-1], ctxType) } } contexts = append(contexts, ctx) @@ -259,6 +238,35 @@ func contextsFor(contexts []reflect.Value, routers []*Router) []reflect.Value { return contexts } +func createDrivedContext(context reflect.Value, neededType reflect.Type) reflect.Value { + ctx := reflect.New(neededType) + childCtx := ctx + for { + f := reflect.Indirect(childCtx).Field(0) + if f.Type() != context.Type() && f.Kind() == reflect.Ptr { + childCtx = reflect.New(f.Type().Elem()) + f.Set(childCtx) + continue + } else { + f.Set(context) + break + } + } + return ctx +} + +func getMatchedParentContext(context reflect.Value, neededType reflect.Type) reflect.Value { + if neededType != context.Type() { + for { + context = reflect.Indirect(context).Field(0) + if context.Type() == neededType { + break + } + } + } + return context +} + // If there's a panic in the root middleware (so that we don't have a route/target), then invoke the root handler or default. // If there's a panic in other middleware, then invoke the target action's function. // If there's a panic in the action handler, then invoke the target action's function. @@ -275,19 +283,17 @@ func (rootRouter *Router) handlePanic(rw *appResponseWriter, req *Request, err i for !targetRouter.errorHandler.IsValid() && targetRouter.parent != nil { targetRouter = targetRouter.parent - - // Need to set context to the next context, UNLESS the context is the same type. - curContextStruct := reflect.Indirect(context) - if targetRouter.contextType.Type != curContextStruct.Type() { - context = curContextStruct.Field(0) - if reflect.Indirect(context).Type() != targetRouter.contextType.Type { - panic("bug: shouldn't get here") - } - } } } if targetRouter.errorHandler.IsValid() { + // Need to set context to the next context, UNLESS the context is the same type. + if _, err := validateContext(reflect.Indirect(reflect.New(targetRouter.contextType.Type)).Interface(), reflect.Indirect(context).Type()); err != nil { + panic(err) + } + + ctxType := reflect.PtrTo(targetRouter.contextType.Type) + context = getMatchedParentContext(context, ctxType) invoke(targetRouter.errorHandler, context, []reflect.Value{reflect.ValueOf(rw), reflect.ValueOf(req), reflect.ValueOf(err)}) } else { http.Error(rw, DefaultPanicResponse, http.StatusInternalServerError) diff --git a/router_setup.go b/router_setup.go index 745cf8d..258ad54 100644 --- a/router_setup.go +++ b/router_setup.go @@ -258,7 +258,7 @@ func (r *Router) depth() int { // Private methods: // -// Panics unless validation is correct +// validate contexts func validateContext(ctx interface{}, parentCtxType reflect.Type) (*ContextSt, error) { doCheck := func(ctxType reflect.Type, parentCtxType reflect.Type) error { for {