go-zero/core/search/tree.go

240 lines
4.2 KiB
Go
Raw Normal View History

2020-07-26 17:09:05 +08:00
package search
import (
"errors"
"fmt"
)
2020-07-26 17:09:05 +08:00
const (
colon = ':'
slash = '/'
)
var (
// errDupItem means adding duplicated item.
errDupItem = errors.New("duplicated item")
// errDupSlash means item is started with more than one slash.
errDupSlash = errors.New("duplicated slash")
// errEmptyItem means adding empty item.
errEmptyItem = errors.New("empty item")
// errInvalidState means search tree is in an invalid state.
errInvalidState = errors.New("search tree is in an invalid state")
// errNotFromRoot means path is not starting with slash.
errNotFromRoot = errors.New("path should start with /")
2020-07-26 17:09:05 +08:00
// NotFound is used to hold the not found result.
2020-07-26 17:09:05 +08:00
NotFound Result
)
type (
innerResult struct {
key string
value string
named bool
found bool
}
node struct {
item interface{}
children [2]map[string]*node
}
// A Tree is a search tree.
2020-07-26 17:09:05 +08:00
Tree struct {
root *node
}
// A Result is a search result from tree.
2020-07-26 17:09:05 +08:00
Result struct {
Item interface{}
Params map[string]string
}
)
// NewTree returns a Tree.
2020-07-26 17:09:05 +08:00
func NewTree() *Tree {
return &Tree{
root: newNode(nil),
}
}
// Add adds item to associate with route.
2020-07-26 17:09:05 +08:00
func (t *Tree) Add(route string, item interface{}) error {
if len(route) == 0 || route[0] != slash {
return errNotFromRoot
2020-07-26 17:09:05 +08:00
}
if item == nil {
return errEmptyItem
2020-07-26 17:09:05 +08:00
}
err := add(t.root, route[1:], item)
switch err {
case errDupItem:
return duplicatedItem(route)
case errDupSlash:
return duplicatedSlash(route)
default:
return err
}
2020-07-26 17:09:05 +08:00
}
// Search searches item that associates with given route.
2020-07-26 17:09:05 +08:00
func (t *Tree) Search(route string) (Result, bool) {
if len(route) == 0 || route[0] != slash {
return NotFound, false
}
var result Result
ok := t.next(t.root, route[1:], &result)
return result, ok
}
func (t *Tree) next(n *node, route string, result *Result) bool {
if len(route) == 0 && n.item != nil {
result.Item = n.item
return true
}
for i := range route {
if route[i] != slash {
continue
2020-07-26 17:09:05 +08:00
}
2021-05-22 23:18:38 +08:00
token := route[:i]
return n.forEach(func(k string, v *node) bool {
r := match(k, token)
if !r.found || !t.next(v, route[i+1:], result) {
return false
}
if r.named {
addParam(result, r.key, r.value)
}
return true
})
2020-07-26 17:09:05 +08:00
}
2021-02-17 14:01:05 +08:00
return n.forEach(func(k string, v *node) bool {
if r := match(k, route); r.found && v.item != nil {
result.Item = v.item
if r.named {
addParam(result, r.key, r.value)
}
2020-07-26 17:09:05 +08:00
2021-02-17 14:01:05 +08:00
return true
}
return false
})
}
func (nd *node) forEach(fn func(string, *node) bool) bool {
for _, children := range nd.children {
for k, v := range children {
if fn(k, v) {
2020-07-26 17:09:05 +08:00
return true
}
}
}
return false
}
func (nd *node) getChildren(route string) map[string]*node {
if len(route) > 0 && route[0] == colon {
return nd.children[1]
}
2021-02-09 13:50:21 +08:00
return nd.children[0]
2020-07-26 17:09:05 +08:00
}
func add(nd *node, route string, item interface{}) error {
if len(route) == 0 {
if nd.item != nil {
return errDupItem
2020-07-26 17:09:05 +08:00
}
nd.item = item
return nil
}
if route[0] == slash {
return errDupSlash
2020-07-26 17:09:05 +08:00
}
for i := range route {
2021-05-22 23:18:38 +08:00
if route[i] != slash {
continue
}
token := route[:i]
children := nd.getChildren(token)
if child, ok := children[token]; ok {
if child != nil {
return add(child, route[i+1:], item)
2020-07-26 17:09:05 +08:00
}
2021-02-09 14:10:38 +08:00
2021-05-22 23:18:38 +08:00
return errInvalidState
2020-07-26 17:09:05 +08:00
}
2021-05-22 23:18:38 +08:00
child := newNode(nil)
children[token] = child
return add(child, route[i+1:], item)
2020-07-26 17:09:05 +08:00
}
children := nd.getChildren(route)
if child, ok := children[route]; ok {
if child.item != nil {
return errDupItem
2020-07-26 17:09:05 +08:00
}
child.item = item
} else {
children[route] = newNode(item)
}
return nil
}
func addParam(result *Result, k, v string) {
if result.Params == nil {
result.Params = make(map[string]string)
}
result.Params[k] = v
}
func duplicatedItem(item string) error {
return fmt.Errorf("duplicated item for %s", item)
}
func duplicatedSlash(item string) error {
return fmt.Errorf("duplicated slash for %s", item)
}
2020-07-26 17:09:05 +08:00
func match(pat, token string) innerResult {
if pat[0] == colon {
return innerResult{
key: pat[1:],
value: token,
named: true,
found: true,
}
}
return innerResult{
found: pat == token,
}
}
func newNode(item interface{}) *node {
return &node{
item: item,
children: [2]map[string]*node{
make(map[string]*node),
make(map[string]*node),
},
}
}