| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
IHP.Router.DSL.Runtime
Contents
Description
The TH splice in IHP.Router.DSL.TH keeps its emitted expressions small by delegating the non-trivial work to the helpers in this module. Everything here is plain Haskell — importable and testable without running any splice.
Synopsis
- buildRouteTrie :: [([StdMethod], [PatternSegment], WaiHandler)] -> RouteTrie
- captureSpec :: Text -> CaptureSpec
- dispatch :: (controller -> Application) -> Maybe controller -> Application
- mkHandler :: (controller -> Application) -> (Captures -> Maybe controller) -> WaiHandler
- mkHandlerQ :: (controller -> Application) -> (Captures -> Query -> Maybe controller) -> WaiHandler
- queryParamRequired :: UrlCapture a => ByteString -> Query -> Maybe a
- queryParamOptional :: UrlCapture a => ByteString -> Query -> Maybe a
- queryParamList :: UrlCapture a => ByteString -> Query -> [a]
- renderQueryString :: [(ByteString, Maybe Text)] -> Text
Documentation
buildRouteTrie :: [([StdMethod], [PatternSegment], WaiHandler)] -> RouteTrie Source #
Build a RouteTrie from a compile-time-known list of
(methods, pattern, handler) triples. Each entry can register one
handler under several HTTP methods at once — the TH splice uses this
so a GET route auto-registers HEAD via the same WaiHandler value
(no duplicate lambda in the emitted Core).
captureSpec :: Text -> CaptureSpec Source #
Build a CaptureSpec from a capture name. Type-level validation
is done by the handler, not the trie — see dispatch.
dispatch :: (controller -> Application) -> Maybe controller -> Application Source #
Dispatch a positional capture list to a controller action, falling
back to 404 Not Found when construction fails (typically because a
capture bytestring couldn't be decoded as the expected type).
The TH splice emits a call to dispatch per route handler; the second
argument is a Maybe expression that tries parseCapture on each
positional bytestring and assembles the action constructor.
Arguments
| :: (controller -> Application) | runner (e.g. |
| -> (Captures -> Maybe controller) | per-route capture decoder |
| -> WaiHandler |
Wrap a per-route capture-and-action builder into a WaiHandler.
The TH splice emits one call to mkHandler (or mkHandlerQ for routes
that read query parameters) per route. Pulling the WAI shell out of
the splice and into a runtime helper saves ~3 KB of Core per route on
the user-facing module: the per-route emission shrinks from a 3-arg
\captures req respond -> let q = queryString req in case captures of …
shape down to a single mkHandler application plus a small lambda.
The builder receives the captures unchanged and returns
on a successful decode or Just actionNothing if any capture rejected. The
runtime then dispatches the action via the user-supplied runner
(typically runAction' in IHP, or any Controller -> Application
function in a generic WAI app).
NOINLINE is deliberate: with INLINE, GHC would inline the body at
every per-route call site (one per route in the user's Routes.hs),
regenerating the \captures req respond -> dispatch … shell we just
factored out. NOINLINE keeps a single shared body in Runtime, the
per-route Core is just one application of mkHandler. The dispatch
helper itself is INLINE and folds into mkHandler's body once.
Runtime cost is one extra indirect call per request — negligible
against actual handler work, and well below the latency we shaved
by avoiding AutoRoute's per-request Data reflection.
mkHandlerQ :: (controller -> Application) -> (Captures -> Query -> Maybe controller) -> WaiHandler Source #
Variant of mkHandler that also threads the query string through.
Used by the TH splice for routes that declare any ?name query
parameters; routes without query params use the plainer mkHandler.
Splitting these two avoids paying for the queryString req pull on
routes that don't read query params (the vast majority of static
routes), and keeps the simple-case Core small.
Query-string helpers
queryParamRequired :: UrlCapture a => ByteString -> Query -> Maybe a Source #
Look up a required query parameter and decode it via parseCapture.
Returns Nothing if the key is absent, the value is missing (?k
without =v), or parseCapture rejects the raw bytes. A Nothing
flows up through the splice's Maybe-do block and triggers a 404,
matching the behaviour of an unmatched path capture.
queryParamOptional :: UrlCapture a => ByteString -> Query -> Maybe a Source #
queryParamList :: UrlCapture a => ByteString -> Query -> [a] Source #
Collect all values of a repeated query parameter (e.g. ?tag=a&tag=b
yields ["a","b"]). Values that fail to decode are silently
dropped — preserves AutoRoute's behaviour of tolerating bad list
entries rather than 400-ing the whole request.
renderQueryString :: [(ByteString, Maybe Text)] -> Text Source #
Render a list of (name, pairs as a Just value)?k1=v1&k2=v2
suffix (URL-encoded). Empty list yields the empty string. Pairs with
Nothing values are dropped — pathTo passes Nothing for absent
Maybe fields and for empty list fields, so they correctly disappear
from the URL.
The splice's generated pathTo clauses call this once per
constructor with the list of query-param tuples for that route.