Files
amcs/vendor/github.com/uptrace/bunrouter/node.go
Hein 1adf50e3db
CI / build-and-test (push) Failing after 1s
Release / release (push) Failing after 19m26s
fix(go.sum): update ResolveSpec dependency to v1.0.87
2026-06-23 13:17:16 +02:00

397 lines
7.7 KiB
Go

package bunrouter
import (
"fmt"
"net/http"
"sort"
"strings"
)
type node struct {
route string
part string
handlerMap *handlerMap
parent *node
colon *node
isWC bool
nodes []*node
index struct {
table []uint8 // index table for the nodes: firstChar-minChar => node position
minChar byte // min char in the table
maxChar byte // max char in the table
}
}
func (n *node) addRoute(route string) (*node, map[string]int) {
parts, params := splitRoute(route)
currNode := n
for _, part := range parts {
currNode = currNode.addPart(part)
}
if currNode.route == "" {
currNode.route = route
}
n.indexNodes()
return currNode, params
}
func (n *node) addPart(part string) *node {
if part == "*" {
n.isWC = true
return n
}
if part == ":" {
if n.colon == nil {
n.colon = &node{part: ":"}
}
return n.colon
}
for childNodeIndex, childNode := range n.nodes {
if childNode.part[0] != part[0] {
continue
}
// Check for a common prefix.
for i, c := range []byte(part) {
if i == len(childNode.part) {
break
}
if c == childNode.part[i] {
continue
}
// Create a node for the common prefix.
childNode.part = childNode.part[i:]
newNode := &node{part: part[i:]}
n.nodes[childNodeIndex] = &node{
part: part[:i], // common prefix
nodes: []*node{childNode, newNode},
}
return newNode
}
// Parts match completely.
switch {
case len(part) > len(childNode.part): // part is bigger
part = part[len(childNode.part):]
return childNode.addPart(part)
case len(part) < len(childNode.part): // part is smaller
childNode.part = childNode.part[len(part):]
newNode := &node{part: part}
newNode.nodes = []*node{childNode}
n.nodes[childNodeIndex] = newNode
return newNode
default:
return childNode // exact match
}
}
node := &node{part: part}
n.nodes = append(n.nodes, node)
return node
}
func (n *node) findRoute(meth, path string) (*node, *routeHandler, int) {
if path == "" {
return nil, nil, 0
}
path = path[1:] // strip leading "/"
if path == "" {
if n.handlerMap != nil {
return n, n.handlerMap.Get(meth), 0
}
return nil, nil, 0
}
return n._findRoute(meth, path)
}
func (n *node) _findRoute(meth, path string) (*node, *routeHandler, int) {
var found *node
if firstChar := path[0]; firstChar >= n.index.minChar && firstChar <= n.index.maxChar {
if i := n.index.table[firstChar-n.index.minChar]; i != 0 {
childNode := n.nodes[i-1]
if childNode.part == path {
if childNode.handlerMap != nil {
if handler := childNode.handlerMap.Get(meth); handler != nil {
return childNode, handler, 0
}
found = childNode
}
} else {
partLen := len(childNode.part)
if strings.HasPrefix(path, childNode.part) {
node, handler, wildcardLen := childNode._findRoute(meth, path[partLen:])
if handler != nil {
return node, handler, wildcardLen
}
if node != nil {
found = node
}
}
}
}
}
if n.colon != nil {
if i := strings.IndexByte(path, '/'); i > 0 {
node, handler, wildcardLen := n.colon._findRoute(meth, path[i:])
if handler != nil {
return node, handler, wildcardLen
}
} else if n.colon.handlerMap != nil {
if handler := n.colon.handlerMap.Get(meth); handler != nil {
return n.colon, handler, 0
}
if found == nil {
found = n.colon
}
}
}
if n.isWC && n.handlerMap != nil {
if handler := n.handlerMap.Get(meth); handler != nil {
return n, handler, len(path)
}
if found == nil {
found = n
}
}
return found, nil, 0
}
func (n *node) indexNodes() {
if len(n.nodes) > 0 {
n._indexNodes()
}
if n.colon != nil {
n.colon.parent = n
n.colon.indexNodes()
}
}
func (n *node) _indexNodes() {
sort.Slice(n.nodes, func(i, j int) bool {
return n.nodes[i].part[0] < n.nodes[j].part[0]
})
n.index.minChar = n.nodes[0].part[0]
n.index.maxChar = n.nodes[len(n.nodes)-1].part[0]
// Reset index.
if size := int(n.index.maxChar - n.index.minChar + 1); len(n.index.table) != size {
n.index.table = make([]uint8, size)
} else {
for i := range n.index.table {
n.index.table[i] = 0
}
}
// Index nodes by the first char in a part.
for childNodeIndex, childNode := range n.nodes {
childNode.parent = n
childNode.indexNodes()
firstChar := childNode.part[0] - n.index.minChar
n.index.table[firstChar] = uint8(childNodeIndex + 1)
}
}
func (n *node) setHandler(verb string, handler *routeHandler) {
if n.handlerMap == nil {
n.handlerMap = newHandlerMap()
}
n.handlerMap.Set(verb, handler)
}
//------------------------------------------------------------------------------
type routeParser struct {
segments []string
i int
acc []string
parts []string
}
func (p *routeParser) valid() bool {
return p.i < len(p.segments)
}
func (p *routeParser) next() string {
s := p.segments[p.i]
p.i++
return s
}
func (p *routeParser) accumulate(s string) {
p.acc = append(p.acc, s)
}
func (p *routeParser) finalizePart(withSlash bool) {
if part := join(p.acc, withSlash); part != "" {
p.parts = append(p.parts, part)
}
p.acc = p.acc[:0]
if p.valid() {
p.acc = append(p.acc, "")
}
}
func join(ss []string, withSlash bool) string {
if len(ss) == 0 {
return ""
}
s := strings.Join(ss, "/")
if withSlash {
return s + "/"
}
return s
}
func splitRoute(route string) (_ []string, _ map[string]int) {
if route == "" || route[0] != '/' {
panic(fmt.Errorf("invalid route: %q", route))
}
if route == "/" {
return []string{}, nil
}
route = route[1:] // trim first "/"
ss := strings.Split(route, "/")
if len(ss) == 0 {
panic(fmt.Errorf("invalid route: %q", route))
}
p := routeParser{
segments: ss,
}
var params []string
for p.valid() {
segment := p.next()
if segment == "" {
p.accumulate("")
continue
}
switch firstChar := segment[0]; firstChar {
case ':':
p.finalizePart(true)
p.parts = append(p.parts, ":")
params = append(params, segment[1:])
case '*':
p.finalizePart(true)
p.parts = append(p.parts, "*")
params = append(params, segment[1:])
default:
p.accumulate(segment)
}
}
p.finalizePart(false)
if len(params) > 0 {
return p.parts, paramMap(route, params)
}
return p.parts, nil
}
func paramMap(route string, params []string) map[string]int {
m := make(map[string]int, len(params))
for i, param := range params {
if param == "" {
panic(fmt.Errorf("param must have a name: %q", route))
}
m[param] = i
}
return m
}
//------------------------------------------------------------------------------
type handlerMap struct {
get *routeHandler
post *routeHandler
put *routeHandler
delete *routeHandler
head *routeHandler
options *routeHandler
patch *routeHandler
notAllowed *routeHandler
}
type routeHandler struct {
fn HandlerFunc
params map[string]int // param name => param position
}
func newHandlerMap() *handlerMap {
return new(handlerMap)
}
func (h *handlerMap) Get(meth string) *routeHandler {
switch meth {
case http.MethodGet:
return h.get
case http.MethodPost:
return h.post
case http.MethodPut:
return h.put
case http.MethodDelete:
return h.delete
case http.MethodHead:
return h.head
case http.MethodOptions:
return h.options
case http.MethodPatch:
return h.patch
default:
return nil
}
}
func (h *handlerMap) Set(meth string, handler *routeHandler) {
switch meth {
case http.MethodGet:
h.get = handler
case http.MethodPost:
h.post = handler
case http.MethodPut:
h.put = handler
case http.MethodDelete:
h.delete = handler
case http.MethodHead:
h.head = handler
case http.MethodOptions:
h.options = handler
case http.MethodPatch:
h.patch = handler
default:
panic(fmt.Errorf("unknown HTTP method: %s", meth))
}
}