| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
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
- data RouteTrie = RouteTrie {
- static :: !(HashMap ByteString RouteTrie)
- dynamic :: !(Maybe (CaptureSpec, RouteTrie))
- splat :: !(Maybe (Text, HandlersByMethod))
- handlers :: !HandlersByMethod
- newtype CaptureSpec = CaptureSpec {
- captureName :: Text
- data PatternSegment
- type HandlersByMethod = Map StdMethod WaiHandler
- type WaiHandler = Captures -> Application
- type Captures = [ByteString]
- data LookupResult
- emptyTrie :: RouteTrie
- insertRoute :: [PatternSegment] -> StdMethod -> WaiHandler -> RouteTrie -> RouteTrie
- insertRouteMethods :: [PatternSegment] -> [StdMethod] -> WaiHandler -> RouteTrie -> RouteTrie
- lookupTrie :: RouteTrie -> StdMethod -> [ByteString] -> LookupResult
- mergeTrie :: RouteTrie -> RouteTrie -> RouteTrie
- splitPath :: ByteString -> [ByteString]
Documentation
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.
Constructors
| RouteTrie | |
Fields
| |
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.
Constructors
| LiteralSeg !ByteString | |
| CaptureSeg !CaptureSpec | |
| SplatSeg !Text |
type HandlersByMethod = Map StdMethod WaiHandler Source #
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.
Constructors
| Matched !WaiHandler !Captures | |
| MethodNotAllowed ![StdMethod] | |
| NotMatched |
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 [].