roadrunner_router (roadrunner v0.1.0)
View SourcePath → handler dispatch with parameterized segments.
A route is either a tuple shorthand or a map. Both forms share the
same Path and Handler:
{Path, Handler}— only routes the path; no state, no per-route middlewares.{Path, Handler, State}— adds opaque per-handler state surfaced viaroadrunner_req:state/1.#{path => Path, handler => Handler, state => State, middlewares => [Mw, ...]}— full map form. Use this when you want to attach per-route middlewares (or any future per-route framework knob). Onlypathandhandlerare required.
The tuple shorthand intentionally cannot carry middlewares — that keeps the simple case syntactically light and pushes "more than just state" to the more verbose map form.
Path is a binary like /users/:id/posts/:post_id. Segments
starting with : capture a single segment into bindings keyed by
the binary name that follows the colon — we deliberately avoid
binary_to_atom/1 on the parsed name to keep the "everything is
binary on the wire" rule we already use for header names.
Segments starting with * (e.g. /static/*path) are wildcard
captures: they consume all remaining path segments and bind them as
a list under the given name. A wildcard must be the last segment in
a pattern; anything after it never matches.
Literal segments must match byte-exactly; comparison is case-sensitive per RFC 3986.
Routes are tried in declaration order — earlier entries win. The
opaque compiled() shape is a list of pre-parsed segment patterns;
swapping to a trie/DAG later is a non-breaking change for callers.
Summary
Types
Captured route parameters, populated by match/2.
The compiled-routes representation match/2 consumes. Treat as
opaque: the shape is an implementation detail and may change.
A single route entry. Three shapes are accepted
An ordered list of routes; matched first-to-last.
Types
Captured route parameters, populated by match/2.
:param segments produce a single binary value
(#{~"id" => ~"42"}). *wildcard segments produce the list of
remaining path segments
(#{~"rest" => [~"a", ~"b"]}). Empty for routes with no captures.
-opaque compiled()
The compiled-routes representation match/2 consumes. Treat as
opaque: the shape is an implementation detail and may change.
-type route() :: {Path :: binary(), Handler :: module()} | {Path :: binary(), Handler :: module(), State :: term()} | #{path := binary(), handler := module(), state => term(), middlewares => roadrunner_middleware:middleware_list()}.
A single route entry. Three shapes are accepted:
{Path, Handler}— shorthand: no state, no middlewares.{Path, Handler, State}— shorthand with state only.#{path := Path, handler := Handler, state => State, middlewares => Mws}— map form; use this to attach per-route middlewares or future per-route framework knobs.
Path is a binary pattern (literal segments, :param captures, or
*wildcard catch-all). Handler is the module implementing
roadrunner_handler. State is opaque per-route data threaded back
to the handler via roadrunner_req:state/1; unset → undefined.
-type routes() :: [route()].
An ordered list of routes; matched first-to-last.
Functions
-spec compile(routes(), roadrunner_middleware:middleware_list()) -> compiled().
Compile a list of routes into the lookup form match/2 expects.
Each path is split on / (empty leading/trailing segments dropped),
and segments starting with : are recorded as named captures.
ListenerMws is the listener-wide middleware list; it is prepended
to each route's own middlewares and composed once into a single
next() fun (with any per-route state injected before middlewares
run). The conn loop calls that fun straight with the request —
zero closure allocations per request. Pass [] for ListenerMws
when compiling routes outside a listener (typically only in tests).
-spec match(Path :: binary(), compiled()) -> {ok, module(), bindings(), roadrunner_middleware:next(), term()} | not_found.
Look up the handler for a given request path.
Returns {ok, Handler, Bindings, Pipeline, State} on a match —
Bindings is a map populated with captures from :param segments
(empty for purely literal routes); Pipeline is the pre-composed
next() fun built at compile time (listener mws ++ per-route mws,
optionally wrapped in a state-injecting outermost closure, ending in
fun Handler:handle/1); State is the per-route opaque state
attached by the user at compile time (or undefined when the route
shape didn't carry any). The conn loop just calls Pipeline —
State is for callers who need to introspect a route outside the
request flow. Returns not_found when no compiled route matches.