package extpoints import ( "strings" "sync" ) // RouteDefinition holds the metadata and handler list for a single HTTP route. type RouteDefinition struct { Method string Path string Handlers []any Middlewares []any } // RouterExtension defines the interface for registering routes and middlewares. type RouterExtension interface { Use(middlewares ...any) Group(prefix string, middlewares ...any) RouterExtension Handle(method string, path string, handlers ...any) RouteDefinition GET(path string, handlers ...any) RouteDefinition POST(path string, handlers ...any) RouteDefinition PUT(path string, handlers ...any) RouteDefinition DELETE(path string, handlers ...any) RouteDefinition PATCH(path string, handlers ...any) RouteDefinition HEAD(path string, handlers ...any) RouteDefinition OPTIONS(path string, handlers ...any) RouteDefinition Any(path string, handlers ...any) []RouteDefinition Routes() []RouteDefinition Middlewares() []any } // RouterRegistry implements RouterExtension as the root route and middleware collector. type RouterRegistry struct { mu sync.RWMutex routes []RouteDefinition middlewares []any } // NewRouterRegistry creates a new root router collector. func NewRouterRegistry() *RouterRegistry { return &RouterRegistry{} } // Use registers global middlewares to the router. func (r *RouterRegistry) Use(middlewares ...any) { r.mu.Lock() defer r.mu.Unlock() r.middlewares = append(r.middlewares, middlewares...) } // Middlewares returns a copy of registered root middlewares. func (r *RouterRegistry) Middlewares() []any { r.mu.RLock() defer r.mu.RUnlock() res := make([]any, len(r.middlewares)) copy(res, r.middlewares) return res } // Group creates a new RouteGroup under the router. func (r *RouterRegistry) Group(prefix string, middlewares ...any) RouterExtension { return &RouterGroup{ registry: r, prefix: cleanPath(prefix), middlewares: middlewares, } } // Handle registers a route with a custom HTTP method and handlers. func (r *RouterRegistry) Handle(method, path string, handlers ...any) RouteDefinition { r.mu.Lock() defer r.mu.Unlock() rd := RouteDefinition{ Method: strings.ToUpper(method), Path: cleanPath(path), Handlers: handlers, Middlewares: append([]any(nil), r.middlewares...), } r.routes = append(r.routes, rd) return rd } // GET registers a GET route. func (r *RouterRegistry) GET(path string, handlers ...any) RouteDefinition { return r.Handle("GET", path, handlers...) } // POST registers a POST route. func (r *RouterRegistry) POST(path string, handlers ...any) RouteDefinition { return r.Handle("POST", path, handlers...) } // PUT registers a PUT route. func (r *RouterRegistry) PUT(path string, handlers ...any) RouteDefinition { return r.Handle("PUT", path, handlers...) } // DELETE registers a DELETE route. func (r *RouterRegistry) DELETE(path string, handlers ...any) RouteDefinition { return r.Handle("DELETE", path, handlers...) } // PATCH registers a PATCH route. func (r *RouterRegistry) PATCH(path string, handlers ...any) RouteDefinition { return r.Handle("PATCH", path, handlers...) } // HEAD registers a HEAD route. func (r *RouterRegistry) HEAD(path string, handlers ...any) RouteDefinition { return r.Handle("HEAD", path, handlers...) } // OPTIONS registers an OPTIONS route. func (r *RouterRegistry) OPTIONS(path string, handlers ...any) RouteDefinition { return r.Handle("OPTIONS", path, handlers...) } // Any registers a route for standard HTTP methods. func (r *RouterRegistry) Any(path string, handlers ...any) []RouteDefinition { methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"} defs := make([]RouteDefinition, 0, len(methods)) for _, m := range methods { defs = append(defs, r.Handle(m, path, handlers...)) } return defs } // Routes returns a copy of all collected RouteDefinitions. func (r *RouterRegistry) Routes() []RouteDefinition { r.mu.RLock() defer r.mu.RUnlock() res := make([]RouteDefinition, len(r.routes)) copy(res, r.routes) return res } // RouterGroup represents a scoped route group with a path prefix and group-level middlewares. type RouterGroup struct { registry *RouterRegistry prefix string middlewares []any } // Use adds middlewares to this group. func (g *RouterGroup) Use(middlewares ...any) { g.middlewares = append(g.middlewares, middlewares...) } // Group creates a nested RouteGroup. func (g *RouterGroup) Group(prefix string, middlewares ...any) RouterExtension { combinedPrefix := joinPaths(g.prefix, prefix) combinedMiddlewares := make([]any, 0, len(g.middlewares)+len(middlewares)) combinedMiddlewares = append(combinedMiddlewares, g.middlewares...) combinedMiddlewares = append(combinedMiddlewares, middlewares...) return &RouterGroup{ registry: g.registry, prefix: combinedPrefix, middlewares: combinedMiddlewares, } } // Handle registers a route under this group. func (g *RouterGroup) Handle(method, path string, handlers ...any) RouteDefinition { g.registry.mu.Lock() defer g.registry.mu.Unlock() fullPath := joinPaths(g.prefix, path) allMiddlewares := make([]any, 0, len(g.registry.middlewares)+len(g.middlewares)) allMiddlewares = append(allMiddlewares, g.registry.middlewares...) allMiddlewares = append(allMiddlewares, g.middlewares...) rd := RouteDefinition{ Method: strings.ToUpper(method), Path: fullPath, Handlers: handlers, Middlewares: allMiddlewares, } g.registry.routes = append(g.registry.routes, rd) return rd } // GET registers a GET route in this group. func (g *RouterGroup) GET(path string, handlers ...any) RouteDefinition { return g.Handle("GET", path, handlers...) } // POST registers a POST route in this group. func (g *RouterGroup) POST(path string, handlers ...any) RouteDefinition { return g.Handle("POST", path, handlers...) } // PUT registers a PUT route in this group. func (g *RouterGroup) PUT(path string, handlers ...any) RouteDefinition { return g.Handle("PUT", path, handlers...) } // DELETE registers a DELETE route in this group. func (g *RouterGroup) DELETE(path string, handlers ...any) RouteDefinition { return g.Handle("DELETE", path, handlers...) } // PATCH registers a PATCH route in this group. func (g *RouterGroup) PATCH(path string, handlers ...any) RouteDefinition { return g.Handle("PATCH", path, handlers...) } // HEAD registers a HEAD route in this group. func (g *RouterGroup) HEAD(path string, handlers ...any) RouteDefinition { return g.Handle("HEAD", path, handlers...) } // OPTIONS registers an OPTIONS route in this group. func (g *RouterGroup) OPTIONS(path string, handlers ...any) RouteDefinition { return g.Handle("OPTIONS", path, handlers...) } // Any registers a route in this group for standard HTTP methods. func (g *RouterGroup) Any(path string, handlers ...any) []RouteDefinition { methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"} defs := make([]RouteDefinition, 0, len(methods)) for _, m := range methods { defs = append(defs, g.Handle(m, path, handlers...)) } return defs } // Routes returns all routes from the parent registry. func (g *RouterGroup) Routes() []RouteDefinition { return g.registry.Routes() } // Middlewares returns a copy of the group's middlewares. func (g *RouterGroup) Middlewares() []any { res := make([]any, len(g.middlewares)) copy(res, g.middlewares) return res } func cleanPath(p string) string { if p == "" { return "/" } if !strings.HasPrefix(p, "/") { p = "/" + p } if len(p) > 1 && strings.HasSuffix(p, "/") { p = strings.TrimSuffix(p, "/") } return p } func joinPaths(base, relative string) string { if base == "" || base == "/" { return cleanPath(relative) } if relative == "" || relative == "/" { return cleanPath(base) } base = strings.TrimSuffix(base, "/") relative = strings.TrimPrefix(relative, "/") return cleanPath(base + "/" + relative) }