diff --git a/router.go b/router.go index 4c3898c46..749dbf4f6 100644 --- a/router.go +++ b/router.go @@ -14,14 +14,16 @@ type ( echo *Echo } node struct { - kind kind - label byte - prefix string - parent *node - children children - ppath string - pnames []string - methodHandler *methodHandler + kind kind + label byte + prefix string + parent *node + staticChildrens children + ppath string + pnames []string + methodHandler *methodHandler + paramChildren *node + anyChildren *node } kind uint8 children []*node @@ -44,6 +46,9 @@ const ( skind kind = iota pkind akind + + paramLabel = byte(':') + anyLabel = byte('*') ) // NewRouter returns a new Router instance. @@ -134,23 +139,32 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string } } else if l < pl { // Split node - n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames) + n := newNode(cn.kind, cn.prefix[l:], cn, cn.staticChildrens, cn.methodHandler, cn.ppath, cn.pnames, cn.paramChildren, cn.anyChildren) // Update parent path for all children to new node - for _, child := range cn.children { + for _, child := range cn.staticChildrens { child.parent = n } + if cn.paramChildren != nil { + cn.paramChildren.parent = n + } + if cn.anyChildren != nil { + cn.anyChildren.parent = n + } // Reset parent node cn.kind = skind cn.label = cn.prefix[0] cn.prefix = cn.prefix[:l] - cn.children = nil + cn.staticChildrens = nil cn.methodHandler = new(methodHandler) cn.ppath = "" cn.pnames = nil + cn.paramChildren = nil + cn.anyChildren = nil - cn.addChild(n) + // Only Static children could reach here + cn.addStaticChild(n) if l == sl { // At parent node @@ -160,9 +174,10 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string cn.pnames = pnames } else { // Create child node - n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames) + n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames, nil, nil) n.addHandler(method, h) - cn.addChild(n) + // Only Static children could reach here + cn.addStaticChild(n) } } else if l < sl { search = search[l:] @@ -173,9 +188,16 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string continue } // Create child node - n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames) + n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames, nil, nil) n.addHandler(method, h) - cn.addChild(n) + switch t { + case skind: + cn.addStaticChild(n) + case pkind: + cn.paramChildren = n + case akind: + cn.anyChildren = n + } } else { // Node already exists if h != nil { @@ -190,26 +212,28 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string } } -func newNode(t kind, pre string, p *node, c children, mh *methodHandler, ppath string, pnames []string) *node { +func newNode(t kind, pre string, p *node, sc children, mh *methodHandler, ppath string, pnames []string, paramChildren, anyChildren *node) *node { return &node{ - kind: t, - label: pre[0], - prefix: pre, - parent: p, - children: c, - ppath: ppath, - pnames: pnames, - methodHandler: mh, + kind: t, + label: pre[0], + prefix: pre, + parent: p, + staticChildrens: sc, + ppath: ppath, + pnames: pnames, + methodHandler: mh, + paramChildren: paramChildren, + anyChildren: anyChildren, } } -func (n *node) addChild(c *node) { - n.children = append(n.children, c) +func (n *node) addStaticChild(c *node) { + n.staticChildrens = append(n.staticChildrens, c) } -func (n *node) findChild(l byte, t kind) *node { - for _, c := range n.children { - if c.label == l && c.kind == t { +func (n *node) findStaticChild(l byte) *node { + for _, c := range n.staticChildrens { + if c.label == l { return c } } @@ -217,19 +241,16 @@ func (n *node) findChild(l byte, t kind) *node { } func (n *node) findChildWithLabel(l byte) *node { - for _, c := range n.children { + for _, c := range n.staticChildrens { if c.label == l { return c } } - return nil -} - -func (n *node) findChildByKind(t kind) *node { - for _, c := range n.children { - if c.kind == t { - return c - } + if l == paramLabel { + return n.paramChildren + } + if l == anyLabel { + return n.anyChildren } return nil } @@ -356,7 +377,7 @@ func (r *Router) Find(method, path string, c Context) { // Attempt to go back up the tree on no matching prefix or no remaining search if l != pl || search == "" { // Handle special case of trailing slash route with existing any route (see #1526) - if path[len(path)-1] == '/' && cn.findChildByKind(akind) != nil { + if path[len(path)-1] == '/' && cn.anyChildren != nil { goto Any } if nn == nil { // Issue #1348 @@ -372,7 +393,7 @@ func (r *Router) Find(method, path string, c Context) { } // Static node - if child = cn.findChild(search[0], skind); child != nil { + if child = cn.findStaticChild(search[0]); child != nil { // Save next if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623 nk = pkind @@ -385,7 +406,7 @@ func (r *Router) Find(method, path string, c Context) { Param: // Param node - if child = cn.findChildByKind(pkind); child != nil { + if child = cn.paramChildren; child != nil { // Issue #378 if len(pvalues) == n { continue @@ -410,7 +431,7 @@ func (r *Router) Find(method, path string, c Context) { Any: // Any node - if cn = cn.findChildByKind(akind); cn != nil { + if cn = cn.anyChildren; cn != nil { // If any node is found, use remaining path for pvalues pvalues[len(cn.pnames)-1] = search break @@ -424,7 +445,7 @@ func (r *Router) Find(method, path string, c Context) { search = ns np := nn.parent // Consider param route one level up only - if cn = nn.findChildByKind(pkind); cn != nil { + if cn = nn.paramChildren; cn != nil { pos := strings.IndexByte(ns, '/') if pos == -1 { // If no slash is remaining in search string set param value @@ -443,7 +464,7 @@ func (r *Router) Find(method, path string, c Context) { // No param route found, try to resolve nearest any route for { np = nn.parent - if cn = nn.findChildByKind(akind); cn != nil { + if cn = nn.anyChildren; cn != nil { break } if np == nil { @@ -474,7 +495,7 @@ func (r *Router) Find(method, path string, c Context) { // Dig further for any, might have an empty value for *, e.g. // serving a directory. Issue #207. - if cn = cn.findChildByKind(akind); cn == nil { + if cn = cn.anyChildren; cn == nil { return } if h := cn.findHandler(method); h != nil { diff --git a/router_test.go b/router_test.go index fca3a79bb..36b681a12 100644 --- a/router_test.go +++ b/router_test.go @@ -175,8 +175,10 @@ var ( {"GET", "/authorizations", ""}, {"GET", "/authorizations/:id", ""}, {"POST", "/authorizations", ""}, - //{"PUT", "/authorizations/clients/:client_id", ""}, - //{"PATCH", "/authorizations/:id", ""}, + + {"PUT", "/authorizations/clients/:client_id", ""}, + {"PATCH", "/authorizations/:id", ""}, + {"DELETE", "/authorizations/:id", ""}, {"GET", "/applications/:client_id/tokens/:access_token", ""}, {"DELETE", "/applications/:client_id/tokens", ""}, @@ -198,7 +200,9 @@ var ( {"PUT", "/notifications", ""}, {"PUT", "/repos/:owner/:repo/notifications", ""}, {"GET", "/notifications/threads/:id", ""}, - //{"PATCH", "/notifications/threads/:id", ""}, + + {"PATCH", "/notifications/threads/:id", ""}, + {"GET", "/notifications/threads/:id/subscription", ""}, {"PUT", "/notifications/threads/:id/subscription", ""}, {"DELETE", "/notifications/threads/:id/subscription", ""}, @@ -221,11 +225,15 @@ var ( // Gists {"GET", "/users/:user/gists", ""}, {"GET", "/gists", ""}, - //{"GET", "/gists/public", ""}, - //{"GET", "/gists/starred", ""}, + + {"GET", "/gists/public", ""}, + {"GET", "/gists/starred", ""}, + {"GET", "/gists/:id", ""}, {"POST", "/gists", ""}, - //{"PATCH", "/gists/:id", ""}, + + {"PATCH", "/gists/:id", ""}, + {"PUT", "/gists/:id/star", ""}, {"DELETE", "/gists/:id/star", ""}, {"GET", "/gists/:id/star", ""}, @@ -237,11 +245,15 @@ var ( {"POST", "/repos/:owner/:repo/git/blobs", ""}, {"GET", "/repos/:owner/:repo/git/commits/:sha", ""}, {"POST", "/repos/:owner/:repo/git/commits", ""}, - //{"GET", "/repos/:owner/:repo/git/refs/*ref", ""}, + + {"GET", "/repos/:owner/:repo/git/refs/*ref", ""}, + {"GET", "/repos/:owner/:repo/git/refs", ""}, {"POST", "/repos/:owner/:repo/git/refs", ""}, - //{"PATCH", "/repos/:owner/:repo/git/refs/*ref", ""}, - //{"DELETE", "/repos/:owner/:repo/git/refs/*ref", ""}, + + {"PATCH", "/repos/:owner/:repo/git/refs/*ref", ""}, + {"DELETE", "/repos/:owner/:repo/git/refs/*ref", ""}, + {"GET", "/repos/:owner/:repo/git/tags/:sha", ""}, {"POST", "/repos/:owner/:repo/git/tags", ""}, {"GET", "/repos/:owner/:repo/git/trees/:sha", ""}, @@ -254,22 +266,32 @@ var ( {"GET", "/repos/:owner/:repo/issues", ""}, {"GET", "/repos/:owner/:repo/issues/:number", ""}, {"POST", "/repos/:owner/:repo/issues", ""}, - //{"PATCH", "/repos/:owner/:repo/issues/:number", ""}, + + {"PATCH", "/repos/:owner/:repo/issues/:number", ""}, + {"GET", "/repos/:owner/:repo/assignees", ""}, {"GET", "/repos/:owner/:repo/assignees/:assignee", ""}, {"GET", "/repos/:owner/:repo/issues/:number/comments", ""}, - //{"GET", "/repos/:owner/:repo/issues/comments", ""}, - //{"GET", "/repos/:owner/:repo/issues/comments/:id", ""}, + + {"GET", "/repos/:owner/:repo/issues/comments", ""}, + {"GET", "/repos/:owner/:repo/issues/comments/:id", ""}, + {"POST", "/repos/:owner/:repo/issues/:number/comments", ""}, - //{"PATCH", "/repos/:owner/:repo/issues/comments/:id", ""}, - //{"DELETE", "/repos/:owner/:repo/issues/comments/:id", ""}, + + {"PATCH", "/repos/:owner/:repo/issues/comments/:id", ""}, + {"DELETE", "/repos/:owner/:repo/issues/comments/:id", ""}, + {"GET", "/repos/:owner/:repo/issues/:number/events", ""}, - //{"GET", "/repos/:owner/:repo/issues/events", ""}, - //{"GET", "/repos/:owner/:repo/issues/events/:id", ""}, + + {"GET", "/repos/:owner/:repo/issues/events", ""}, + {"GET", "/repos/:owner/:repo/issues/events/:id", ""}, + {"GET", "/repos/:owner/:repo/labels", ""}, {"GET", "/repos/:owner/:repo/labels/:name", ""}, {"POST", "/repos/:owner/:repo/labels", ""}, - //{"PATCH", "/repos/:owner/:repo/labels/:name", ""}, + + {"PATCH", "/repos/:owner/:repo/labels/:name", ""}, + {"DELETE", "/repos/:owner/:repo/labels/:name", ""}, {"GET", "/repos/:owner/:repo/issues/:number/labels", ""}, {"POST", "/repos/:owner/:repo/issues/:number/labels", ""}, @@ -280,7 +302,9 @@ var ( {"GET", "/repos/:owner/:repo/milestones", ""}, {"GET", "/repos/:owner/:repo/milestones/:number", ""}, {"POST", "/repos/:owner/:repo/milestones", ""}, - //{"PATCH", "/repos/:owner/:repo/milestones/:number", ""}, + + {"PATCH", "/repos/:owner/:repo/milestones/:number", ""}, + {"DELETE", "/repos/:owner/:repo/milestones/:number", ""}, // Miscellaneous @@ -296,7 +320,9 @@ var ( {"GET", "/users/:user/orgs", ""}, {"GET", "/user/orgs", ""}, {"GET", "/orgs/:org", ""}, - //{"PATCH", "/orgs/:org", ""}, + + {"PATCH", "/orgs/:org", ""}, + {"GET", "/orgs/:org/members", ""}, {"GET", "/orgs/:org/members/:user", ""}, {"DELETE", "/orgs/:org/members/:user", ""}, @@ -307,7 +333,9 @@ var ( {"GET", "/orgs/:org/teams", ""}, {"GET", "/teams/:id", ""}, {"POST", "/orgs/:org/teams", ""}, - //{"PATCH", "/teams/:id", ""}, + + {"PATCH", "/teams/:id", ""}, + {"DELETE", "/teams/:id", ""}, {"GET", "/teams/:id/members", ""}, {"GET", "/teams/:id/members/:user", ""}, @@ -323,17 +351,22 @@ var ( {"GET", "/repos/:owner/:repo/pulls", ""}, {"GET", "/repos/:owner/:repo/pulls/:number", ""}, {"POST", "/repos/:owner/:repo/pulls", ""}, - //{"PATCH", "/repos/:owner/:repo/pulls/:number", ""}, + + {"PATCH", "/repos/:owner/:repo/pulls/:number", ""}, + {"GET", "/repos/:owner/:repo/pulls/:number/commits", ""}, {"GET", "/repos/:owner/:repo/pulls/:number/files", ""}, {"GET", "/repos/:owner/:repo/pulls/:number/merge", ""}, {"PUT", "/repos/:owner/:repo/pulls/:number/merge", ""}, {"GET", "/repos/:owner/:repo/pulls/:number/comments", ""}, - //{"GET", "/repos/:owner/:repo/pulls/comments", ""}, - //{"GET", "/repos/:owner/:repo/pulls/comments/:number", ""}, + + {"GET", "/repos/:owner/:repo/pulls/comments", ""}, + {"GET", "/repos/:owner/:repo/pulls/comments/:number", ""}, + {"PUT", "/repos/:owner/:repo/pulls/:number/comments", ""}, - //{"PATCH", "/repos/:owner/:repo/pulls/comments/:number", ""}, - //{"DELETE", "/repos/:owner/:repo/pulls/comments/:number", ""}, + + {"PATCH", "/repos/:owner/:repo/pulls/comments/:number", ""}, + {"DELETE", "/repos/:owner/:repo/pulls/comments/:number", ""}, // Repositories {"GET", "/user/repos", ""}, @@ -343,7 +376,9 @@ var ( {"POST", "/user/repos", ""}, {"POST", "/orgs/:org/repos", ""}, {"GET", "/repos/:owner/:repo", ""}, - //{"PATCH", "/repos/:owner/:repo", ""}, + + {"PATCH", "/repos/:owner/:repo", ""}, + {"GET", "/repos/:owner/:repo/contributors", ""}, {"GET", "/repos/:owner/:repo/languages", ""}, {"GET", "/repos/:owner/:repo/teams", ""}, @@ -359,19 +394,26 @@ var ( {"GET", "/repos/:owner/:repo/commits/:sha/comments", ""}, {"POST", "/repos/:owner/:repo/commits/:sha/comments", ""}, {"GET", "/repos/:owner/:repo/comments/:id", ""}, - //{"PATCH", "/repos/:owner/:repo/comments/:id", ""}, + + {"PATCH", "/repos/:owner/:repo/comments/:id", ""}, + {"DELETE", "/repos/:owner/:repo/comments/:id", ""}, {"GET", "/repos/:owner/:repo/commits", ""}, {"GET", "/repos/:owner/:repo/commits/:sha", ""}, {"GET", "/repos/:owner/:repo/readme", ""}, + //{"GET", "/repos/:owner/:repo/contents/*path", ""}, //{"PUT", "/repos/:owner/:repo/contents/*path", ""}, //{"DELETE", "/repos/:owner/:repo/contents/*path", ""}, - //{"GET", "/repos/:owner/:repo/:archive_format/:ref", ""}, + + {"GET", "/repos/:owner/:repo/:archive_format/:ref", ""}, + {"GET", "/repos/:owner/:repo/keys", ""}, {"GET", "/repos/:owner/:repo/keys/:id", ""}, {"POST", "/repos/:owner/:repo/keys", ""}, - //{"PATCH", "/repos/:owner/:repo/keys/:id", ""}, + + {"PATCH", "/repos/:owner/:repo/keys/:id", ""}, + {"DELETE", "/repos/:owner/:repo/keys/:id", ""}, {"GET", "/repos/:owner/:repo/downloads", ""}, {"GET", "/repos/:owner/:repo/downloads/:id", ""}, @@ -381,14 +423,18 @@ var ( {"GET", "/repos/:owner/:repo/hooks", ""}, {"GET", "/repos/:owner/:repo/hooks/:id", ""}, {"POST", "/repos/:owner/:repo/hooks", ""}, - //{"PATCH", "/repos/:owner/:repo/hooks/:id", ""}, + + {"PATCH", "/repos/:owner/:repo/hooks/:id", ""}, + {"POST", "/repos/:owner/:repo/hooks/:id/tests", ""}, {"DELETE", "/repos/:owner/:repo/hooks/:id", ""}, {"POST", "/repos/:owner/:repo/merges", ""}, {"GET", "/repos/:owner/:repo/releases", ""}, {"GET", "/repos/:owner/:repo/releases/:id", ""}, {"POST", "/repos/:owner/:repo/releases", ""}, - //{"PATCH", "/repos/:owner/:repo/releases/:id", ""}, + + {"PATCH", "/repos/:owner/:repo/releases/:id", ""}, + {"DELETE", "/repos/:owner/:repo/releases/:id", ""}, {"GET", "/repos/:owner/:repo/releases/:id/assets", ""}, {"GET", "/repos/:owner/:repo/stats/contributors", ""}, @@ -412,7 +458,9 @@ var ( // Users {"GET", "/users/:user", ""}, {"GET", "/user", ""}, - //{"PATCH", "/user", ""}, + + {"PATCH", "/user", ""}, + {"GET", "/users", ""}, {"GET", "/user/emails", ""}, {"POST", "/user/emails", ""}, @@ -429,7 +477,9 @@ var ( {"GET", "/user/keys", ""}, {"GET", "/user/keys/:id", ""}, {"POST", "/user/keys", ""}, - //{"PATCH", "/user/keys/:id", ""}, + + {"PATCH", "/user/keys/:id", ""}, + {"DELETE", "/user/keys/:id", ""}, } @@ -500,6 +550,88 @@ var ( {"DELETE", "/moments/:id", ""}, } + paramAndAnyAPI = []*Route{ + {"GET", "/root/:first/foo/*", ""}, + {"GET", "/root/:first/:second/*", ""}, + {"GET", "/root/:first/bar/:second/*", ""}, + {"GET", "/root/:first/qux/:second/:third/:fourth", ""}, + {"GET", "/root/:first/qux/:second/:third/:fourth/*", ""}, + {"GET", "/root/*", ""}, + + {"POST", "/root/:first/foo/*", ""}, + {"POST", "/root/:first/:second/*", ""}, + {"POST", "/root/:first/bar/:second/*", ""}, + {"POST", "/root/:first/qux/:second/:third/:fourth", ""}, + {"POST", "/root/:first/qux/:second/:third/:fourth/*", ""}, + {"POST", "/root/*", ""}, + + {"PUT", "/root/:first/foo/*", ""}, + {"PUT", "/root/:first/:second/*", ""}, + {"PUT", "/root/:first/bar/:second/*", ""}, + {"PUT", "/root/:first/qux/:second/:third/:fourth", ""}, + {"PUT", "/root/:first/qux/:second/:third/:fourth/*", ""}, + {"PUT", "/root/*", ""}, + + {"DELETE", "/root/:first/foo/*", ""}, + {"DELETE", "/root/:first/:second/*", ""}, + {"DELETE", "/root/:first/bar/:second/*", ""}, + {"DELETE", "/root/:first/qux/:second/:third/:fourth", ""}, + {"DELETE", "/root/:first/qux/:second/:third/:fourth/*", ""}, + {"DELETE", "/root/*", ""}, + } + + paramAndAnyAPIToFind = []*Route{ + {"GET", "/root/one/foo/after/the/asterisk", ""}, + {"GET", "/root/one/foo/path/after/the/asterisk", ""}, + {"GET", "/root/one/two/path/after/the/asterisk", ""}, + {"GET", "/root/one/bar/two/after/the/asterisk", ""}, + {"GET", "/root/one/qux/two/three/four", ""}, + {"GET", "/root/one/qux/two/three/four/after/the/asterisk", ""}, + + {"POST", "/root/one/foo/after/the/asterisk", ""}, + {"POST", "/root/one/foo/path/after/the/asterisk", ""}, + {"POST", "/root/one/two/path/after/the/asterisk", ""}, + {"POST", "/root/one/bar/two/after/the/asterisk", ""}, + {"POST", "/root/one/qux/two/three/four", ""}, + {"POST", "/root/one/qux/two/three/four/after/the/asterisk", ""}, + + {"PUT", "/root/one/foo/after/the/asterisk", ""}, + {"PUT", "/root/one/foo/path/after/the/asterisk", ""}, + {"PUT", "/root/one/two/path/after/the/asterisk", ""}, + {"PUT", "/root/one/bar/two/after/the/asterisk", ""}, + {"PUT", "/root/one/qux/two/three/four", ""}, + {"PUT", "/root/one/qux/two/three/four/after/the/asterisk", ""}, + + {"DELETE", "/root/one/foo/after/the/asterisk", ""}, + {"DELETE", "/root/one/foo/path/after/the/asterisk", ""}, + {"DELETE", "/root/one/two/path/after/the/asterisk", ""}, + {"DELETE", "/root/one/bar/two/after/the/asterisk", ""}, + {"DELETE", "/root/one/qux/two/three/four", ""}, + {"DELETE", "/root/one/qux/two/three/four/after/the/asterisk", ""}, + } + + missesAPI = []*Route{ + {"GET", "/missOne", ""}, + {"GET", "/miss/two", ""}, + {"GET", "/miss/three/levels", ""}, + {"GET", "/miss/four/levels/nooo", ""}, + + {"POST", "/missOne", ""}, + {"POST", "/miss/two", ""}, + {"POST", "/miss/three/levels", ""}, + {"POST", "/miss/four/levels/nooo", ""}, + + {"PUT", "/missOne", ""}, + {"PUT", "/miss/two", ""}, + {"PUT", "/miss/three/levels", ""}, + {"PUT", "/miss/four/levels/nooo", ""}, + + {"DELETE", "/missOne", ""}, + {"DELETE", "/miss/two", ""}, + {"DELETE", "/miss/three/levels", ""}, + {"DELETE", "/miss/four/levels/nooo", ""}, + } + // handlerHelper created a function that will set a context key for assertion handlerHelper = func(key string, value int) func(c Context) error { return func(c Context) error { @@ -1332,7 +1464,7 @@ func TestRouterPanicWhenParamNoRootOnlyChildsFailsFind(t *testing.T) { assert.Equal(t, http.StatusNotFound, he.Code) } -func benchmarkRouterRoutes(b *testing.B, routes []*Route) { +func benchmarkRouterRoutes(b *testing.B, routes []*Route, routesToFind []*Route) { e := New() r := e.router b.ReportAllocs() @@ -1344,9 +1476,12 @@ func benchmarkRouterRoutes(b *testing.B, routes []*Route) { }) } + // Routes adding are performed just once, so it doesn't make sense to see that in the benchmark + b.ResetTimer() + // Find routes for i := 0; i < b.N; i++ { - for _, route := range gitHubAPI { + for _, route := range routesToFind { c := e.pool.Get().(*context) r.Find(route.Method, route.Path, c) e.pool.Put(c) @@ -1355,28 +1490,56 @@ func benchmarkRouterRoutes(b *testing.B, routes []*Route) { } func BenchmarkRouterStaticRoutes(b *testing.B) { - benchmarkRouterRoutes(b, staticRoutes) + benchmarkRouterRoutes(b, staticRoutes, staticRoutes) +} + +func BenchmarkRouterStaticRoutesMisses(b *testing.B) { + benchmarkRouterRoutes(b, staticRoutes, missesAPI) } func BenchmarkRouterGitHubAPI(b *testing.B) { - benchmarkRouterRoutes(b, gitHubAPI) + benchmarkRouterRoutes(b, gitHubAPI, gitHubAPI) +} + +func BenchmarkRouterGitHubAPIMisses(b *testing.B) { + benchmarkRouterRoutes(b, gitHubAPI, missesAPI) } func BenchmarkRouterParseAPI(b *testing.B) { - benchmarkRouterRoutes(b, parseAPI) + benchmarkRouterRoutes(b, parseAPI, parseAPI) +} + +func BenchmarkRouterParseAPIMisses(b *testing.B) { + benchmarkRouterRoutes(b, parseAPI, missesAPI) } func BenchmarkRouterGooglePlusAPI(b *testing.B) { - benchmarkRouterRoutes(b, googlePlusAPI) + benchmarkRouterRoutes(b, googlePlusAPI, googlePlusAPI) +} + +func BenchmarkRouterGooglePlusAPIMisses(b *testing.B) { + benchmarkRouterRoutes(b, googlePlusAPI, missesAPI) +} + +func BenchmarkRouterParamsAndAnyAPI(b *testing.B) { + benchmarkRouterRoutes(b, paramAndAnyAPI, paramAndAnyAPIToFind) } func (n *node) printTree(pfx string, tail bool) { p := prefix(tail, pfx, "└── ", "├── ") fmt.Printf("%s%s, %p: type=%d, parent=%p, handler=%v, pnames=%v\n", p, n.prefix, n, n.kind, n.parent, n.methodHandler, n.pnames) - children := n.children - l := len(children) p = prefix(tail, pfx, " ", "│ ") + + children := n.staticChildrens + l := len(children) + + if n.paramChildren != nil { + n.paramChildren.printTree(p, n.anyChildren == nil && l == 0) + } + if n.anyChildren != nil { + n.anyChildren.printTree(p, l == 0) + } for i := 0; i < l-1; i++ { children[i].printTree(p, false) }