package server import ( "net/http" "strings" ) // Router is a lightweight wrapper around http.ServeMux that adds middleware // groups and sub-router mounting. It leverages Go 1.22+ enhanced patterns // like "GET /users/{id}". type Router struct { mux *http.ServeMux prefix string middlewares []Middleware } // NewRouter creates a new Router backed by a fresh http.ServeMux. func NewRouter() *Router { return &Router{ mux: http.NewServeMux(), } } // Handle registers a handler for the given pattern. The pattern follows // http.ServeMux conventions, including method-based patterns like "GET /users". func (r *Router) Handle(pattern string, handler http.Handler) { if len(r.middlewares) > 0 { handler = Chain(r.middlewares...)(handler) } r.mux.Handle(r.prefixedPattern(pattern), handler) } // HandleFunc registers a handler function for the given pattern. func (r *Router) HandleFunc(pattern string, fn http.HandlerFunc) { r.Handle(pattern, fn) } // Group creates a sub-router with a shared prefix and optional middleware. // Patterns registered on the group are prefixed automatically. The group // shares the underlying ServeMux with the parent router. // // Example: // // api := router.Group("/api/v1", authMiddleware) // api.HandleFunc("GET /users", listUsers) // registers "GET /api/v1/users" func (r *Router) Group(prefix string, mws ...Middleware) *Router { return &Router{ mux: r.mux, prefix: r.prefix + prefix, middlewares: append(r.middlewaresSnapshot(), mws...), } } // Mount attaches an http.Handler under the given prefix. All requests // starting with prefix are forwarded to the handler with the prefix stripped. func (r *Router) Mount(prefix string, handler http.Handler) { full := r.prefix + prefix if !strings.HasSuffix(full, "/") { full += "/" } r.mux.Handle(full, http.StripPrefix(strings.TrimSuffix(full, "/"), handler)) } // ServeHTTP implements http.Handler, making Router usable as a handler. func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { r.mux.ServeHTTP(w, req) } // prefixedPattern inserts the router prefix into a pattern. It is aware of // method prefixes: "GET /users" with prefix "/api" becomes "GET /api/users". func (r *Router) prefixedPattern(pattern string) string { if r.prefix == "" { return pattern } // Split method prefix if present: "GET /users" → method="GET ", path="/users" method, path, hasMethod := splitMethodPattern(pattern) path = r.prefix + path if hasMethod { return method + path } return path } // splitMethodPattern splits "GET /path" into ("GET ", "/path", true). // If there is no method prefix, returns ("", pattern, false). func splitMethodPattern(pattern string) (method, path string, hasMethod bool) { if idx := strings.IndexByte(pattern, ' '); idx >= 0 { return pattern[:idx+1], pattern[idx+1:], true } return "", pattern, false } func (r *Router) middlewaresSnapshot() []Middleware { if len(r.middlewares) == 0 { return nil } cp := make([]Middleware, len(r.middlewares)) copy(cp, r.middlewares) return cp }