package router import ( "path/filepath" "strings" "time" ) // node represents a node in the radix trie type node struct { // Static children mapped by path segment staticChild map[string]*node // Parameter child for dynamic segments (e.g., :id) paramChild *node paramName string // Handler information handler string // Path to the handler file indexFile string // Path to index.lua if exists modTime time.Time // Modification time of the handler fsPath string // Filesystem path (includes groups) // Compilation error if any err error } // pathInfo holds both URL path and filesystem path type pathInfo struct { urlPath string // URL path without groups (e.g., /users) fsPath string // Filesystem path with groups (e.g., /(admin)/users) } // parsePathWithGroups parses a filesystem path, extracting groups func parsePathWithGroups(fsPath string) *pathInfo { segments := strings.Split(strings.Trim(fsPath, "/"), "/") var urlSegments []string for _, segment := range segments { if segment == "" { continue } // Skip group segments (enclosed in parentheses) if strings.HasPrefix(segment, "(") && strings.HasSuffix(segment, ")") { continue } urlSegments = append(urlSegments, segment) } urlPath := "/" if len(urlSegments) > 0 { urlPath = "/" + strings.Join(urlSegments, "/") } return &pathInfo{ urlPath: urlPath, fsPath: fsPath, } } // findOrCreateNode finds or creates a node at the given URL path func (r *LuaRouter) findOrCreateNode(root *node, urlPath string) *node { segments := strings.Split(strings.Trim(urlPath, "/"), "/") if len(segments) == 1 && segments[0] == "" { return root } current := root for _, segment := range segments { if segment == "" { continue } // Check if it's a parameter if strings.HasPrefix(segment, ":") { paramName := segment[1:] if current.paramChild == nil { current.paramChild = &node{ staticChild: make(map[string]*node), paramName: paramName, } } current = current.paramChild } else { // Static segment if _, exists := current.staticChild[segment]; !exists { current.staticChild[segment] = &node{ staticChild: make(map[string]*node), } } current = current.staticChild[segment] } } return current } // addRoute adds a route to the tree func (r *LuaRouter) addRoute(root *node, pathInfo *pathInfo, handlerPath string, modTime time.Time) { node := r.findOrCreateNode(root, pathInfo.urlPath) node.handler = handlerPath node.modTime = modTime node.fsPath = pathInfo.fsPath // Compile the route with middleware r.compileWithMiddleware(node, pathInfo.fsPath, handlerPath) // Track failed routes if node.err != nil { key := filepath.Base(handlerPath) + ":" + pathInfo.urlPath r.failedRoutes[key] = &RouteError{ Path: pathInfo.urlPath, Method: strings.ToUpper(strings.TrimSuffix(filepath.Base(handlerPath), ".lua")), ScriptPath: handlerPath, Err: node.err, } } } // nodeForHandler finds a node by its handler path func (r *LuaRouter) nodeForHandler(handlerPath string) (*node, bool) { r.mu.RLock() defer r.mu.RUnlock() for _, root := range r.routes { if node := findNodeByHandler(root, handlerPath); node != nil { return node, true } } return nil, false } // findNodeByHandler recursively searches for a node with the given handler path func findNodeByHandler(n *node, handlerPath string) *node { if n.handler == handlerPath || n.indexFile == handlerPath { return n } // Search static children for _, child := range n.staticChild { if found := findNodeByHandler(child, handlerPath); found != nil { return found } } // Search param child if n.paramChild != nil { if found := findNodeByHandler(n.paramChild, handlerPath); found != nil { return found } } return nil } // countNodesAndBytecode counts nodes and bytecode size in the tree func countNodesAndBytecode(n *node) (int, int64) { if n == nil { return 0, 0 } count := 0 bytes := int64(0) // Count this node if it has a handler if n.handler != "" || n.indexFile != "" { count = 1 // Estimate bytecode size (would need actual bytecode cache lookup for accuracy) bytes = 1024 // Placeholder } // Count static children for _, child := range n.staticChild { c, b := countNodesAndBytecode(child) count += c bytes += b } // Count param child if n.paramChild != nil { c, b := countNodesAndBytecode(n.paramChild) count += c bytes += b } return count, bytes }