Moonshark/router/node.go

191 lines
4.4 KiB
Go

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
}