Skip to content

[Runtime] Reject canonical-equivalent dynamic backend route patterns #740

Description

@cssbruno

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

  • Registering two dynamic routes with the same method and same match shape returns a duplicate/conflict error.
  • Parameter names alone do not make two otherwise identical route patterns distinct.
  • Rest-capture routes receive equivalent conflict detection.
  • Static routes and dynamic routes have a deterministic precedence policy documented in tests.
  • Public runtime docs state what route pattern syntax BackendRouter accepts.
  • Generated routes continue to pass through unchanged.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions