242 lines
4.9 KiB
Go
242 lines
4.9 KiB
Go
package router
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
// Handler function that takes parameters as strings
|
|
type Handler func(params []string)
|
|
|
|
type node struct {
|
|
segment string
|
|
handlerID int
|
|
children []*node
|
|
isDynamic bool
|
|
isWildcard bool
|
|
maxParams uint8
|
|
}
|
|
|
|
type Router struct {
|
|
get, post, put, patch, delete *node
|
|
paramsBuffer []string
|
|
}
|
|
|
|
// Params holds URL parameters
|
|
type Params struct {
|
|
Keys []string
|
|
Values []string
|
|
}
|
|
|
|
// Get returns a parameter value by name
|
|
func (p *Params) Get(name string) string {
|
|
for i, key := range p.Keys {
|
|
if key == name && i < len(p.Values) {
|
|
return p.Values[i]
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// New creates a new Router instance
|
|
func New() *Router {
|
|
return &Router{
|
|
get: &node{},
|
|
post: &node{},
|
|
put: &node{},
|
|
patch: &node{},
|
|
delete: &node{},
|
|
paramsBuffer: make([]string, 64),
|
|
}
|
|
}
|
|
|
|
func (r *Router) methodNode(method string) *node {
|
|
switch method {
|
|
case "GET":
|
|
return r.get
|
|
case "POST":
|
|
return r.post
|
|
case "PUT":
|
|
return r.put
|
|
case "PATCH":
|
|
return r.patch
|
|
case "DELETE":
|
|
return r.delete
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// AddRoute adds a route with handler ID (for compatibility)
|
|
func (r *Router) AddRoute(method, path string, handlerID int) error {
|
|
root := r.methodNode(method)
|
|
if root == nil {
|
|
return fmt.Errorf("unsupported method: %s", method)
|
|
}
|
|
|
|
// Create a handler that stores the ID
|
|
h := func(params []string) {
|
|
// This is a placeholder - the actual execution happens in HTTP module
|
|
}
|
|
|
|
return r.addRoute(root, path, h, handlerID)
|
|
}
|
|
|
|
// readSegment extracts the next path segment
|
|
func readSegment(path string, start int) (segment string, end int, hasMore bool) {
|
|
if start >= len(path) {
|
|
return "", start, false
|
|
}
|
|
if path[start] == '/' {
|
|
start++
|
|
}
|
|
if start >= len(path) {
|
|
return "", start, false
|
|
}
|
|
end = start
|
|
for end < len(path) && path[end] != '/' {
|
|
end++
|
|
}
|
|
return path[start:end], end, end < len(path)
|
|
}
|
|
|
|
// addRoute adds a new route to the trie
|
|
func (r *Router) addRoute(root *node, path string, h Handler, handlerID int) error {
|
|
if path == "/" {
|
|
root.handlerID = handlerID
|
|
return nil
|
|
}
|
|
|
|
current := root
|
|
pos := 0
|
|
lastWC := false
|
|
count := uint8(0)
|
|
|
|
for {
|
|
seg, newPos, more := readSegment(path, pos)
|
|
if seg == "" {
|
|
break
|
|
}
|
|
|
|
isDyn := len(seg) > 1 && seg[0] == ':'
|
|
isWC := len(seg) > 0 && seg[0] == '*'
|
|
|
|
if isWC {
|
|
if lastWC || more {
|
|
return fmt.Errorf("wildcard must be the last segment in the path")
|
|
}
|
|
lastWC = true
|
|
}
|
|
|
|
if isDyn || isWC {
|
|
count++
|
|
}
|
|
|
|
var child *node
|
|
for _, c := range current.children {
|
|
if c.segment == seg {
|
|
child = c
|
|
break
|
|
}
|
|
}
|
|
|
|
if child == nil {
|
|
child = &node{segment: seg, isDynamic: isDyn, isWildcard: isWC}
|
|
current.children = append(current.children, child)
|
|
}
|
|
|
|
if child.maxParams < count {
|
|
child.maxParams = count
|
|
}
|
|
|
|
current = child
|
|
pos = newPos
|
|
}
|
|
|
|
current.handlerID = handlerID
|
|
return nil
|
|
}
|
|
|
|
// Lookup finds a handler matching method and path
|
|
func (r *Router) Lookup(method, path string) (int, *Params, bool) {
|
|
root := r.methodNode(method)
|
|
if root == nil {
|
|
return 0, nil, false
|
|
}
|
|
|
|
if path == "/" {
|
|
if root.handlerID != 0 {
|
|
return root.handlerID, &Params{}, true
|
|
}
|
|
return 0, nil, false
|
|
}
|
|
|
|
buffer := r.paramsBuffer
|
|
if cap(buffer) < int(root.maxParams) {
|
|
buffer = make([]string, root.maxParams)
|
|
r.paramsBuffer = buffer
|
|
}
|
|
buffer = buffer[:0]
|
|
|
|
handlerID, paramCount, paramKeys, found := r.match(root, path, 0, &buffer)
|
|
if !found {
|
|
return 0, nil, false
|
|
}
|
|
|
|
params := &Params{
|
|
Keys: paramKeys,
|
|
Values: buffer[:paramCount],
|
|
}
|
|
|
|
return handlerID, params, true
|
|
}
|
|
|
|
// match traverses the trie to find a handler
|
|
func (r *Router) match(current *node, path string, start int, params *[]string) (int, int, []string, bool) {
|
|
paramCount := 0
|
|
var paramKeys []string
|
|
|
|
// Check wildcards first
|
|
for _, c := range current.children {
|
|
if c.isWildcard {
|
|
rem := path[start:]
|
|
if len(rem) > 0 && rem[0] == '/' {
|
|
rem = rem[1:]
|
|
}
|
|
*params = append(*params, rem)
|
|
// Extract param name from *name format
|
|
paramName := c.segment[1:] // Remove the * prefix
|
|
paramKeys = append(paramKeys, paramName)
|
|
return c.handlerID, 1, paramKeys, c.handlerID != 0
|
|
}
|
|
}
|
|
|
|
seg, pos, more := readSegment(path, start)
|
|
if seg == "" {
|
|
return current.handlerID, 0, paramKeys, current.handlerID != 0
|
|
}
|
|
|
|
for _, c := range current.children {
|
|
if c.segment == seg || c.isDynamic {
|
|
if c.isDynamic {
|
|
*params = append(*params, seg)
|
|
// Extract param name from :name format
|
|
paramName := c.segment[1:] // Remove the : prefix
|
|
paramKeys = append(paramKeys, paramName)
|
|
paramCount++
|
|
}
|
|
|
|
if !more {
|
|
return c.handlerID, paramCount, paramKeys, c.handlerID != 0
|
|
}
|
|
|
|
handlerID, nestedCount, nestedKeys, ok := r.match(c, path, pos, params)
|
|
if ok {
|
|
allKeys := append(paramKeys, nestedKeys...)
|
|
return handlerID, paramCount + nestedCount, allKeys, true
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0, 0, nil, false
|
|
}
|