util/http: add trie case insesitive matching

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2021-09-29 13:41:47 +03:00
parent 15c020fac5
commit e3fee6f8a6
2 changed files with 55 additions and 4 deletions

View File

@ -6,6 +6,21 @@ import (
"sync" "sync"
) )
// TrieOptions contains search options
type TrieOptions struct {
IgnoreCase bool
}
// TrieOption func signature
type TrieOption func(*TrieOptions)
// IgnoreCase says that search must be case insensitive
func IgnoreCase(b bool) TrieOption {
return func(o *TrieOptions) {
o.IgnoreCase = b
}
}
// Tree is a trie tree. // Tree is a trie tree.
type Trie struct { type Trie struct {
node *node node *node
@ -79,19 +94,35 @@ func (t *Trie) Insert(methods []string, path string, handler interface{}) {
} }
// Search searches a path from a tree. // Search searches a path from a tree.
func (t *Trie) Search(method string, path string) (interface{}, map[string]string, bool) { func (t *Trie) Search(method string, path string, opts ...TrieOption) (interface{}, map[string]string, bool) {
params := make(map[string]string) params := make(map[string]string)
options := TrieOptions{}
for _, o := range opts {
o(&options)
}
curNode := t.node curNode := t.node
nodeLoop:
for _, p := range splitPath(path) { for _, p := range splitPath(path) {
nextNode, ok := curNode.children[p] nextNode, ok := curNode.children[p]
if ok { if ok {
curNode = nextNode curNode = nextNode
continue continue nodeLoop
}
if options.IgnoreCase {
// additional loop for case insensitive matching
for k, v := range curNode.children {
if literalEqual(k, p, true) {
curNode = v
continue nodeLoop
}
}
} }
if len(curNode.children) == 0 { if len(curNode.children) == 0 {
if curNode.label != p { if !literalEqual(curNode.label, p, options.IgnoreCase) {
// no matching path was found. // no matching path was found
return nil, nil, false return nil, nil, false
} }
break break
@ -199,3 +230,10 @@ func splitPath(path string) []string {
} }
return r return r
} }
func literalEqual(component, literal string, ignoreCase bool) bool {
if ignoreCase {
return strings.EqualFold(component, literal)
}
return component == literal
}

View File

@ -5,6 +5,19 @@ import (
"testing" "testing"
) )
func TestTrieIgnoreCase(t *testing.T) {
type handler struct {
name string
}
tr := NewTrie()
tr.Insert([]string{http.MethodPut}, "/v1/create/{id}", &handler{name: "test"})
_, _, ok := tr.Search(http.MethodPut, "/v1/CREATE/12", IgnoreCase(true))
if !ok {
t.Fatalf("unexpected error")
}
}
func TestTrieContentType(t *testing.T) { func TestTrieContentType(t *testing.T) {
type handler struct { type handler struct {
name string name string