Priority
Medium-high — deterministic routing and public runtime contract correctness.
Context
runtime/app.BackendRouter supports exact routes and dynamic route patterns. Static routes are stored by (method, path). Dynamic routes are appended to router.patterns, and duplicate detection currently compares the raw normalized pattern string:
if backendRouteIsDynamic(key.path) {
for _, existing := range router.patterns {
if existing.key == key {
return fmt.Errorf("duplicate backend route %s %s", key.method, key.path)
}
}
router.patterns = append(router.patterns, ...)
}
Route matching itself treats parameter names as captures. For example, /blog/{slug} and /blog/{id} match the same request path shape.
Problem
The runtime router can accept two dynamic route patterns that are semantically equivalent but differ only by parameter name or type annotation.
Examples:
router.API("GET", "/blog/{slug}", handlerA)
router.API("GET", "/blog/{id}", handlerB)
Both can match /blog/hello. Dispatch then selects whichever pattern was registered first, making behavior depend on registration order rather than an explicit conflict diagnostic.
The compiler validates generated route conflicts, but runtime/app.BackendRouter is also a public runtime type. It should protect its own route table invariants instead of assuming only compiler-produced inputs.
Proposed direction
Canonicalize backend route patterns before duplicate/conflict checks.
A canonical pattern should preserve literal segments and dynamic/rest segment positions while ignoring capture names where they do not affect matching. Typed parameters should either be part of the route matching contract or rejected from the public runtime route registration contract if unsupported there.
Suggested examples:
/blog/{slug} -> /blog/{}
/blog/{id} -> /blog/{}
/files/{path...} -> /files/{...}
Acceptance criteria
Related
Priority
Medium-high — deterministic routing and public runtime contract correctness.
Context
runtime/app.BackendRoutersupports exact routes and dynamic route patterns. Static routes are stored by(method, path). Dynamic routes are appended torouter.patterns, and duplicate detection currently compares the raw normalized pattern string:Route matching itself treats parameter names as captures. For example,
/blog/{slug}and/blog/{id}match the same request path shape.Problem
The runtime router can accept two dynamic route patterns that are semantically equivalent but differ only by parameter name or type annotation.
Examples:
Both can match
/blog/hello. Dispatch then selects whichever pattern was registered first, making behavior depend on registration order rather than an explicit conflict diagnostic.The compiler validates generated route conflicts, but
runtime/app.BackendRouteris also a public runtime type. It should protect its own route table invariants instead of assuming only compiler-produced inputs.Proposed direction
Canonicalize backend route patterns before duplicate/conflict checks.
A canonical pattern should preserve literal segments and dynamic/rest segment positions while ignoring capture names where they do not affect matching. Typed parameters should either be part of the route matching contract or rejected from the public runtime route registration contract if unsupported there.
Suggested examples:
Acceptance criteria
BackendRouteraccepts.Related