github.com/lingyao2333/mo-zero@v1.4.1/core/search/tree.go (about) 1 package search 2 3 import ( 4 "errors" 5 "fmt" 6 ) 7 8 const ( 9 colon = ':' 10 slash = '/' 11 ) 12 13 var ( 14 // errDupItem means adding duplicated item. 15 errDupItem = errors.New("duplicated item") 16 // errDupSlash means item is started with more than one slash. 17 errDupSlash = errors.New("duplicated slash") 18 // errEmptyItem means adding empty item. 19 errEmptyItem = errors.New("empty item") 20 // errInvalidState means search tree is in an invalid state. 21 errInvalidState = errors.New("search tree is in an invalid state") 22 // errNotFromRoot means path is not starting with slash. 23 errNotFromRoot = errors.New("path should start with /") 24 25 // NotFound is used to hold the not found result. 26 NotFound Result 27 ) 28 29 type ( 30 innerResult struct { 31 key string 32 value string 33 named bool 34 found bool 35 } 36 37 node struct { 38 item interface{} 39 children [2]map[string]*node 40 } 41 42 // A Tree is a search tree. 43 Tree struct { 44 root *node 45 } 46 47 // A Result is a search result from tree. 48 Result struct { 49 Item interface{} 50 Params map[string]string 51 } 52 ) 53 54 // NewTree returns a Tree. 55 func NewTree() *Tree { 56 return &Tree{ 57 root: newNode(nil), 58 } 59 } 60 61 // Add adds item to associate with route. 62 func (t *Tree) Add(route string, item interface{}) error { 63 if len(route) == 0 || route[0] != slash { 64 return errNotFromRoot 65 } 66 67 if item == nil { 68 return errEmptyItem 69 } 70 71 err := add(t.root, route[1:], item) 72 switch err { 73 case errDupItem: 74 return duplicatedItem(route) 75 case errDupSlash: 76 return duplicatedSlash(route) 77 default: 78 return err 79 } 80 } 81 82 // Search searches item that associates with given route. 83 func (t *Tree) Search(route string) (Result, bool) { 84 if len(route) == 0 || route[0] != slash { 85 return NotFound, false 86 } 87 88 var result Result 89 ok := t.next(t.root, route[1:], &result) 90 return result, ok 91 } 92 93 func (t *Tree) next(n *node, route string, result *Result) bool { 94 if len(route) == 0 && n.item != nil { 95 result.Item = n.item 96 return true 97 } 98 99 for i := range route { 100 if route[i] != slash { 101 continue 102 } 103 104 token := route[:i] 105 return n.forEach(func(k string, v *node) bool { 106 r := match(k, token) 107 if !r.found || !t.next(v, route[i+1:], result) { 108 return false 109 } 110 if r.named { 111 addParam(result, r.key, r.value) 112 } 113 114 return true 115 }) 116 } 117 118 return n.forEach(func(k string, v *node) bool { 119 if r := match(k, route); r.found && v.item != nil { 120 result.Item = v.item 121 if r.named { 122 addParam(result, r.key, r.value) 123 } 124 125 return true 126 } 127 128 return false 129 }) 130 } 131 132 func (nd *node) forEach(fn func(string, *node) bool) bool { 133 for _, children := range nd.children { 134 for k, v := range children { 135 if fn(k, v) { 136 return true 137 } 138 } 139 } 140 141 return false 142 } 143 144 func (nd *node) getChildren(route string) map[string]*node { 145 if len(route) > 0 && route[0] == colon { 146 return nd.children[1] 147 } 148 149 return nd.children[0] 150 } 151 152 func add(nd *node, route string, item interface{}) error { 153 if len(route) == 0 { 154 if nd.item != nil { 155 return errDupItem 156 } 157 158 nd.item = item 159 return nil 160 } 161 162 if route[0] == slash { 163 return errDupSlash 164 } 165 166 for i := range route { 167 if route[i] != slash { 168 continue 169 } 170 171 token := route[:i] 172 children := nd.getChildren(token) 173 if child, ok := children[token]; ok { 174 if child != nil { 175 return add(child, route[i+1:], item) 176 } 177 178 return errInvalidState 179 } 180 181 child := newNode(nil) 182 children[token] = child 183 return add(child, route[i+1:], item) 184 } 185 186 children := nd.getChildren(route) 187 if child, ok := children[route]; ok { 188 if child.item != nil { 189 return errDupItem 190 } 191 192 child.item = item 193 } else { 194 children[route] = newNode(item) 195 } 196 197 return nil 198 } 199 200 func addParam(result *Result, k, v string) { 201 if result.Params == nil { 202 result.Params = make(map[string]string) 203 } 204 205 result.Params[k] = v 206 } 207 208 func duplicatedItem(item string) error { 209 return fmt.Errorf("duplicated item for %s", item) 210 } 211 212 func duplicatedSlash(item string) error { 213 return fmt.Errorf("duplicated slash for %s", item) 214 } 215 216 func match(pat, token string) innerResult { 217 if pat[0] == colon { 218 return innerResult{ 219 key: pat[1:], 220 value: token, 221 named: true, 222 found: true, 223 } 224 } 225 226 return innerResult{ 227 found: pat == token, 228 } 229 } 230 231 func newNode(item interface{}) *node { 232 return &node{ 233 item: item, 234 children: [2]map[string]*node{ 235 make(map[string]*node), 236 make(map[string]*node), 237 }, 238 } 239 }