ihp-router-1.0.0: Trie-based routing with a Yesod-style DSL for WAI
Safe HaskellNone
LanguageGHC2021

IHP.Router.Trie

Description

A RouteTrie is the runtime data structure used by IHP's router to dispatch incoming requests to handlers. Paths are split on / and walked segment-by-segment. At each node, literal matches are tried first, then a typed capture, then a splat fall-through. Handlers at a leaf are indexed by HTTP method so the same path can dispatch to different actions for GET POST PATCH / etc.

The trie is built once at application startup and shared across all requests; lookup is a single linear walk with no backtracking.

Synopsis

Documentation

data RouteTrie Source #

Path-indexed trie node.

Lookup priority at each node is: literal segment > typed capture > splat. Splat captures consume the remaining path and land on the bundled HandlersByMethod.

newtype CaptureSpec Source #

Specification of a capture at a trie node.

A {name} segment accepts any non-empty bytestring up to the next /; a {+name} splat accepts the remainder of the path. Type-level validation is not performed during the walk — it's the handler's job, via parseCapture, once per request. This decouples URL matching from parameter decoding and means each segment is parsed exactly once.

The captureName is kept for debugging and error messages; the trie itself doesn't use it.

Constructors

CaptureSpec 

Fields

data PatternSegment Source #

A single segment of a route pattern during trie construction.

type HandlersByMethod = Map StdMethod WaiHandler Source #

Per-method handlers at a trie leaf. Missing entries correspond to 405 Method Not Allowed. Backed by Map (not HashMap) because StdMethod doesn't have a Hashable instance and the per-node cardinality is tiny (≤9 methods).

type WaiHandler = Captures -> Application Source #

A WAI Application that also receives the captures collected during trie traversal.

type Captures = [ByteString] Source #

Captures collected while walking the trie, in path order. Each entry is the raw bytestring the trie accepted for a {name} or {+name} segment. The handler at the leaf — generated by the TH splice — knows each capture's Haskell type by position and calls parseCapture to extract the typed value.

Using a plain [ByteString] (rather than a HashMap Text Dynamic) avoids the toDyn/fromDynamic round-trip and the TypeRep equality check on every request. The trade-off is that the ordering of the list must match the order of captures in the path pattern, which the splice takes care of.

data LookupResult Source #

Outcome of a trie lookup.

emptyTrie :: RouteTrie Source #

An empty trie — matches nothing, has no handlers.

insertRoute :: [PatternSegment] -> StdMethod -> WaiHandler -> RouteTrie -> RouteTrie Source #

Insert a route into the trie. Given a list of pattern segments, a method, and a handler, this either extends the trie with new nodes or merges into existing ones.

Inserting the same (method, pattern) twice silently overrides.

insertRouteMethods :: [PatternSegment] -> [StdMethod] -> WaiHandler -> RouteTrie -> RouteTrie Source #

Like insertRoute but registers the same handler under multiple methods at once.

The TH splice uses this to implement GET ⇒ GET+HEAD expansion without emitting a duplicate handler lambda per method: one WaiHandler value, one trie walk, both methods registered at the leaf.

lookupTrie :: RouteTrie -> StdMethod -> [ByteString] -> LookupResult Source #

Look up a method+path pair in the trie.

Walks path segments with the priority order literal > typed capture > splat. Returns Matched with collected captures on success; if the path matches a leaf but the method isn't registered, MethodNotAllowed is returned with the list of methods that do match. NotMatched is returned when no path walk reaches a leaf or splat.

mergeTrie :: RouteTrie -> RouteTrie -> RouteTrie Source #

Deep-merge two tries. Used at application startup to combine fragments coming from separate controllers into one app-wide trie.

Right-biased: where both sides define a handler for the same method at the same leaf, b wins.

splitPath :: ByteString -> [ByteString] Source #

Split a path like "posts123/edit" into ["posts","123","edit"]. Leading, trailing, and empty segments are dropped so "/" and "" both yield [].