mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 23:56:37 +08:00
feat(core): add HandleRaw and BasePath for trailing-slash routes
This commit is contained in:
@@ -22,6 +22,11 @@ type RouterExtension interface {
|
|||||||
Use(middlewares ...any)
|
Use(middlewares ...any)
|
||||||
Group(prefix string, middlewares ...any) RouterExtension
|
Group(prefix string, middlewares ...any) RouterExtension
|
||||||
Handle(method, path string, handlers ...any) RouteDefinition
|
Handle(method, path string, handlers ...any) RouteDefinition
|
||||||
|
// HandleRaw joins path with the group prefix but preserves a trailing slash,
|
||||||
|
// so `/resource` and `/resource/` can coexist as distinct routes.
|
||||||
|
HandleRaw(method, path string, handlers ...any) RouteDefinition
|
||||||
|
// BasePath reports this group's absolute prefix ("" for the root registry).
|
||||||
|
BasePath() string
|
||||||
GET(path string, handlers ...any) RouteDefinition
|
GET(path string, handlers ...any) RouteDefinition
|
||||||
POST(path string, handlers ...any) RouteDefinition
|
POST(path string, handlers ...any) RouteDefinition
|
||||||
PUT(path string, handlers ...any) RouteDefinition
|
PUT(path string, handlers ...any) RouteDefinition
|
||||||
@@ -80,6 +85,11 @@ func (r *RouterRegistry) Group(prefix string, middlewares ...any) RouterExtensio
|
|||||||
|
|
||||||
// Handle registers a route with a custom HTTP method and handlers.
|
// Handle registers a route with a custom HTTP method and handlers.
|
||||||
func (r *RouterRegistry) Handle(method, path string, handlers ...any) RouteDefinition {
|
func (r *RouterRegistry) Handle(method, path string, handlers ...any) RouteDefinition {
|
||||||
|
return r.addRoute(method, cleanPath(path), handlers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRoute appends a route whose path is already normalised.
|
||||||
|
func (r *RouterRegistry) addRoute(method, fullPath string, handlers ...any) RouteDefinition {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
@@ -87,7 +97,7 @@ func (r *RouterRegistry) Handle(method, path string, handlers ...any) RouteDefin
|
|||||||
rd := RouteDefinition{
|
rd := RouteDefinition{
|
||||||
ID: r.nextID,
|
ID: r.nextID,
|
||||||
Method: strings.ToUpper(method),
|
Method: strings.ToUpper(method),
|
||||||
Path: cleanPath(path),
|
Path: fullPath,
|
||||||
Handlers: handlers,
|
Handlers: handlers,
|
||||||
Middlewares: append([]any(nil), r.middlewares...),
|
Middlewares: append([]any(nil), r.middlewares...),
|
||||||
}
|
}
|
||||||
@@ -223,11 +233,14 @@ func (g *RouterGroup) Group(prefix string, middlewares ...any) RouterExtension {
|
|||||||
|
|
||||||
// Handle registers a route under this group.
|
// Handle registers a route under this group.
|
||||||
func (g *RouterGroup) Handle(method, path string, handlers ...any) RouteDefinition {
|
func (g *RouterGroup) Handle(method, path string, handlers ...any) RouteDefinition {
|
||||||
|
return g.addRoute(method, joinPaths(g.prefix, path), handlers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRoute appends a route under this group whose path is already joined.
|
||||||
|
func (g *RouterGroup) addRoute(method, fullPath string, handlers ...any) RouteDefinition {
|
||||||
g.registry.mu.Lock()
|
g.registry.mu.Lock()
|
||||||
defer g.registry.mu.Unlock()
|
defer g.registry.mu.Unlock()
|
||||||
|
|
||||||
fullPath := joinPaths(g.prefix, path)
|
|
||||||
|
|
||||||
allMiddlewares := make([]any, 0, len(g.registry.middlewares)+len(g.middlewares))
|
allMiddlewares := make([]any, 0, len(g.registry.middlewares)+len(g.middlewares))
|
||||||
allMiddlewares = append(allMiddlewares, g.registry.middlewares...)
|
allMiddlewares = append(allMiddlewares, g.registry.middlewares...)
|
||||||
allMiddlewares = append(allMiddlewares, g.middlewares...)
|
allMiddlewares = append(allMiddlewares, g.middlewares...)
|
||||||
@@ -503,3 +516,43 @@ func (w *PathWhitelist) Patterns() []string {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Raw path registration ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// ensureLeadingSlash normalises a path to start with exactly one "/" while
|
||||||
|
// preserving any trailing slash (unlike cleanPath).
|
||||||
|
func ensureLeadingSlash(p string) string {
|
||||||
|
if p == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(p, "/") {
|
||||||
|
return "/" + p
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// joinPathPreservingTrailing joins a group prefix and a relative path without
|
||||||
|
// stripping a trailing slash, so a group "/x" can serve both "/x" and "/x/".
|
||||||
|
func joinPathPreservingTrailing(base, relative string) string {
|
||||||
|
rel := ensureLeadingSlash(relative)
|
||||||
|
if base == "" || base == "/" {
|
||||||
|
return rel
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(cleanPath(base), "/") + rel
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleRaw registers a route on the root registry, preserving a trailing slash.
|
||||||
|
func (r *RouterRegistry) HandleRaw(method, path string, handlers ...any) RouteDefinition {
|
||||||
|
return r.addRoute(method, ensureLeadingSlash(path), handlers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasePath returns "" because the root registry has no prefix.
|
||||||
|
func (r *RouterRegistry) BasePath() string { return "" }
|
||||||
|
|
||||||
|
// HandleRaw registers a route under this group, preserving a trailing slash.
|
||||||
|
func (g *RouterGroup) HandleRaw(method, path string, handlers ...any) RouteDefinition {
|
||||||
|
return g.addRoute(method, joinPathPreservingTrailing(g.prefix, path), handlers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasePath returns this group's absolute prefix.
|
||||||
|
func (g *RouterGroup) BasePath() string { return g.prefix }
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package extpoints
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestHandleRawPreservesTrailingSlash(t *testing.T) {
|
||||||
|
r := &RouterRegistry{}
|
||||||
|
g := r.Group("/api/v1/nodes")
|
||||||
|
|
||||||
|
if got := g.BasePath(); got != "/api/v1/nodes" {
|
||||||
|
t.Fatalf("BasePath() = %q, want %q", got, "/api/v1/nodes")
|
||||||
|
}
|
||||||
|
slashless := g.Handle("GET", "")
|
||||||
|
slashed := g.HandleRaw("GET", "/")
|
||||||
|
|
||||||
|
if slashless.Path != "/api/v1/nodes" {
|
||||||
|
t.Errorf("Handle(\"\") path = %q, want %q", slashless.Path, "/api/v1/nodes")
|
||||||
|
}
|
||||||
|
if slashed.Path != "/api/v1/nodes/" {
|
||||||
|
t.Errorf("HandleRaw(\"/\") path = %q, want %q", slashed.Path, "/api/v1/nodes/")
|
||||||
|
}
|
||||||
|
if slashed.ID == slashless.ID {
|
||||||
|
t.Error("HandleRaw must allocate its own route ID")
|
||||||
|
}
|
||||||
|
if got := len(r.Routes()); got != 2 {
|
||||||
|
t.Errorf("registry routes = %d, want 2", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,20 @@ func (s *scopedRouterExtension) Handle(method, path string, handlers ...any) ext
|
|||||||
return rd
|
return rd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleRaw registers a trailing-slash-preserving route and tears it down with the scope.
|
||||||
|
func (s *scopedRouterExtension) HandleRaw(method, path string, handlers ...any) extpoints.RouteDefinition {
|
||||||
|
rd := s.underlying.HandleRaw(method, path, handlers...)
|
||||||
|
routeID := rd.ID
|
||||||
|
s.ctx.OnDispose(func() error {
|
||||||
|
s.underlying.UnregisterByID(routeID)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return rd
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasePath delegates to the wrapped group prefix.
|
||||||
|
func (s *scopedRouterExtension) BasePath() string { return s.underlying.BasePath() }
|
||||||
|
|
||||||
func (s *scopedRouterExtension) GET(path string, handlers ...any) extpoints.RouteDefinition {
|
func (s *scopedRouterExtension) GET(path string, handlers ...any) extpoints.RouteDefinition {
|
||||||
return s.Handle("GET", path, handlers...)
|
return s.Handle("GET", path, handlers...)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user