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 }