roadrunner_router (roadrunner v0.1.0)

View Source

Path → 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 via roadrunner_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). Only path and handler are 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.

Functions

Compile a list of routes into the lookup form match/2 expects.

Look up the handler for a given request path.

Types

bindings()

-type bindings() :: #{binary() => binary() | [binary()]}.

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.

compiled()

-opaque compiled()

The compiled-routes representation match/2 consumes. Treat as opaque: the shape is an implementation detail and may change.

route()

-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.

routes()

-type routes() :: [route()].

An ordered list of routes; matched first-to-last.

Functions

compile(Routes, ListenerMws)

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).

match(Path, Compiled)

-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 PipelineState is for callers who need to introspect a route outside the request flow. Returns not_found when no compiled route matches.