Skip to content

Commit

Permalink
Merge pull request #42 from bmf-san/fix/bugs-in-trie
Browse files Browse the repository at this point in the history
Fix bugs in trie
  • Loading branch information
bmf-san authored May 16, 2021
2 parents 7b89892 + 95cb606 commit 0afcfcd
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 58 deletions.
58 changes: 32 additions & 26 deletions trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,35 +63,41 @@ func NewTree() *Tree {
func (t *Tree) Insert(methods []string, path string, handler http.Handler, mws middlewares) error {
curNode := t.node

for _, method := range methods {
if path == pathRoot {
curNode.label = path
// For root node.
if path == pathRoot {
curNode.label = path
for _, method := range methods {
curNode.actions[method] = handler
curNode.middlewares = mws
return nil
}
curNode.middlewares = mws
}

for _, l := range deleteEmpty(strings.Split(path, pathDelimiter)) {
if nextNode, ok := curNode.children[l]; ok {
curNode = nextNode
} else {
curNode.children[l] = &Node{
label: l,
actions: make(map[string]http.Handler),
middlewares: mws,
children: make(map[string]*Node),
}
curNode.children[l].actions[method] = handler

curNode = curNode.children[l]
ep := explodePath(strings.Split(path, pathDelimiter))
for i, l := range ep {
nextNode, ok := curNode.children[l]
if ok {
curNode = nextNode
}
// Create a new node.
if !ok {
curNode.children[l] = &Node{
label: l,
actions: make(map[string]http.Handler),
middlewares: nil,
children: make(map[string]*Node),
}
curNode = curNode.children[l]
}
// last loop.
if i == len(ep)-1 {
curNode.label = l
for _, method := range methods {
curNode.actions[method] = handler
}
curNode.middlewares = mws
}
curNode.label = path
curNode.actions[method] = handler
curNode.middlewares = mws

return nil
}

return nil
}

Expand Down Expand Up @@ -126,7 +132,7 @@ func (t *Tree) Search(method string, path string) (*Result, error) {

n := t.node

label := deleteEmpty(strings.Split(path, pathDelimiter))
label := explodePath(strings.Split(path, pathDelimiter))
curNode := n

for _, l := range label {
Expand Down Expand Up @@ -215,8 +221,8 @@ func getParamName(label string) string {
return label[leftI+1 : rightI]
}

// deleteEmpty removes an empty value in slice.
func deleteEmpty(s []string) []string {
// explodePath removes an empty value in slice.
func explodePath(s []string) []string {
var r []string
for _, str := range s {
if str != "" {
Expand Down
178 changes: 146 additions & 32 deletions trie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,25 +100,25 @@ func TestSearchAllMethod(t *testing.T) {
tree := NewTree()

rootGetHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
fooGetHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
rootPostHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
fooPostHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
rootPutHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
fooPutHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
rootPatchHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
fooPatchHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
rootDeleteHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
fooGetHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
fooPostHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
fooPutHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
fooPatchHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
fooDeleteHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})

tree.Insert([]string{http.MethodGet}, "/", rootGetHandler, []middleware{first})
tree.Insert([]string{http.MethodGet}, "/foo", fooGetHandler, []middleware{first})
tree.Insert([]string{http.MethodPost}, "/", rootPostHandler, []middleware{first})
tree.Insert([]string{http.MethodPost}, "/foo", fooPostHandler, []middleware{first})
tree.Insert([]string{http.MethodPut}, "/", rootPutHandler, []middleware{first})
tree.Insert([]string{http.MethodPut}, "/foo", fooPutHandler, []middleware{first})
tree.Insert([]string{http.MethodPatch}, `/`, rootPatchHandler, []middleware{first})
tree.Insert([]string{http.MethodPatch}, `/foo`, fooPatchHandler, []middleware{first})
tree.Insert([]string{http.MethodDelete}, `/`, rootDeleteHandler, []middleware{first})
tree.Insert([]string{http.MethodGet}, "/foo", fooGetHandler, []middleware{first})
tree.Insert([]string{http.MethodPost}, "/foo", fooPostHandler, []middleware{first})
tree.Insert([]string{http.MethodPut}, "/foo", fooPutHandler, []middleware{first})
tree.Insert([]string{http.MethodPatch}, `/foo`, fooPatchHandler, []middleware{first})
tree.Insert([]string{http.MethodDelete}, `/foo`, fooDeleteHandler, []middleware{first})

cases := []struct {
Expand All @@ -138,66 +138,77 @@ func TestSearchAllMethod(t *testing.T) {
},
{
item: &Item{
method: http.MethodGet,
path: "/foo",
method: http.MethodPost,
path: "/",
},
expected: &Result{
handler: fooGetHandler,
handler: rootPostHandler,
params: Params{},
middlewares: []middleware{first},
},
},
{
item: &Item{
method: http.MethodPost,
method: http.MethodPut,
path: "/",
},
expected: &Result{
handler: rootPostHandler,
handler: rootPutHandler,
params: Params{},
middlewares: []middleware{first},
},
},
{
item: &Item{
method: http.MethodPost,
path: "/foo",
method: http.MethodPatch,
path: "/",
},
expected: &Result{
handler: fooPostHandler,
handler: rootPatchHandler,
params: Params{},
middlewares: []middleware{first},
},
},
{
item: &Item{
method: http.MethodPut,
method: http.MethodDelete,
path: "/",
},
expected: &Result{
handler: rootPutHandler,
handler: rootDeleteHandler,
params: Params{},
middlewares: []middleware{first},
},
},
{
item: &Item{
method: http.MethodPut,
method: http.MethodGet,
path: "/foo",
},
expected: &Result{
handler: fooPutHandler,
handler: fooGetHandler,
params: Params{},
middlewares: []middleware{first},
},
},
{
item: &Item{
method: http.MethodPatch,
path: "/",
method: http.MethodPost,
path: "/foo",
},
expected: &Result{
handler: rootPatchHandler,
handler: fooPostHandler,
params: Params{},
middlewares: []middleware{first},
},
},
{
item: &Item{
method: http.MethodPut,
path: "/foo",
},
expected: &Result{
handler: fooPutHandler,
params: Params{},
middlewares: []middleware{first},
},
Expand All @@ -216,21 +227,125 @@ func TestSearchAllMethod(t *testing.T) {
{
item: &Item{
method: http.MethodDelete,
path: "/foo",
},
expected: &Result{
handler: fooDeleteHandler,
params: Params{},
middlewares: []middleware{first},
},
},
}

for _, c := range cases {
actual, err := tree.Search(c.item.method, c.item.path)
if err != nil {
t.Errorf("err: %v actual: %v expected: %v\n", err, actual, c.expected)
}

if reflect.ValueOf(actual.handler) != reflect.ValueOf(c.expected.handler) {
t.Errorf("actual:%v expected:%v", actual.handler, c.expected.handler)
}

if len(actual.params) != len(c.expected.params) {
t.Errorf("actual: %v expected: %v\n", len(actual.params), len(c.expected.params))
}

for i, param := range actual.params {
if !reflect.DeepEqual(param, c.expected.params[i]) {
t.Errorf("actual: %v expected: %v\n", param, c.expected.params[i])
}
}

if len(actual.middlewares) != len(c.expected.middlewares) {
t.Errorf("actual: %v expected: %v\n", len(actual.middlewares), len(c.expected.middlewares))
}

for i, mws := range actual.middlewares {
if reflect.ValueOf(mws) != reflect.ValueOf(c.expected.middlewares[i]) {
t.Errorf("actual: %v expected: %v\n", mws, c.expected.middlewares[i])
}
}
}
}

func TestSearchPathCommon(t *testing.T) {
tree := NewTree()

rootHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
fooHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
fooBarHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})

tree.Insert([]string{http.MethodGet}, "/", rootHandler, []middleware{first})
tree.Insert([]string{http.MethodGet, http.MethodPost}, "/foo", fooHandler, []middleware{first})
tree.Insert([]string{http.MethodGet, http.MethodPost, http.MethodDelete}, "/foo/bar", fooBarHandler, []middleware{first})

cases := []struct {
item *Item
expected *Result
}{
{
item: &Item{
method: http.MethodGet,
path: "/",
},
expected: &Result{
handler: rootDeleteHandler,
handler: rootHandler,
params: Params{},
middlewares: []middleware{first},
},
},
{
item: &Item{
method: http.MethodDelete,
method: http.MethodGet,
path: "/foo",
},
expected: &Result{
handler: fooDeleteHandler,
handler: fooHandler,
params: Params{},
middlewares: []middleware{first},
},
},
{
item: &Item{
method: http.MethodPost,
path: "/foo",
},
expected: &Result{
handler: fooHandler,
params: Params{},
middlewares: []middleware{first},
},
},
{
item: &Item{
method: http.MethodGet,
path: "/foo/bar",
},
expected: &Result{
handler: fooBarHandler,
params: Params{},
middlewares: []middleware{first},
},
},
{
item: &Item{
method: http.MethodPost,
path: "/foo/bar",
},
expected: &Result{
handler: fooBarHandler,
params: Params{},
middlewares: []middleware{first},
},
},
{
item: &Item{
method: http.MethodDelete,
path: "/foo/bar",
},
expected: &Result{
handler: fooBarHandler,
params: Params{},
middlewares: []middleware{first},
},
Expand Down Expand Up @@ -268,7 +383,6 @@ func TestSearchAllMethod(t *testing.T) {
}
}
}

func TestSearchWithoutRoot(t *testing.T) {
tree := NewTree()

Expand Down Expand Up @@ -1224,25 +1338,25 @@ func TestGetParamName(t *testing.T) {
}
}

func TestDeleteEmpty(t *testing.T) {
func TestExplodePath(t *testing.T) {
cases := []struct {
actual []string
expected []string
}{
{
actual: deleteEmpty(strings.Split("/", pathDelimiter)),
actual: explodePath(strings.Split("/", pathDelimiter)),
expected: nil,
},
{
actual: deleteEmpty(strings.Split("/foo", pathDelimiter)),
actual: explodePath(strings.Split("/foo", pathDelimiter)),
expected: []string{"foo"},
},
{
actual: deleteEmpty(strings.Split("/foo/bar", pathDelimiter)),
actual: explodePath(strings.Split("/foo/bar", pathDelimiter)),
expected: []string{"foo", "bar"},
},
{
actual: deleteEmpty(strings.Split("/foo/bar/baz", pathDelimiter)),
actual: explodePath(strings.Split("/foo/bar/baz", pathDelimiter)),
expected: []string{"foo", "bar", "baz"},
},
}
Expand Down

0 comments on commit 0afcfcd

Please sign in to comment.