@@ -25,6 +25,54 @@ type Router struct {
2525 optimizedHandlers * []* optimizedHandler // for finalization optimization
2626 finalized bool
2727 debugLog bool // enable debug logging for auto-promotion
28+
29+ // Homing support for late propagation
30+ parents []parentRouter // parent routers to notify of new routes
31+ }
32+
33+ type parentRouter struct {
34+ router * Router
35+ pathPrefix string
36+ namePrefix string
37+ }
38+
39+ // MountBuilder provides a fluent API for building mounted routers
40+ type MountBuilder struct {
41+ router * Router
42+ subRouter * Router
43+ pathPrefix string
44+ namePrefix string
45+ }
46+
47+ // Name assigns a name prefix to all routes in the mounted sub-router
48+ func (mb * MountBuilder ) Name (name string ) * MountBuilder {
49+ if mb .subRouter == nil {
50+ return mb
51+ }
52+
53+ if name != "" && ! strings .HasSuffix (name , "." ) {
54+ name += "."
55+ }
56+
57+ // Update the prefix for all future routes
58+ newNamePrefix := mb .router .namePrefix + name
59+
60+ // Find the parent entry in the subRouter and update it
61+ for i := range mb .subRouter .parents {
62+ p := & mb .subRouter .parents [i ]
63+ if p .router == mb .router && p .pathPrefix == mb .pathPrefix {
64+ // Found it! Update the prefix
65+ oldNamePrefix := p .namePrefix
66+ p .namePrefix = newNamePrefix
67+
68+ // Now we MUST also update any ALREADY propagated routes' names.
69+ // This is because propagateRoutes was called with the old prefix during Mount.
70+ mb .router .propagateRouteNames (oldNamePrefix , newNamePrefix , mb .subRouter )
71+ }
72+ }
73+
74+ mb .namePrefix = newNamePrefix
75+ return mb
2876}
2977
3078// RouteBuilder provides a fluent API for building routes
@@ -72,6 +120,11 @@ func (rb *RouteBuilder) Name(name string) *RouteBuilder {
72120 // Register in name index
73121 rb .router .nameIndex [fullName ] = rb .route
74122
123+ // Update parents if name was added late (homing)
124+ for _ , p := range rb .router .parents {
125+ p .router .propagateRouteName (p .namePrefix , rb .route )
126+ }
127+
75128 return rb
76129}
77130
@@ -265,6 +318,11 @@ func (r *Router) handleDirect(method, pattern string, handler http.Handler) *Rou
265318
266319 * r .routes = append (* r .routes , rt )
267320
321+ // Propagate to parents (Homing)
322+ for _ , p := range r .parents {
323+ p .router .propagateRoute (p .pathPrefix , p .namePrefix , rt )
324+ }
325+
268326 dispatcherKey := method + ":" + chiPattern
269327
270328 // Check if dispatcher already exists (from QueryGET/etc)
@@ -330,6 +388,11 @@ func (r *Router) handleQuery(method, pattern string, handler http.Handler) *Rout
330388
331389 * r .routes = append (* r .routes , rt )
332390
391+ // Propagate to parents (Homing)
392+ for _ , p := range r .parents {
393+ p .router .propagateRoute (p .pathPrefix , p .namePrefix , rt )
394+ }
395+
333396 // Get or create dispatcher for this method+pattern
334397 dispatcherKey := method + ":" + chiPattern
335398 disp , exists := r .dispatchers [dispatcherKey ]
@@ -460,40 +523,152 @@ func (r *Router) Use(middlewares ...func(http.Handler) http.Handler) {
460523 r .mux .Use (middlewares ... )
461524}
462525
526+ // MountNamed is like Mount, but allows specifying a name prefix for the sub-router's routes.
527+ // The provided namePrefix will be prepended to all routes in the sub-router.
528+ func (r * Router ) MountNamed (pattern , namePrefix string , handler http.Handler ) * MountBuilder {
529+ fullPattern := r .pathPrefix + pattern
530+
531+ if namePrefix != "" && ! strings .HasSuffix (namePrefix , "." ) {
532+ namePrefix += "."
533+ }
534+ fullNamePrefix := r .namePrefix + namePrefix
535+
536+ var subRouter * Router
537+ if sr , ok := handler .(* Router ); ok {
538+ subRouter = sr
539+
540+ // Check if we are already mounted at this pattern
541+ alreadyMounted := false
542+ for _ , p := range subRouter .parents {
543+ if p .router == r && p .pathPrefix == fullPattern {
544+ alreadyMounted = true
545+ break
546+ }
547+ }
548+
549+ if ! alreadyMounted {
550+ r .mux .Mount (pattern , handler )
551+
552+ // Late propagation of existing routes
553+ r .propagateRoutes (fullPattern , fullNamePrefix , subRouter )
554+
555+ // Set up "Homing" for future routes
556+ subRouter .parents = append (subRouter .parents , parentRouter {
557+ router : r ,
558+ pathPrefix : fullPattern ,
559+ namePrefix : fullNamePrefix ,
560+ })
561+ }
562+ } else {
563+ r .mux .Mount (pattern , handler )
564+ }
565+
566+ return & MountBuilder {
567+ router : r ,
568+ subRouter : subRouter ,
569+ pathPrefix : fullPattern ,
570+ namePrefix : fullNamePrefix ,
571+ }
572+ }
573+
463574// Mount attaches another http.Handler (typically a router) to a prefix.
464575// If the handler is a *teapot.Router, all its routes are propagated to the
465576// parent router with the prefix prepended for unified listing and URL generation.
466- func (r * Router ) Mount (pattern string , handler http.Handler ) {
467- fullPattern := r .pathPrefix + pattern
468- r .mux .Mount (pattern , handler )
469-
470- r .propagateRoutes (fullPattern , handler )
471- }
472-
473- func (r * Router ) propagateRoutes (prefix string , handler http.Handler ) {
474- // Propagate routes if it's a teapot Router
475- if subRouter , ok := handler .(* Router ); ok {
476- for _ , rt := range * subRouter .routes {
477- // Create a copy of the route with updated pattern and name
478- newRoute := & core.Route {
479- Method : rt .Method ,
480- Pattern : prefix + rt .Pattern ,
481- ChiPattern : prefix + rt .ChiPattern ,
482- Handler : rt .Handler ,
483- Name : r .namePrefix + rt .Name ,
484- Action : rt .Action ,
485- QueryMatchers : rt .QueryMatchers ,
486- Middlewares : rt .Middlewares ,
487- WildcardParams : rt .WildcardParams ,
577+ // Furthermore, future routes added to the sub-router will also be propagated.
578+ func (r * Router ) Mount (pattern string , handler http.Handler ) * MountBuilder {
579+ return r .MountNamed (pattern , "" , handler )
580+ }
581+
582+ func (r * Router ) propagateRoutes (pathPrefix , namePrefix string , subRouter * Router ) {
583+ for _ , rt := range * subRouter .routes {
584+ r .propagateRoute (pathPrefix , namePrefix , rt )
585+ }
586+ }
587+
588+ func (r * Router ) propagateRoute (pathPrefix , namePrefix string , rt * core.Route ) {
589+ // Create a copy of the route with updated pattern and name
590+ newRoute := & core.Route {
591+ Method : rt .Method ,
592+ Pattern : pathPrefix + rt .Pattern ,
593+ ChiPattern : pathPrefix + rt .ChiPattern ,
594+ Handler : rt .Handler ,
595+ Name : namePrefix + rt .Name ,
596+ Action : rt .Action ,
597+ QueryMatchers : rt .QueryMatchers ,
598+ Middlewares : rt .Middlewares ,
599+ WildcardParams : rt .WildcardParams ,
600+ OriginalRoute : rt , // Link to original for name updates
601+ }
602+ * r .routes = append (* r .routes , newRoute )
603+
604+ // Update name index for URL generation
605+ if newRoute .Name != "" {
606+ r .nameIndex [newRoute .Name ] = newRoute
607+ }
608+
609+ // Propagate further up if this router also has parents
610+ for _ , p := range r .parents {
611+ p .router .propagateRoute (p .pathPrefix , p .namePrefix , newRoute )
612+ }
613+ }
614+
615+ func (r * Router ) propagateRouteName (namePrefix string , originalRt * core.Route ) {
616+ for _ , rt := range * r .routes {
617+ if rt .OriginalRoute == originalRt {
618+ rt .Name = namePrefix + originalRt .Name
619+ if rt .Name != "" {
620+ r .nameIndex [rt .Name ] = rt
488621 }
489- * r .routes = append (* r .routes , newRoute )
490622
491- // Update name index for URL generation
492- if newRoute .Name != "" {
493- r .nameIndex [newRoute .Name ] = newRoute
623+ // Propagate further up
624+ for _ , p := range r .parents {
625+ p .router .propagateRouteName (p .namePrefix , rt )
626+ }
627+ return
628+ }
629+ }
630+ }
631+
632+ func (r * Router ) propagateRouteNames (oldPrefix , newPrefix string , subRouter * Router ) {
633+ for _ , rt := range * r .routes {
634+ if rt .OriginalRoute != nil && strings .HasPrefix (rt .Name , oldPrefix ) {
635+ // Check if this route actually belongs to the subRouter tree
636+ if r .belongsTo (rt , subRouter ) {
637+ // Remove old name from index
638+ if rt .Name != "" {
639+ delete (r .nameIndex , rt .Name )
640+ }
641+
642+ // Update name
643+ suffix := strings .TrimPrefix (rt .Name , oldPrefix )
644+ rt .Name = newPrefix + suffix
645+
646+ // Re-add to index
647+ if rt .Name != "" {
648+ r .nameIndex [rt .Name ] = rt
649+ }
650+
651+ // Propagate further up
652+ for _ , p := range r .parents {
653+ p .router .propagateRouteNames (p .namePrefix + oldPrefix , p .namePrefix + newPrefix , subRouter )
654+ }
655+ }
656+ }
657+ }
658+ }
659+
660+ func (r * Router ) belongsTo (rt * core.Route , subRouter * Router ) bool {
661+ // Follow OriginalRoute chain to see if it leads to one of subRouter's routes
662+ curr := rt .OriginalRoute
663+ for curr != nil {
664+ for _ , subRt := range * subRouter .routes {
665+ if curr == subRt {
666+ return true
494667 }
495668 }
669+ curr = curr .OriginalRoute
496670 }
671+ return false
497672}
498673
499674// SubRouter creates a new child router whose routes are automatically
0 commit comments