When several registered routes could all match one request, a router has to
pick the most SPECIFIC one -- /users/me (fully literal) should win over
/users/:id (a parameter) when the request is literally /users/me, even
though both technically match. Registration order shouldn't matter; specificity
should.
A pattern is a string like /users/:id -- segments starting with : are
parameters (match any single segment, capturing its value); other segments
must match literally. Only patterns with the SAME NUMBER of segments as the
request path are even candidates.
Implement:
add_route(method, pattern, handler)-- register a route.match(method, path)-- return(handler, params_dict)for the BEST matching route, orNoneif nothing matches. "Best" = the candidate with the most literal (non-parameter) segment matches.
Example
r = Router()
r.add_route("GET", "/users/:id", "get_user")
r.add_route("GET", "/users/me", "get_current_user")
r.match("GET", "/users/me") # -> ("get_current_user", {}) -- literal wins
r.match("GET", "/users/42") # -> ("get_user", {"id": "42"}) -- only match
Constraints
- Method must match exactly.
- Segment COUNT must match exactly --
/a/bnever matches a 3-segment pattern. - Among same-length candidates, more literal-segment matches wins; parameter segments never count toward specificity.
Real-world grounding
- Caddy's route matcher (path matching with priority ordering):
modules/caddyhttp/matchers.go - Traefik's routing rule engine, same underlying problem:
traefik/traefik
Real routers add wildcard/catch-all segments (/files/*path), regex
constraints on parameters, and header/query matching on top of this -- but
"most literal segments wins" is the specificity rule underneath nearly all of
them.