0
0
mirror of https://github.com/rls-moe/nyx synced 2024-11-14 22:12:24 +00:00
nyx/vendor/github.com/pressly/chi/tree.go

536 lines
10 KiB
Go
Raw Normal View History

package chi
// Radix tree implementation below is a based on the original work by
// Armon Dadgar in https://github.com/armon/go-radix/blob/master/radix.go
// (MIT licensed). It's been heavily modified for use as a HTTP routing tree.
import (
"net/http"
"sort"
"strings"
)
type methodTyp int
const (
mCONNECT methodTyp = 1 << iota
mDELETE
mGET
mHEAD
mOPTIONS
mPATCH
mPOST
mPUT
mTRACE
mSTUB
mALL methodTyp = mCONNECT | mDELETE | mGET | mHEAD | mOPTIONS |
mPATCH | mPOST | mPUT | mTRACE
)
var methodMap = map[string]methodTyp{
"CONNECT": mCONNECT,
"DELETE": mDELETE,
"GET": mGET,
"HEAD": mHEAD,
"OPTIONS": mOPTIONS,
"PATCH": mPATCH,
"POST": mPOST,
"PUT": mPUT,
"TRACE": mTRACE,
}
type nodeTyp uint8
const (
ntStatic nodeTyp = iota // /home
ntRegexp // /:id([0-9]+) or #id^[0-9]+$
ntParam // /:user
ntCatchAll // /api/v1/*
)
type node struct {
// node type
typ nodeTyp
// first byte of the prefix
label byte
// prefix is the common prefix we ignore
prefix string
// pattern is the computed path of prefixes
pattern string
// HTTP handler on the leaf node
handlers methodHandlers
// chi subroutes on the leaf node
subroutes Routes
// Child nodes should be stored in-order for iteration,
// in groups of the node type.
children [ntCatchAll + 1]nodes
}
func (n *node) FindRoute(rctx *Context, path string) methodHandlers {
// Reset the context routing pattern
rctx.RoutePattern = ""
// Find the routing handlers for the path
rn := n.findRoute(rctx, path)
if rn == nil {
return nil
}
// Record the routing pattern in the request lifecycle
if rn.pattern != "" {
rctx.RoutePattern = rn.pattern
rctx.RoutePatterns = append(rctx.RoutePatterns, rctx.RoutePattern)
}
return rn.handlers
}
func (n *node) InsertRoute(method methodTyp, pattern string, handler http.Handler) *node {
var parent *node
search := pattern
for {
// Handle key exhaustion
if len(search) == 0 {
// Insert or update the node's leaf handler
n.setHandler(method, handler)
n.pattern = pattern
return n
}
// Look for the edge
parent = n
n = n.getEdge(search[0])
// No edge, create one
if n == nil {
cn := &node{label: search[0], prefix: search, pattern: pattern}
cn.setHandler(method, handler)
parent.addChild(pattern, cn)
return cn
}
if n.typ > ntStatic {
// We found a wildcard node, meaning search path starts with
// a wild prefix. Trim off the wildcard search path and continue.
p := strings.Index(search, "/")
if p < 0 {
p = len(search)
}
search = search[p:]
continue
}
// Static nodes fall below here.
// Determine longest prefix of the search key on match.
commonPrefix := n.longestPrefix(search, n.prefix)
if commonPrefix == len(n.prefix) {
// the common prefix is as long as the current node's prefix we're attempting to insert.
// keep the search going.
search = search[commonPrefix:]
continue
}
// Split the node
child := &node{
typ: ntStatic,
prefix: search[:commonPrefix],
}
parent.replaceChild(search[0], child)
// Restore the existing node
n.label = n.prefix[commonPrefix]
n.prefix = n.prefix[commonPrefix:]
child.addChild(pattern, n)
// If the new key is a subset, add to to this node
search = search[commonPrefix:]
if len(search) == 0 {
child.setHandler(method, handler)
child.pattern = pattern
return child
}
// Create a new edge for the node
subchild := &node{
typ: ntStatic,
label: search[0],
prefix: search,
pattern: pattern,
}
subchild.setHandler(method, handler)
child.addChild(pattern, subchild)
return subchild
}
}
func (n *node) findPattern(pattern string) *node {
nn := n
for _, nds := range nn.children {
if len(nds) == 0 {
continue
}
n = nn.getEdge(pattern[0])
if n == nil {
continue
}
idx := n.longestPrefix(pattern, n.prefix)
xpattern := pattern[idx:]
if len(xpattern) == 0 {
return n
} else if xpattern[0] == '/' && idx < len(n.prefix) {
continue
}
return n.findPattern(xpattern)
}
return nil
}
func (n *node) isLeaf() bool {
return n.handlers != nil
}
func (n *node) addChild(pattern string, child *node) {
search := child.prefix
// Find any wildcard segments
p := strings.IndexAny(search, ":*")
// Determine new node type
ntyp := child.typ
if p >= 0 {
switch search[p] {
case ':':
ntyp = ntParam
case '*':
ntyp = ntCatchAll
}
}
if p == 0 {
// Path starts with a wildcard
handlers := child.handlers
child.typ = ntyp
if ntyp == ntCatchAll {
p = -1
} else {
p = strings.IndexByte(search, '/')
}
if p < 0 {
p = len(search)
}
child.prefix = search[:p]
if p != len(search) {
// add edge for the remaining part, split the end.
child.handlers = nil
search = search[p:]
child.addChild(pattern, &node{
typ: ntStatic,
label: search[0], // this will always start with /
prefix: search,
pattern: pattern,
handlers: handlers,
})
}
} else if p > 0 {
// Path has some wildcard
// starts with a static segment
handlers := child.handlers
child.typ = ntStatic
child.prefix = search[:p]
child.handlers = nil
// add the wild edge node
search = search[p:]
child.addChild(pattern, &node{
typ: ntyp,
label: search[0],
prefix: search,
pattern: pattern,
handlers: handlers,
})
} else {
// Path is all static
child.typ = ntyp
}
n.children[child.typ] = append(n.children[child.typ], child)
n.children[child.typ].Sort()
}
func (n *node) replaceChild(label byte, child *node) {
for i := 0; i < len(n.children[child.typ]); i++ {
if n.children[child.typ][i].label == label {
n.children[child.typ][i] = child
n.children[child.typ][i].label = label
return
}
}
panic("chi: replacing missing child")
}
func (n *node) getEdge(label byte) *node {
for _, nds := range n.children {
num := len(nds)
for i := 0; i < num; i++ {
if nds[i].label == label {
return nds[i]
}
}
}
return nil
}
func (n *node) findEdge(ntyp nodeTyp, label byte) *node {
nds := n.children[ntyp]
num := len(nds)
idx := 0
switch ntyp {
case ntStatic:
i, j := 0, num-1
for i <= j {
idx = i + (j-i)/2
if label > nds[idx].label {
i = idx + 1
} else if label < nds[idx].label {
j = idx - 1
} else {
i = num // breaks cond
}
}
if nds[idx].label != label {
return nil
}
return nds[idx]
default: // wild nodes
// TODO: right now we match them all.. but regexp should
// run through regexp matcher
return nds[idx]
}
}
// Recursive edge traversal by checking all nodeTyp groups along the way.
// It's like searching through a multi-dimensional radix trie.
func (n *node) findRoute(rctx *Context, path string) *node {
nn := n
search := path
for t, nds := range nn.children {
ntyp := nodeTyp(t)
if len(nds) == 0 {
continue
}
// search subset of edges of the index for a matching node
var label byte
if search != "" {
label = search[0]
}
xn := nn.findEdge(ntyp, label) // next node
if xn == nil {
continue
}
// Prepare next search path by trimming prefix from requested path
xsearch := search
if xn.typ > ntStatic {
p := -1
if xn.typ < ntCatchAll {
p = strings.IndexByte(xsearch, '/')
}
if p < 0 {
p = len(xsearch)
}
if xn.typ == ntCatchAll {
rctx.URLParams.Add("*", xsearch)
} else {
rctx.URLParams.Add(xn.prefix[1:], xsearch[:p])
}
xsearch = xsearch[p:]
} else if strings.HasPrefix(xsearch, xn.prefix) {
xsearch = xsearch[len(xn.prefix):]
} else {
continue // no match
}
// did we find it yet?
if len(xsearch) == 0 {
if xn.isLeaf() {
return xn
}
}
// recursively find the next node..
fin := xn.findRoute(rctx, xsearch)
if fin != nil {
// found a node, return it
return fin
}
// Did not found final handler, let's remove the param here if it was set
if xn.typ > ntStatic {
if xn.typ == ntCatchAll {
rctx.URLParams.Del("*")
} else {
rctx.URLParams.Del(xn.prefix[1:])
}
}
}
return nil
}
// longestPrefix finds the length of the shared prefix
// of two strings
func (n *node) longestPrefix(k1, k2 string) int {
max := len(k1)
if l := len(k2); l < max {
max = l
}
var i int
for i = 0; i < max; i++ {
if k1[i] != k2[i] {
break
}
}
return i
}
func (n *node) setHandler(method methodTyp, handler http.Handler) {
if n.handlers == nil {
n.handlers = make(methodHandlers, 0)
}
if method&mSTUB == mSTUB {
n.handlers[mSTUB] = handler
} else {
n.handlers[mSTUB] = nil
}
if method&mALL == mALL {
n.handlers[mALL] = handler
for _, m := range methodMap {
n.handlers[m] = handler
}
} else {
n.handlers[method] = handler
}
}
func (n *node) isEmpty() bool {
for _, nds := range n.children {
if len(nds) > 0 {
return false
}
}
return true
}
func (n *node) routes() []Route {
rts := []Route{}
n.walkRoutes(n.prefix, n, func(pattern string, handlers methodHandlers, subroutes Routes) bool {
if handlers[mSTUB] != nil && subroutes == nil {
return false
}
if subroutes != nil && len(pattern) > 2 {
pattern = pattern[:len(pattern)-2]
}
var hs = make(map[string]http.Handler, 0)
if handlers[mALL] != nil {
hs["*"] = handlers[mALL]
}
for mt, h := range handlers {
if h == nil {
continue
}
m := methodTypString(mt)
if m == "" {
continue
}
hs[m] = h
}
rt := Route{pattern, hs, subroutes}
rts = append(rts, rt)
return false
})
return rts
}
func (n *node) walkRoutes(pattern string, nd *node, fn walkFn) bool {
pattern = nd.pattern
// Visit the leaf values if any
if (nd.handlers != nil || nd.subroutes != nil) && fn(pattern, nd.handlers, nd.subroutes) {
return true
}
// Recurse on the children
for _, nds := range nd.children {
for _, nd := range nds {
if n.walkRoutes(pattern, nd, fn) {
return true
}
}
}
return false
}
func methodTypString(method methodTyp) string {
for s, t := range methodMap {
if method == t {
return s
}
}
return ""
}
type walkFn func(pattern string, handlers methodHandlers, subroutes Routes) bool
// methodHandlers is a mapping of http method constants to handlers
// for a given route.
type methodHandlers map[methodTyp]http.Handler
type nodes []*node
// Sort the list of nodes by label
func (ns nodes) Len() int { return len(ns) }
func (ns nodes) Less(i, j int) bool { return ns[i].label < ns[j].label }
func (ns nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
func (ns nodes) Sort() { sort.Sort(ns) }
type Route struct {
Pattern string
Handlers map[string]http.Handler
SubRoutes Routes
}