budgit/internal/router/router.go
2026-04-11 15:38:07 +00:00

173 lines
5.1 KiB
Go

package router
import (
"net/http"
"time"
"git.juancwu.dev/juancwu/budgit/internal/middleware"
"git.juancwu.dev/juancwu/budgit/internal/routeurl"
)
type Group struct {
prefix string
middleware []middleware.Middleware
limiter *middleware.RateLimiter
parent *Group
mux *http.ServeMux
}
// Route is returned by route-registration calls so callers can attach
// metadata to a freshly registered route (e.g. a name for URL lookup).
type Route struct {
path string
}
// Name registers this route under the given name so templates can resolve
// it via routeurl.URL. The stored path keeps Go 1.22 named wildcards intact
// (e.g. "/join/{token}") so URL() can substitute them at render time.
func (r *Route) Name(name string) *Route {
routeurl.Register(name, r.path)
return r
}
func (g *Group) Use(mw ...middleware.Middleware) {
g.middleware = append(g.middleware, mw...)
}
// RateLimit sets a rate limit on this group. It runs before any middleware
// in the chain, including inherited middleware from parent groups.
// Parent group rate limits are checked first (root → leaf order).
func (g *Group) RateLimit(limit int, window time.Duration) {
g.limiter = middleware.NewRateLimiter(limit, window)
}
type Method string
const (
MethodGet Method = "GET"
MethodPost Method = "POST"
MethodPut Method = "PUT"
MethodDelete Method = "DELETE"
MethodPatch Method = "PATCH"
)
func (g *Group) Handle(method Method, path string, handler http.HandlerFunc, mw ...middleware.Middleware) *Route {
// Build chain: [rate limiters root→self] → [middleware root→self] → [route mw] → handler
rateLimiters := g.collectRateLimiters()
middlewares := g.collectMiddleware()
middlewares = append(middlewares, mw...)
chain := append(rateLimiters, middlewares...)
fullPath := g.prefix + path
pattern := string(method) + " " + fullPath
wrapped := middleware.Chain(handler, chain...)
g.mux.Handle(pattern, wrapped)
return &Route{path: fullPath}
}
func (g *Group) Get(path string, h http.HandlerFunc, mw ...middleware.Middleware) *Route {
return g.Handle(MethodGet, path, h, mw...)
}
func (g *Group) Post(path string, h http.HandlerFunc, mw ...middleware.Middleware) *Route {
return g.Handle(MethodPost, path, h, mw...)
}
func (g *Group) Put(path string, h http.HandlerFunc, mw ...middleware.Middleware) *Route {
return g.Handle(MethodPut, path, h, mw...)
}
func (g *Group) Patch(path string, h http.HandlerFunc, mw ...middleware.Middleware) *Route {
return g.Handle(MethodPatch, path, h, mw...)
}
func (g *Group) Delete(path string, h http.HandlerFunc, mw ...middleware.Middleware) *Route {
return g.Handle(MethodDelete, path, h, mw...)
}
// SubGroup creates a nested group. It inherits rate limits and middleware
// from the parent via the parent pointer (not by copying).
func (g *Group) SubGroup(prefix string, fn func(*Group)) {
sub := &Group{
prefix: g.prefix + prefix,
parent: g,
mux: g.mux,
}
fn(sub)
}
// collectRateLimiters walks up the parent chain and returns rate limit
// middleware in root → leaf order.
func (g *Group) collectRateLimiters() []middleware.Middleware {
var result []middleware.Middleware
if g.parent != nil {
result = g.parent.collectRateLimiters()
}
if g.limiter != nil {
result = append(result, g.limiter.Middleware())
}
return result
}
// collectMiddleware walks up the parent chain and returns middleware
// in root → leaf order.
func (g *Group) collectMiddleware() []middleware.Middleware {
var result []middleware.Middleware
if g.parent != nil {
result = g.parent.collectMiddleware()
}
result = append(result, g.middleware...)
return result
}
type Router struct {
root *Group
mux *http.ServeMux
}
func New() *Router {
mux := http.NewServeMux()
return &Router{
mux: mux,
root: &Group{
mux: mux,
},
}
}
func (r *Router) Mux() *http.ServeMux {
return r.mux
}
func (r *Router) Use(mw ...middleware.Middleware) {
r.root.Use(mw...)
}
// Group creates a route group that inherits global middleware from the router.
func (r *Router) Group(prefix string, fn func(*Group)) {
r.root.SubGroup(prefix, fn)
}
// Handler returns the final http.Handler. All middleware is already applied
// per-route through the group hierarchy, so this just returns the mux.
func (r *Router) Handler() http.Handler {
return r.mux
}
func (r *Router) Handle(method Method, path string, handler http.HandlerFunc, mw ...middleware.Middleware) *Route {
return r.root.Handle(method, path, handler, mw...)
}
func (r *Router) Get(path string, h http.HandlerFunc, mw ...middleware.Middleware) *Route {
return r.root.Get(path, h, mw...)
}
func (r *Router) Post(path string, h http.HandlerFunc, mw ...middleware.Middleware) *Route {
return r.root.Post(path, h, mw...)
}
func (r *Router) Put(path string, h http.HandlerFunc, mw ...middleware.Middleware) *Route {
return r.root.Put(path, h, mw...)
}
func (r *Router) Patch(path string, h http.HandlerFunc, mw ...middleware.Middleware) *Route {
return r.root.Patch(path, h, mw...)
}
func (r *Router) Delete(path string, h http.HandlerFunc, mw ...middleware.Middleware) *Route {
return r.root.Delete(path, h, mw...)
}