| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
IHP.Router.DSL.TH
Description
The splice parses the DSL body, reifys the target controller type, and
emits IHP-free declarations:
instance HasPath Ctrl— pathTo per constructor.<ctrlLower>Trie :: (Ctrl -> Application) -> RouteTrie— top-level binding that builds the trie when applied to a user-supplied dispatch function. Plug it intorouteTrieMiddlewarefor a working WAI dispatcher.
The IHP-flavoured [routes|…|] quoter (which additionally emits an
instance CanRoute Ctrl and a webRoutes :: [ControllerRoute app]
binding) lives in IHP's IHP.Router.IHP module and calls into
parseAndReify / genericEmit here.
Names referenced from inside the splice (HasPath, pathTo,
renderCapture, parseCapture) are imported via mkName so they
resolve at the splice call site — typically via import IHP.Router.WAI
for plain WAI users or import IHP.RouterPrelude for IHP apps.
Names from sibling ihp-router modules (LiteralSeg, CaptureSeg,
buildRouteTrie, mkHandler, mkHandlerQ, …) are referenced
hygienically via QuoteName syntax.
Synopsis
- routes :: QuasiQuoter
- routesDec :: String -> Q [Dec]
- genericRoutesDec :: String -> Q [Dec]
- parseAndReify :: String -> Q ParsedBlock
- genericEmit :: ParsedBlock -> Q [Dec]
- data ParsedBlock = ParsedBlock {
- pbHeader :: !HeaderForm
- pbGroups :: ![(ControllerInfo, [ValidatedRoute])]
- pbWsRoutes :: ![(Name, ByteString)]
- data HeaderForm
- data ControllerInfo = ControllerInfo {
- ciTypeName :: !Name
- ciConstructors :: !(Map Text ConstructorInfo)
- data ConstructorInfo = ConstructorInfo {}
- data ValidatedRoute = ValidatedRoute {
- vrMethods :: ![Method]
- vrPath :: ![ValidatedSeg]
- vrCon :: !ConstructorInfo
- vrLine :: !Int
- vrBindingsByCapture :: !(Map Text Text)
- vrQueryFields :: ![(Text, Text, QueryFieldKind, Type)]
- data ValidatedSeg
- data QueryFieldKind
- trieValueName :: Name -> Name
Splice entry points
routes :: QuasiQuoter Source #
The [routes|...|] quasi-quoter. Captures use RFC 6570 URI-template
syntax — {name} for a single segment, {+name} for a splat — and
?name1&name2 for query-string parameters.
[routes|PostsController
GET /posts PostsAction
GET /posts/{postId} ShowPostAction
PATCH /posts/{postId} UpdatePostAction
GET /search?q&page SearchAction
|]Query parameters are declared explicitly: each ?name names a
record field on the action constructor that the URL carries in the
query string. The field's Haskell type determines the shape:
plain a is required (missing/unparseable ⇒ 404), is
optional, Maybe a[a] collects repeated values (?tags=a&tags=b).
Every record field of the action constructor must be covered
by either a path capture or a query-param entry — unbound fields
fail at splice time with a pointer to the exact fields left over.
Renaming. To map a capture or query-param name to a differently
named record field, use the { field = #name } syntax after the
action. Works for path captures and query params alike:
GET /ShowPost?id ShowPostAction { postId = #id }
GET /users/{uid} ShowUserAction { userId = #uid }This is the ihp-router (plain-WAI) flavour of the quoter — emits
HasPath instances and a per-controller <ctrlLower>Trie binding
only. IHP apps use the wrapping quoter from IHP.Router.IHP (re-exported
through IHP.Router.DSL), which additionally emits the IHP-flavoured
CanRoute instance and lowercase-header webRoutes binding.
Three header forms — all top-level declarations:
- Single-controller. Uppercase-initial header = controller type.
- Binding-named (multi-controller). Lowercase-initial header is
the name of a binding the splice emits. In
ihp-routerit carries the per-controller trie values; in IHP it additionally carries the[ControllerRoute app]list forFrontController.controllers. - No header. Multi-controller, instances + trie values only.
routesDec :: String -> Q [Dec] Source #
The default splice for the [routes|…|] quoter in ihp-router.
Emits the IHP-free declarations a plain WAI app needs — an
instance HasPath Ctrl per controller and a parameterised
<ctrlLower>Trie :: (Ctrl -> Application) -> RouteTrie binding
per controller.
IHP apps don't call this directly; they use the IHP-flavoured
routes quoter from IHP.Router.IHP, which composes genericEmit
with its own IHP-specific CanRoute / webRoutes emission.
genericRoutesDec :: String -> Q [Dec] Source #
The IHP-free entry point: parses the DSL, reifies the
controller(s), and emits genericEmit's output. Suitable for plain
WAI applications.
Same as routesDec; provided under the more descriptive name so the
IHP-side shim can call it explicitly when composing the
IHP-flavoured quoter.
Shared types and helpers
Re-exported for the IHP-side IHP.Router.IHP shim, which
composes its own quoter on top of parseAndReify /
genericEmit. Plain WAI users typically don't need to touch
these directly.
parseAndReify :: String -> Q ParsedBlock Source #
Parse the DSL body, reify each referenced controller type, and
validate every route against its action constructor. Returns a
ParsedBlock both emitters can consume.
WebSocket routes (those whose routeKind is WebSocketRoute)
are split out into pbWsRoutes rather than going through
groupRoutesByParent / validateRoute — they reference a WSApp
type by name (not an action constructor of a controller), so the
HTTP-route validation pipeline doesn't apply. For v1 we also restrict
WS routes to the lowercase-header form, since the IHP shim only
knows how to register them through the webRoutes binding the
lowercase form emits.
genericEmit :: ParsedBlock -> Q [Dec] Source #
Emit the generic HasPath instance per controller plus the
top-level <ctrlLower>Trie binding per controller. No IHP-specific
references — this is the half that moves to ihp-router in the
extraction PR.
data ParsedBlock Source #
A parsed [routes|…|] body, ready for emission. Produced by
parseAndReify and consumed by genericEmit and ihpEmit.
Constructors
| ParsedBlock | |
Fields
| |
data HeaderForm Source #
Which of the three header forms the parser saw.
Constructors
| HeaderUppercase !Text |
|
| HeaderLowercase !Text |
|
| HeaderAbsent | Header-less multi-controller form. Instances only, no binding. |
data ControllerInfo Source #
Constructors
| ControllerInfo | |
Fields
| |
data ConstructorInfo Source #
Constructors
| ConstructorInfo | |
data ValidatedRoute Source #
Constructors
| ValidatedRoute | |
Fields
| |
data QueryFieldKind Source #
Constructors
| QFRequired | plain |
| QFOptional | |
| QFList |
|
Instances
| Show QueryFieldKind Source # | |
Defined in IHP.Router.DSL.TH Methods showsPrec :: Int -> QueryFieldKind -> ShowS # show :: QueryFieldKind -> String # showList :: [QueryFieldKind] -> ShowS # | |
| Eq QueryFieldKind Source # | |
Defined in IHP.Router.DSL.TH Methods (==) :: QueryFieldKind -> QueryFieldKind -> Bool # (/=) :: QueryFieldKind -> QueryFieldKind -> Bool # | |
trieValueName :: Name -> Name Source #
Top-level binding name for the per-controller trie value: e.g.
PostsController becomes postsControllerTrie. Both the emitter
(emitTrieValue) and any IHP-side wrapper that wants to call the
binding with a specific dispatch function (e.g.
IHP.Router.IHP.emitCanRoute) use this helper to agree on the name.