110 lines
3.7 KiB
Go
110 lines
3.7 KiB
Go
package gus
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"tildegit.org/tjp/gus/internal"
|
|
)
|
|
|
|
// Router stores a mapping of request path patterns to handlers.
|
|
//
|
|
// Pattern may begin with "/" and then contain slash-delimited segments.
|
|
// - Segments beginning with colon (:) are wildcards and will match any path
|
|
// segment at that location. It may optionally have a word after the colon,
|
|
// which will be the parameter name the path segment is captured into.
|
|
// - Segments beginning with asterisk (*) are remainder wildcards. This must
|
|
// come last and will capture any remainder of the path. It may have a name
|
|
// after the asterisk which will be the parameter name.
|
|
// - Any other segment in the pattern must match a path segment exactly.
|
|
//
|
|
// These patterns do not match any path which shares a prefix, rather then
|
|
// full path must match a pattern. If you want to only match a prefix of the
|
|
// path you can end the pattern with a *remainder segment.
|
|
//
|
|
// The zero value is a usable Router which will fail to match any requst path.
|
|
type Router struct {
|
|
tree internal.PathTree[Handler]
|
|
|
|
middleware []Middleware
|
|
routeAdded bool
|
|
}
|
|
|
|
// Route adds a handler to the router under a path pattern.
|
|
func (r *Router) Route(pattern string, handler Handler) {
|
|
for i := len(r.middleware) - 1; i >= 0; i-- {
|
|
handler = r.middleware[i](handler)
|
|
}
|
|
r.tree.Add(pattern, handler)
|
|
r.routeAdded = true
|
|
}
|
|
|
|
// Handler matches against the request path and dipatches to a route handler.
|
|
//
|
|
// If no route matches, it returns a nil response.
|
|
// Captured path parameters will be stored in the context passed into the handler
|
|
// and can be retrieved with RouteParams().
|
|
func (r Router) Handler(ctx context.Context, request *Request) *Response {
|
|
handler, params := r.Match(request)
|
|
if handler == nil {
|
|
return nil
|
|
}
|
|
|
|
return handler.Handle(context.WithValue(ctx, routeParamsKey, params), request)
|
|
}
|
|
|
|
// Match returns the matched handler and captured path parameters, or (nil, nil).
|
|
//
|
|
// The returned handlers will be wrapped with any middleware attached to the router.
|
|
func (r Router) Match(request *Request) (Handler, map[string]string) {
|
|
handler, params := r.tree.Match(request.Path)
|
|
if handler == nil {
|
|
return nil, nil
|
|
}
|
|
return *handler, params
|
|
}
|
|
|
|
// Mount attaches a sub-router to handle path suffixes after an initial prefix pattern.
|
|
//
|
|
// The prefix pattern may include segment :wildcards, but no *remainder segment. The
|
|
// mounted sub-router should have patterns which only include the portion of the path
|
|
// after whatever was matched by the prefix pattern.
|
|
func (r *Router) Mount(prefix string, subrouter *Router) {
|
|
prefix = strings.TrimSuffix(prefix, "/")
|
|
|
|
for _, subroute := range subrouter.tree.Routes() {
|
|
r.Route(prefix+"/"+subroute.Pattern, subroute.Value)
|
|
}
|
|
}
|
|
|
|
// Use attaches a middleware to the router.
|
|
//
|
|
// Any routes set on the router will have their handlers decorated by the attached
|
|
// middlewares in reverse order (the first middleware attached will be the outer-most:
|
|
// first to see requests and the last to see responses).
|
|
//
|
|
// Use will panic if Route or Mount have already been called on the router -
|
|
// middlewares must be set before any routes.
|
|
func (r *Router) Use(mw Middleware) {
|
|
if r.routeAdded {
|
|
panic("all middlewares must be added prior to adding routes")
|
|
}
|
|
r.middleware = append(r.middleware, mw)
|
|
}
|
|
|
|
// RouteParams gathers captured path parameters from the request context.
|
|
//
|
|
// If the context doesn't contain a parameter map, it returns nil.
|
|
// If Router was used but no parameters were captured in the pattern, it
|
|
// returns a non-nil empty map.
|
|
func RouteParams(ctx context.Context) map[string]string {
|
|
if m, ok := ctx.Value(routeParamsKey).(map[string]string); ok {
|
|
return m
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type routeParamsKeyType struct{}
|
|
|
|
var routeParamsKey = routeParamsKeyType{}
|