Collect all routes globally and add a method to find the one that matches the path
Thereby getting the stack trace from where it was added.
This commit is contained in:
+140
-1
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -173,12 +174,144 @@ type HtmxRoute interface {
|
|||||||
ToUrlFromContext(c HtmxContext, language Language) string
|
ToUrlFromContext(c HtmxContext, language Language) string
|
||||||
GetHtmx(language Language, queryParams ...HtmxPathParam) gomponents.Node
|
GetHtmx(language Language, queryParams ...HtmxPathParam) gomponents.Node
|
||||||
SetActivePath(activePath *ActivePath) HtmxRoute
|
SetActivePath(activePath *ActivePath) HtmxRoute
|
||||||
|
GetPaths() LanguagePaths
|
||||||
Add(server HtmxServer)
|
Add(server HtmxServer)
|
||||||
Execute(c HtmxContext)
|
Execute(c HtmxContext)
|
||||||
RedirectToThisRoute(c HtmxContext, params LocationRedirectParams)
|
RedirectToThisRoute(c HtmxContext, params LocationRedirectParams)
|
||||||
IsCurrentRoute(c HtmxContext) bool
|
IsCurrentRoute(c HtmxContext) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GlobalHtmxRoutes interface {
|
||||||
|
GetRoutes() []HtmxRoute
|
||||||
|
Add(route HtmxRoute)
|
||||||
|
// MatchByPath tries to find a route whose template matches the given path
|
||||||
|
// even if the path contains path parameters. Returns (route, true) if found.
|
||||||
|
MatchByPath(path string) (*HtmxRouteInfo, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HtmxRouteInfo struct {
|
||||||
|
initTrace string
|
||||||
|
route HtmxRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
type GlobalHtmxRoutesImpl struct {
|
||||||
|
routes map[string]*HtmxRouteInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
var GlobalHtmxRoutesInstance GlobalHtmxRoutes = NewGlobalHtmxRoutesImpl()
|
||||||
|
|
||||||
|
func (g *GlobalHtmxRoutesImpl) GetRoutes() []HtmxRoute {
|
||||||
|
routes := make([]HtmxRoute, len(g.routes))
|
||||||
|
index := 0
|
||||||
|
for _, route := range g.routes {
|
||||||
|
Debug("index: ", index, "route: ", route, "")
|
||||||
|
routes[index] = route.route
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GlobalHtmxRoutesImpl) Add(route HtmxRoute) {
|
||||||
|
paths := route.GetPaths()
|
||||||
|
uniqueKey := ""
|
||||||
|
for language, path := range paths {
|
||||||
|
uniqueKey += string(language) + path
|
||||||
|
}
|
||||||
|
g.routes[uniqueKey] = &HtmxRouteInfo{
|
||||||
|
initTrace: string(debug.Stack()),
|
||||||
|
route: route,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchByPath tries to find a route whose language-specific template matches the given
|
||||||
|
// concrete URL path. It treats path parameters like {id} as wildcards.
|
||||||
|
func (g *GlobalHtmxRoutesImpl) MatchByPath(path string) (*HtmxRouteInfo, bool) {
|
||||||
|
p := normalizePath(path)
|
||||||
|
// Extract language as first segment (if present)
|
||||||
|
trim := strings.Trim(p, "/")
|
||||||
|
segs := []string{}
|
||||||
|
if trim != "" {
|
||||||
|
segs = strings.Split(trim, "/")
|
||||||
|
}
|
||||||
|
if len(segs) == 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
lang := Language(segs[0])
|
||||||
|
|
||||||
|
for _, info := range g.routes {
|
||||||
|
paths := info.route.GetPaths()
|
||||||
|
tpl, ok := paths[lang]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
candidate := addLanguageToPath(tpl, lang)
|
||||||
|
if matchTemplate(candidate, p) {
|
||||||
|
return info, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizePath(path string) string {
|
||||||
|
if path == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
// strip query string and fragment
|
||||||
|
if i := strings.Index(path, "?"); i >= 0 {
|
||||||
|
path = path[:i]
|
||||||
|
}
|
||||||
|
if i := strings.Index(path, "#"); i >= 0 {
|
||||||
|
path = path[:i]
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(path, "/") {
|
||||||
|
path = "/" + path
|
||||||
|
}
|
||||||
|
// remove duplicate slashes (basic)
|
||||||
|
for strings.Contains(path, "//") {
|
||||||
|
path = strings.ReplaceAll(path, "//", "/")
|
||||||
|
}
|
||||||
|
// drop trailing slash except root
|
||||||
|
if len(path) > 1 && strings.HasSuffix(path, "/") {
|
||||||
|
path = strings.TrimSuffix(path, "/")
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchTemplate(templatePath, concretePath string) bool {
|
||||||
|
// Normalize both
|
||||||
|
t := normalizePath(templatePath)
|
||||||
|
p := normalizePath(concretePath)
|
||||||
|
// Quick equality short-circuit
|
||||||
|
if t == p {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Compare segment-wise
|
||||||
|
tSegs := strings.Split(strings.Trim(t, "/"), "/")
|
||||||
|
pSegs := strings.Split(strings.Trim(p, "/"), "/")
|
||||||
|
if len(tSegs) != len(pSegs) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range tSegs {
|
||||||
|
seg := tSegs[i]
|
||||||
|
if isParamSegment(seg) {
|
||||||
|
// wildcard for one segment
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if seg != pSegs[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isParamSegment(seg string) bool {
|
||||||
|
return strings.HasPrefix(seg, "{") && strings.HasSuffix(seg, "}") && len(seg) > 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGlobalHtmxRoutesImpl() *GlobalHtmxRoutesImpl {
|
||||||
|
return &GlobalHtmxRoutesImpl{routes: make(map[string]*HtmxRouteInfo)}
|
||||||
|
}
|
||||||
|
|
||||||
func addLanguageToPath(path string, language Language) string {
|
func addLanguageToPath(path string, language Language) string {
|
||||||
return "/" + string(language) + path
|
return "/" + string(language) + path
|
||||||
}
|
}
|
||||||
@@ -192,6 +325,10 @@ type CommonHtmxRoute struct {
|
|||||||
HtmxMethod HtmxHttpMethodFunction
|
HtmxMethod HtmxHttpMethodFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h CommonHtmxRoute) GetPaths() LanguagePaths {
|
||||||
|
return h.Paths
|
||||||
|
}
|
||||||
|
|
||||||
func (h CommonHtmxRoute) ToUrlFromContext(c HtmxContext, language Language) string {
|
func (h CommonHtmxRoute) ToUrlFromContext(c HtmxContext, language Language) string {
|
||||||
routeWithParams := h.WithPathParams(h.extractPathParams(c)...)
|
routeWithParams := h.WithPathParams(h.extractPathParams(c)...)
|
||||||
queryParams := c.GetQueryParams()
|
queryParams := c.GetQueryParams()
|
||||||
@@ -218,7 +355,9 @@ func (h CommonHtmxRoute) RedirectToThisRoute(c HtmxContext, params LocationRedir
|
|||||||
|
|
||||||
func NewCommonHtmxRoute(routeFunc HtmxRouteFunc, paths LanguagePaths, method string,
|
func NewCommonHtmxRoute(routeFunc HtmxRouteFunc, paths LanguagePaths, method string,
|
||||||
htmxMethod HtmxHttpMethodFunction) *CommonHtmxRoute {
|
htmxMethod HtmxHttpMethodFunction) *CommonHtmxRoute {
|
||||||
return &CommonHtmxRoute{RouteFunc: routeFunc, Paths: paths, Method: method, HtmxMethod: htmxMethod}
|
route := &CommonHtmxRoute{RouteFunc: routeFunc, Paths: paths, Method: method, HtmxMethod: htmxMethod}
|
||||||
|
GlobalHtmxRoutesInstance.Add(route)
|
||||||
|
return route
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h CommonHtmxRoute) Execute(c HtmxContext) {
|
func (h CommonHtmxRoute) Execute(c HtmxContext) {
|
||||||
|
|||||||
Reference in New Issue
Block a user