feat(13-01): register overlapping constrained routes in surf

- compile groups routes ServeMux refuses side by side into overlap families
  and registers each under one generated method-less pattern
- the family handler tries members in registration order on literals and
  Where constraints, sets their path values and runs their own wrapped chain
- no match answers the bare 404; a method mismatch answers ServeMux's 405
  and Allow for the same table without the overlap
- unsupported shapes (same shape, {name...}, shadowing route) fail at boot
- README and docs/services/routing.md describe the behaviour
This commit is contained in:
Jakub Zych
2026-10-03 06:22:20 +02:00
parent 6525d967c5
commit fbdeb20126
5 changed files with 936 additions and 3 deletions

View File

@@ -343,16 +343,38 @@ func (r *Router) compile() (http.Handler, error) {
if r.compileErr != nil {
return nil, r.compileErr
}
mux := http.NewServeMux()
for _, rt := range r.routes {
handlers := make([]http.Handler, len(r.routes))
for i, rt := range r.routes {
h, err := r.wrap(rt)
if err != nil {
return nil, err
}
if err := handleRoute(mux, rt, h); err != nil {
handlers[i] = h
}
// Routes that ServeMux refuses side by side but Where constraints keep
// apart register as overlap families (overlap.go); the rest as before.
families, err := overlapFamilies(r.routes)
if err != nil {
return nil, err
}
inFamily := make(map[int]bool)
for _, fam := range families {
for _, idx := range fam.members {
inFamily[idx] = true
}
}
mux := http.NewServeMux()
for i, rt := range r.routes {
if inFamily[i] {
continue
}
if err := handleRoute(mux, rt, handlers[i]); err != nil {
return nil, err
}
}
if err := registerOverlapFamilies(mux, r.routes, handlers, families); err != nil {
return nil, err
}
return pathScopedCORS(r.corsCfg, mux), nil
}