rewrite 2

This commit is contained in:
Sky Johnson 2025-04-26 11:12:58 -05:00
parent 5791042454
commit ba32300bd9
7 changed files with 722 additions and 89 deletions

18
LICENSE
View File

@ -1,9 +1,17 @@
MIT License
# Sharkk Minimal License
Copyright (c) 2024 Sharkk
Copyright © 2025 Sharkk, sharkk.net
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
You can freely use and modify this software if you follow these simple rules:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
1. **Share-Alike**: When sharing modified versions, use this same license.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2. **Share Source Code**: When distributing this software, make the source code available.
3. **No Attribution Needed**: You don't need to credit the original authors publicly, but all copyright notices within the source code must remain intact.
4. **Patent Protection**: Contributors won't sue you for patent infringement on this software.
5. **No Warranty**: This software has no guarantees. You use it at your own risk.
6. **No Liability**: The authors aren't responsible for any damages or problems that might happen when you use this software.

128
README.md
View File

@ -1,11 +1,14 @@
# Router
A high-performance router for Go with support for path parameters and wildcards. Sports an incredibly simple API over the top of a prefix tree data structure. Minimal allocations and, dare we say, 🔥*blazingly* fast🔥.
A high-performance router for Go with support for path parameters, wildcards, middleware, and route grouping. Sports an incredibly simple API over the top of a prefix tree data structure. Minimal allocations and, dare we say, 🔥*blazingly* fast🔥.
## Features
- Zero dependencies
- Fast path matching with radix tree structure
- Support for path parameters (`[id]`) and wildcards (`*path`)
- Middleware support for request processing pipelines
- Route grouping with shared prefixes and middleware
- Adapters for net/http and fasthttp (optional)
- Up to 15x faster than `http.ServeMux` for dynamic routes
- Zero allocations for static routes
@ -17,41 +20,120 @@ go get git.sharkk.net/Go/Router
## Usage
```go
type Handler func(params []string)
### Basic Routing
router := router.New()
```go
// Create a new router
r := router.New()
// Static routes
router.Get("/", func(params []string) {
r.Get("/", router.FuncHandler(func(params []string) {
fmt.Println("Root handler")
})
router.Get("/users/all", func(params []string) {
fmt.Println("All users")
})
}))
// Parameter routes
router.Get("/users/[id]", func(params []string) {
r.Get("/users/[id]", router.FuncHandler(func(params []string) {
userID := params[0]
fmt.Printf("User ID: %s\n", userID)
})
// Nested parameters
router.Get("/users/[id]/posts/[postId]", func(params []string) {
userID := params[0]
postID := params[1]
fmt.Printf("User %s, Post %s\n", userID, postID)
})
}))
// Wildcard routes
router.Get("/files/*path", func(params []string) {
r.Get("/files/*path", router.FuncHandler(func(params []string) {
filePath := params[0]
fmt.Printf("File path: %s\n", filePath)
})
}))
// Lookup routes
if handler, params, ok := router.Lookup("GET", "/users/123"); ok {
handler(params)
if handler, params, ok := r.Lookup("GET", "/users/123"); ok {
handler.Serve(params)
}
```
### Middleware
```go
// Create logging middleware
func LoggingMiddleware(next router.Handler) router.Handler {
return router.FuncHandler(func(params []string) {
fmt.Println("Request started")
next.Serve(params)
fmt.Println("Request completed")
})
}
// Apply middleware globally
r := router.New()
r.Use(LoggingMiddleware)
// Apply middleware to specific routes
r.WithMiddleware(AuthMiddleware).Get("/admin", adminHandler)
```
### Route Groups
```go
// Create a router
r := router.New()
// Create an API group
api := r.Group("/api")
api.Get("/users", listUsersHandler) // matches /api/users
// Nested groups
v1 := api.Group("/v1")
v1.Get("/products", listProductsHandler) // matches /api/v1/products
// Group with middleware
admin := api.Group("/admin")
admin.Use(AuthMiddleware)
admin.Get("/stats", statsHandler) // matches /api/admin/stats
```
### HTTP Integration
```go
// Standard net/http integration
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Find the matching handler
handler, params, found := router.Lookup(r.Method, r.URL.Path)
if !found {
http.NotFound(w, r)
return
}
// Create an HTTP-compatible handler
httpHandler := router.NewHTTP(w, r, func(w http.ResponseWriter, r *http.Request, params []string) {
fmt.Fprintf(w, "Hello, user %s", params[0])
})
// Execute the handler
httpHandler.Serve(params)
})
```
### FastHTTP Integration (Optional)
Build with `-tags fasthttp` to enable FastHTTP support:
```go
// FastHTTP integration
fastHandler := func(ctx *fasthttp.RequestCtx) {
path := string(ctx.Path())
method := string(ctx.Method())
handler, params, found := router.Lookup(method, path)
if !found {
ctx.Error("Not found", fasthttp.StatusNotFound)
return
}
// Create a FastHTTP-compatible handler
fastHandler := router.NewFastHTTP(ctx, func(ctx *fasthttp.RequestCtx, params []string) {
fmt.Fprintf(ctx, "Hello, user %s", params[0])
})
// Execute the handler
fastHandler.Serve(params)
}
```
@ -87,4 +169,4 @@ Key Performance Points:
## License
Licensed under MIT. [Take a look!](LICENSE)
Sharkk Minimal License; do what you like! [Take a look!](LICENSE)

22
fasthttpHandler.go Normal file
View File

@ -0,0 +1,22 @@
//go:build fasthttp
// +build fasthttp
package router
import "github.com/valyala/fasthttp"
// FastHTTPHandler adapts fasthttp handlers to the router.
type FastHTTPHandler struct {
ctx *fasthttp.RequestCtx
h func(ctx *fasthttp.RequestCtx, params []string)
}
// Serve executes the fasthttp handler with parameters.
func (f *FastHTTPHandler) Serve(params []string) {
f.h(f.ctx, params)
}
// NewFastHTTP creates a handler from a fasthttp handler function.
func NewFastHTTP(ctx *fasthttp.RequestCtx, h func(ctx *fasthttp.RequestCtx, params []string)) Handler {
return &FastHTTPHandler{ctx: ctx, h: h}
}

17
handler.go Normal file
View File

@ -0,0 +1,17 @@
package router
// Handler is an interface for handling HTTP requests with path parameters.
type Handler interface {
Serve(params []string)
}
// Middleware wraps a handler with additional functionality.
type Middleware func(Handler) Handler
// FuncHandler is a function that implements the Handler interface.
type FuncHandler func(params []string)
// Serve calls the handler function.
func (f FuncHandler) Serve(params []string) {
f(params)
}

20
httpHandler.go Normal file
View File

@ -0,0 +1,20 @@
package router
import "net/http"
// HTTPHandler adapts net/http handlers to the router.
type HTTPHandler struct {
w http.ResponseWriter
r *http.Request
h func(w http.ResponseWriter, r *http.Request, params []string)
}
// Serve executes the http handler with parameters.
func (h *HTTPHandler) Serve(params []string) {
h.h(h.w, h.r, params)
}
// NewHTTP creates a handler from a net/http handler function.
func NewHTTP(w http.ResponseWriter, r *http.Request, h func(w http.ResponseWriter, r *http.Request, params []string)) Handler {
return &HTTPHandler{w: w, r: r, h: h}
}

241
router.go
View File

@ -4,9 +4,6 @@ import (
"fmt"
)
// Handler processes HTTP requests with optional path parameters.
type Handler func(params []string)
// node represents a segment in the URL path and its handling logic.
type node struct {
segment string // the path segment this node matches
@ -18,52 +15,243 @@ type node struct {
}
// Router routes HTTP requests by method and path.
// It supports static paths, path parameters, and wildcards.
// It supports static paths, path parameters, wildcards, and middleware.
type Router struct {
get *node
post *node
put *node
patch *node
delete *node
get *node
post *node
put *node
patch *node
delete *node
middleware []Middleware // Global middleware
}
// Group represents a route group with a path prefix and shared middleware.
type Group struct {
router *Router
prefix string
middleware []Middleware
}
// New creates a new Router instance.
func New() *Router {
return &Router{
get: &node{},
post: &node{},
put: &node{},
patch: &node{},
delete: &node{},
get: &node{},
post: &node{},
put: &node{},
patch: &node{},
delete: &node{},
middleware: []Middleware{},
}
}
// HTTP method registration functions
// Routes can contain static segments (/users), parameters ([id]), and wildcards (*filepath)
// Use adds middleware to the router's global middleware stack.
func (r *Router) Use(middleware ...Middleware) *Router {
r.middleware = append(r.middleware, middleware...)
return r
}
// Group creates a new route group with the given path prefix.
func (r *Router) Group(prefix string) *Group {
return &Group{
router: r,
prefix: prefix,
middleware: []Middleware{},
}
}
// Use adds middleware to the group's middleware stack.
func (g *Group) Use(middleware ...Middleware) *Group {
g.middleware = append(g.middleware, middleware...)
return g
}
// Group creates a nested group with an additional prefix.
func (g *Group) Group(prefix string) *Group {
return &Group{
router: g.router,
prefix: g.prefix + prefix,
middleware: append([]Middleware{}, g.middleware...),
}
}
// applyMiddleware wraps a handler with middleware in reverse order.
func applyMiddleware(handler Handler, middleware []Middleware) Handler {
h := handler
for i := len(middleware) - 1; i >= 0; i-- {
h = middleware[i](h)
}
return h
}
// HTTP method registration functions for Router
// Get registers a handler for GET requests at the given path.
func (r *Router) Get(path string, handler Handler) error {
return r.addRoute(r.get, path, handler)
return r.addRoute(r.get, path, handler, r.middleware)
}
// Post registers a handler for POST requests at the given path.
func (r *Router) Post(path string, handler Handler) error {
return r.addRoute(r.post, path, handler)
return r.addRoute(r.post, path, handler, r.middleware)
}
// Put registers a handler for PUT requests at the given path.
func (r *Router) Put(path string, handler Handler) error {
return r.addRoute(r.put, path, handler)
return r.addRoute(r.put, path, handler, r.middleware)
}
// Patch registers a handler for PATCH requests at the given path.
func (r *Router) Patch(path string, handler Handler) error {
return r.addRoute(r.patch, path, handler)
return r.addRoute(r.patch, path, handler, r.middleware)
}
// Delete registers a handler for DELETE requests at the given path.
func (r *Router) Delete(path string, handler Handler) error {
return r.addRoute(r.delete, path, handler)
return r.addRoute(r.delete, path, handler, r.middleware)
}
// HTTP method registration functions for Group
// Get registers a handler for GET requests at the given path.
func (g *Group) Get(path string, handler Handler) error {
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.get, g.prefix+path, handler, middleware)
}
// Post registers a handler for POST requests at the given path.
func (g *Group) Post(path string, handler Handler) error {
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.post, g.prefix+path, handler, middleware)
}
// Put registers a handler for PUT requests at the given path.
func (g *Group) Put(path string, handler Handler) error {
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.put, g.prefix+path, handler, middleware)
}
// Patch registers a handler for PATCH requests at the given path.
func (g *Group) Patch(path string, handler Handler) error {
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.patch, g.prefix+path, handler, middleware)
}
// Delete registers a handler for DELETE requests at the given path.
func (g *Group) Delete(path string, handler Handler) error {
middleware := append([]Middleware{}, g.router.middleware...)
middleware = append(middleware, g.middleware...)
return g.router.addRoute(g.router.delete, g.prefix+path, handler, middleware)
}
// WithMiddleware applies specific middleware to the next route registration.
func (r *Router) WithMiddleware(middleware ...Middleware) *MiddlewareRouter {
return &MiddlewareRouter{
router: r,
middleware: middleware,
}
}
// WithMiddleware applies specific middleware to the next route registration.
func (g *Group) WithMiddleware(middleware ...Middleware) *MiddlewareGroup {
return &MiddlewareGroup{
group: g,
middleware: middleware,
}
}
// MiddlewareRouter handles route registration with specific middleware.
type MiddlewareRouter struct {
router *Router
middleware []Middleware
}
// MiddlewareGroup handles group route registration with specific middleware.
type MiddlewareGroup struct {
group *Group
middleware []Middleware
}
// HTTP method registration functions for MiddlewareRouter
// Get registers a handler for GET requests with specific middleware.
func (mr *MiddlewareRouter) Get(path string, handler Handler) error {
middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.get, path, handler, middleware)
}
// Post registers a handler for POST requests with specific middleware.
func (mr *MiddlewareRouter) Post(path string, handler Handler) error {
middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.post, path, handler, middleware)
}
// Put registers a handler for PUT requests with specific middleware.
func (mr *MiddlewareRouter) Put(path string, handler Handler) error {
middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.put, path, handler, middleware)
}
// Patch registers a handler for PATCH requests with specific middleware.
func (mr *MiddlewareRouter) Patch(path string, handler Handler) error {
middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.patch, path, handler, middleware)
}
// Delete registers a handler for DELETE requests with specific middleware.
func (mr *MiddlewareRouter) Delete(path string, handler Handler) error {
middleware := append([]Middleware{}, mr.router.middleware...)
middleware = append(middleware, mr.middleware...)
return mr.router.addRoute(mr.router.delete, path, handler, middleware)
}
// HTTP method registration functions for MiddlewareGroup
// Get registers a handler for GET requests with specific middleware.
func (mg *MiddlewareGroup) Get(path string, handler Handler) error {
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.get, mg.group.prefix+path, handler, middleware)
}
// Post registers a handler for POST requests with specific middleware.
func (mg *MiddlewareGroup) Post(path string, handler Handler) error {
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.post, mg.group.prefix+path, handler, middleware)
}
// Put registers a handler for PUT requests with specific middleware.
func (mg *MiddlewareGroup) Put(path string, handler Handler) error {
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.put, mg.group.prefix+path, handler, middleware)
}
// Patch registers a handler for PATCH requests with specific middleware.
func (mg *MiddlewareGroup) Patch(path string, handler Handler) error {
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.patch, mg.group.prefix+path, handler, middleware)
}
// Delete registers a handler for DELETE requests with specific middleware.
func (mg *MiddlewareGroup) Delete(path string, handler Handler) error {
middleware := append([]Middleware{}, mg.group.router.middleware...)
middleware = append(middleware, mg.group.middleware...)
middleware = append(middleware, mg.middleware...)
return mg.group.router.addRoute(mg.group.router.delete, mg.group.prefix+path, handler, middleware)
}
// readSegment extracts the next path segment starting at the given position.
@ -89,11 +277,12 @@ func readSegment(path string, start int) (segment string, end int, hasMore bool)
return path[start:end], end, end < len(path)
}
// addRoute adds a new route to the prefix tree.
// It validates wildcard positions and tracks parameter counts.
func (r *Router) addRoute(root *node, path string, handler Handler) error {
// addRoute adds a new route to the prefix tree with middleware.
func (r *Router) addRoute(root *node, path string, handler Handler, middleware []Middleware) error {
wrappedHandler := applyMiddleware(handler, middleware)
if path == "/" {
root.handler = handler
root.handler = wrappedHandler
return nil
}
@ -149,7 +338,7 @@ func (r *Router) addRoute(root *node, path string, handler Handler) error {
pos = newPos
}
current.handler = handler
current.handler = wrappedHandler
return nil
}

View File

@ -7,54 +7,55 @@ import (
assert "git.sharkk.net/Go/Assert"
)
func handler(t *testing.T, expectedParams []string) Handler {
return func(params []string) {
// Create a test handler that implements the Handler interface
func testHandler(t *testing.T, expectedParams []string) Handler {
return FuncHandler(func(params []string) {
assert.Equal(t, len(params), len(expectedParams))
for i, expected := range expectedParams {
assert.Equal(t, params[i], expected)
}
}
})
}
func TestRootPath(t *testing.T) {
r := New()
r.Get("/", handler(t, nil))
r.Get("/", testHandler(t, nil))
h, params, found := r.Lookup("GET", "/")
assert.True(t, found)
h(params)
h.Serve(params)
}
func TestStaticPath(t *testing.T) {
r := New()
r.Get("/users/all", handler(t, nil))
r.Get("/users/all", testHandler(t, nil))
h, params, found := r.Lookup("GET", "/users/all")
assert.True(t, found)
h(params)
h.Serve(params)
}
func TestSingleParameter(t *testing.T) {
r := New()
r.Get("/users/[id]", handler(t, []string{"123"}))
r.Get("/users/[id]", testHandler(t, []string{"123"}))
h, params, found := r.Lookup("GET", "/users/123")
assert.True(t, found)
h(params)
h.Serve(params)
}
func TestMultipleParameters(t *testing.T) {
r := New()
r.Get("/users/[id]/posts/[postId]", handler(t, []string{"123", "456"}))
r.Get("/users/[id]/posts/[postId]", testHandler(t, []string{"123", "456"}))
h, params, found := r.Lookup("GET", "/users/123/posts/456")
assert.True(t, found)
h(params)
h.Serve(params)
}
func TestNonExistentPath(t *testing.T) {
r := New()
r.Get("/users/[id]", handler(t, nil))
r.Get("/users/[id]", testHandler(t, nil))
_, _, found := r.Lookup("GET", "/posts/123")
assert.False(t, found)
@ -62,7 +63,7 @@ func TestNonExistentPath(t *testing.T) {
func TestWrongMethod(t *testing.T) {
r := New()
r.Get("/users/[id]", handler(t, nil))
r.Get("/users/[id]", testHandler(t, nil))
_, _, found := r.Lookup("POST", "/users/123")
assert.False(t, found)
@ -70,21 +71,21 @@ func TestWrongMethod(t *testing.T) {
func TestTrailingSlash(t *testing.T) {
r := New()
r.Get("/users/[id]", handler(t, []string{"123"}))
r.Get("/users/[id]", testHandler(t, []string{"123"}))
h, params, found := r.Lookup("GET", "/users/123/")
assert.True(t, found)
h(params)
h.Serve(params)
}
func TestDifferentMethods(t *testing.T) {
r := New()
r.Get("/test", handler(t, nil))
r.Post("/test", handler(t, nil))
r.Put("/test", handler(t, nil))
r.Patch("/test", handler(t, nil))
r.Delete("/test", handler(t, nil))
r.Get("/test", testHandler(t, nil))
r.Post("/test", testHandler(t, nil))
r.Put("/test", testHandler(t, nil))
r.Patch("/test", testHandler(t, nil))
r.Delete("/test", testHandler(t, nil))
methods := []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
for _, method := range methods {
@ -99,47 +100,250 @@ func TestWildcardPath(t *testing.T) {
r := New()
t.Run("simple wildcard", func(t *testing.T) {
err := r.Get("/files/*path", handler(t, []string{"docs/report.pdf"}))
err := r.Get("/files/*path", testHandler(t, []string{"docs/report.pdf"}))
assert.Nil(t, err)
h, params, found := r.Lookup("GET", "/files/docs/report.pdf")
assert.True(t, found)
h(params)
h.Serve(params)
})
t.Run("wildcard with empty path", func(t *testing.T) {
err := r.Get("/download/*filepath", handler(t, []string{""}))
err := r.Get("/download/*filepath", testHandler(t, []string{""}))
assert.Nil(t, err)
h, params, found := r.Lookup("GET", "/download/")
assert.True(t, found)
h(params)
h.Serve(params)
})
t.Run("wildcard with parameter", func(t *testing.T) {
err := r.Get("/users/[id]/*action", handler(t, []string{"123", "settings/profile/avatar"}))
err := r.Get("/users/[id]/*action", testHandler(t, []string{"123", "settings/profile/avatar"}))
assert.Nil(t, err)
h, params, found := r.Lookup("GET", "/users/123/settings/profile/avatar")
assert.True(t, found)
h(params)
h.Serve(params)
})
t.Run("multiple wildcards not allowed", func(t *testing.T) {
err := r.Get("/api/*version/*path", handler(t, nil))
err := r.Get("/api/*version/*path", testHandler(t, nil))
assert.NotNil(t, err)
})
t.Run("non-last wildcard not allowed", func(t *testing.T) {
err := r.Get("/api/*version/users", handler(t, nil))
err := r.Get("/api/*version/users", testHandler(t, nil))
assert.NotNil(t, err)
})
}
// Middleware Tests
func TestMiddleware(t *testing.T) {
t.Run("global middleware", func(t *testing.T) {
r := New()
// Track middleware execution
executed := false
r.Use(func(next Handler) Handler {
return FuncHandler(func(params []string) {
executed = true
next.Serve(params)
})
})
r.Get("/test", testHandler(t, nil))
h, params, found := r.Lookup("GET", "/test")
assert.True(t, found)
h.Serve(params)
assert.True(t, executed)
})
t.Run("multiple middleware", func(t *testing.T) {
r := New()
// Track middleware execution order
order := []int{}
r.Use(func(next Handler) Handler {
return FuncHandler(func(params []string) {
order = append(order, 1)
next.Serve(params)
order = append(order, 4)
})
})
r.Use(func(next Handler) Handler {
return FuncHandler(func(params []string) {
order = append(order, 2)
next.Serve(params)
order = append(order, 3)
})
})
r.Get("/test", FuncHandler(func(params []string) {
order = append(order, 0)
}))
h, params, found := r.Lookup("GET", "/test")
assert.True(t, found)
h.Serve(params)
// Check middleware execution order (first middleware wraps second)
assert.Equal(t, len(order), 5)
assert.Equal(t, order[0], 1) // First middleware enter
assert.Equal(t, order[1], 2) // Second middleware enter
assert.Equal(t, order[2], 0) // Handler
assert.Equal(t, order[3], 3) // Second middleware exit
assert.Equal(t, order[4], 4) // First middleware exit
})
t.Run("route-specific middleware", func(t *testing.T) {
r := New()
executed := false
middleware := func(next Handler) Handler {
return FuncHandler(func(params []string) {
executed = true
next.Serve(params)
})
}
r.WithMiddleware(middleware).Get("/test", testHandler(t, nil))
h, params, found := r.Lookup("GET", "/test")
assert.True(t, found)
h.Serve(params)
assert.True(t, executed)
})
}
// Group Tests
func TestGroup(t *testing.T) {
t.Run("simple group", func(t *testing.T) {
r := New()
// Create API group
api := r.Group("/api")
api.Get("/users", testHandler(t, nil))
h, params, found := r.Lookup("GET", "/api/users")
assert.True(t, found)
h.Serve(params)
})
t.Run("nested groups", func(t *testing.T) {
r := New()
// Create nested groups
api := r.Group("/api")
v1 := api.Group("/v1")
v1.Get("/users", testHandler(t, nil))
h, params, found := r.Lookup("GET", "/api/v1/users")
assert.True(t, found)
h.Serve(params)
})
t.Run("group middleware", func(t *testing.T) {
r := New()
executed := false
// Create group with middleware
api := r.Group("/api")
api.Use(func(next Handler) Handler {
return FuncHandler(func(params []string) {
executed = true
next.Serve(params)
})
})
api.Get("/users", testHandler(t, nil))
h, params, found := r.Lookup("GET", "/api/users")
assert.True(t, found)
h.Serve(params)
assert.True(t, executed)
})
t.Run("nested group middleware", func(t *testing.T) {
r := New()
order := []int{}
// Create group with middleware
api := r.Group("/api")
api.Use(func(next Handler) Handler {
return FuncHandler(func(params []string) {
order = append(order, 1)
next.Serve(params)
})
})
// Create nested group with additional middleware
v1 := api.Group("/v1")
v1.Use(func(next Handler) Handler {
return FuncHandler(func(params []string) {
order = append(order, 2)
next.Serve(params)
})
})
v1.Get("/users", FuncHandler(func(params []string) {
order = append(order, 3)
}))
h, params, found := r.Lookup("GET", "/api/v1/users")
assert.True(t, found)
h.Serve(params)
// Check middleware execution order
assert.Equal(t, len(order), 3)
assert.Equal(t, order[0], 1) // First middleware (from api group)
assert.Equal(t, order[1], 2) // Second middleware (from v1 group)
assert.Equal(t, order[2], 3) // Handler
})
t.Run("route-specific middleware in group", func(t *testing.T) {
r := New()
order := []int{}
// Create group with middleware
api := r.Group("/api")
api.Use(func(next Handler) Handler {
return FuncHandler(func(params []string) {
order = append(order, 1)
next.Serve(params)
})
})
// Add route with specific middleware
api.WithMiddleware(func(next Handler) Handler {
return FuncHandler(func(params []string) {
order = append(order, 2)
next.Serve(params)
})
}).Get("/users", FuncHandler(func(params []string) {
order = append(order, 3)
}))
h, params, found := r.Lookup("GET", "/api/users")
assert.True(t, found)
h.Serve(params)
// Check middleware execution order
assert.Equal(t, len(order), 3)
assert.Equal(t, order[0], 1) // Group middleware
assert.Equal(t, order[1], 2) // Route-specific middleware
assert.Equal(t, order[2], 3) // Handler
})
}
// Benchmarks
func BenchmarkRouterLookup(b *testing.B) {
r := New()
h := func(params []string) {}
h := FuncHandler(func(params []string) {})
// Setup routes for benchmarking
r.Get("/", h)
@ -180,7 +384,7 @@ func BenchmarkRouterLookup(b *testing.B) {
func BenchmarkParallelLookup(b *testing.B) {
r := New()
h := func(params []string) {}
h := FuncHandler(func(params []string) {})
r.Get("/users/[id]", h)
r.Get("/posts/[id]/comments", h)
@ -204,7 +408,7 @@ func BenchmarkParallelLookup(b *testing.B) {
func BenchmarkWildcardLookup(b *testing.B) {
r := New()
h := func(params []string) {}
h := FuncHandler(func(params []string) {})
// Setup routes for benchmarking
r.Get("/files/*path", h)
@ -223,13 +427,104 @@ func BenchmarkWildcardLookup(b *testing.B) {
})
}
func BenchmarkMiddleware(b *testing.B) {
passthrough := func(next Handler) Handler {
return FuncHandler(func(params []string) {
next.Serve(params)
})
}
simpleHandler := FuncHandler(func(params []string) {})
b.Run("no_middleware", func(b *testing.B) {
r := New()
r.Get("/test", simpleHandler)
b.ResetTimer()
for i := 0; i < b.N; i++ {
h, params, _ := r.Lookup("GET", "/test")
h.Serve(params)
}
})
b.Run("one_middleware", func(b *testing.B) {
r := New()
r.Use(passthrough)
r.Get("/test", simpleHandler)
b.ResetTimer()
for i := 0; i < b.N; i++ {
h, params, _ := r.Lookup("GET", "/test")
h.Serve(params)
}
})
b.Run("five_middleware", func(b *testing.B) {
r := New()
for i := 0; i < 5; i++ {
r.Use(passthrough)
}
r.Get("/test", simpleHandler)
b.ResetTimer()
for i := 0; i < b.N; i++ {
h, params, _ := r.Lookup("GET", "/test")
h.Serve(params)
}
})
}
func BenchmarkGroups(b *testing.B) {
simpleHandler := FuncHandler(func(params []string) {})
b.Run("flat_route", func(b *testing.B) {
r := New()
r.Get("/api/v1/users", simpleHandler)
b.ResetTimer()
for i := 0; i < b.N; i++ {
r.Lookup("GET", "/api/v1/users")
}
})
b.Run("grouped_route", func(b *testing.B) {
r := New()
api := r.Group("/api")
v1 := api.Group("/v1")
v1.Get("/users", simpleHandler)
b.ResetTimer()
for i := 0; i < b.N; i++ {
r.Lookup("GET", "/api/v1/users")
}
})
b.Run("grouped_route_with_middleware", func(b *testing.B) {
r := New()
api := r.Group("/api")
api.Use(func(next Handler) Handler {
return FuncHandler(func(params []string) {
next.Serve(params)
})
})
v1 := api.Group("/v1")
v1.Get("/users", simpleHandler)
b.ResetTimer()
for i := 0; i < b.N; i++ {
h, params, _ := r.Lookup("GET", "/api/v1/users")
h.Serve(params)
}
})
}
func BenchmarkComparison(b *testing.B) {
// Custom router setup
customRouter := New()
customRouter.Get("/", func(params []string) {})
customRouter.Get("/users/all", func(params []string) {})
customRouter.Get("/users/[id]", func(params []string) {})
customRouter.Get("/users/[id]/posts/[postId]", func(params []string) {})
customRouter.Get("/", FuncHandler(func(params []string) {}))
customRouter.Get("/users/all", FuncHandler(func(params []string) {}))
customRouter.Get("/users/[id]", FuncHandler(func(params []string) {}))
customRouter.Get("/users/[id]/posts/[postId]", FuncHandler(func(params []string) {}))
// Standard mux setup
mux := http.NewServeMux()