| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
IHP.Router.WAI
Description
Re-exports the surface a plain WAI app needs to declare routes via the
[routes|…|] quasi-quoter and dispatch incoming requests through the
trie:
routes— the generic quasi-quoter from IHP.Router.DSL.THRouteTrie,routeTrieMiddleware— the runtime dispatcherUrlCapture,HasPath,pathTo,parseCapture,renderCapture— types and class methods referenced from inside the spliceSegment, the standard non-empty path-segment newtype
Typical use:
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
module Main where
import IHP.Router.WAI
import Network.Wai (Application, responseLBS)
import Network.Wai.Handler.Warp (run)
import Network.HTTP.Types (status200, status404)
data Routes = ListPosts | ShowPost { postId :: Int }
deriving (Eq, Show)
[routes|Routes
GET /posts ListPosts
GET posts{postId} ShowPost
|]
dispatch :: Routes -> Application
dispatch ListPosts _ respond = respond (responseLBS status200 [] "list")
dispatch (ShowPost { postId }) _ respond =
respond (responseLBS status200 [] (cs (show postId)))
main :: IO ()
main = run 3000 (routeTrieMiddleware (routesTrie dispatch) notFound)
where
notFound _ respond = respond (responseLBS status404 [] "Not Found")
The routesTrie binding is emitted by the splice (one per controller
type) — its name is the controller's <lowercaseFirst>Trie.
Synopsis
- routes :: QuasiQuoter
- data RouteTrie
- routeTrieMiddleware :: RouteTrie -> Middleware
- class Typeable a => UrlCapture a where
- parseCapture :: ByteString -> Maybe a
- renderCapture :: a -> Text
- newtype Segment = Segment {}
- class HasPath controller where
The DSL
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.
Trie + middleware
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.
routeTrieMiddleware :: RouteTrie -> Middleware Source #
Wrap a RouteTrie as a WAI Middleware.
Semantics:
- If the trie matches, the matched handler (a
) runs with the captured path segments and the incoming request.Captures->Application - If the path matches but the HTTP method does not, a
405 Method Not Allowedresponse is returned with anAllowheader listing the supported methods. - If neither the path nor a splat matches, the request is handed to the
fallbackapplication — which is the standard WAI behaviour when a middleware declines a request.
The middleware is pure with respect to the trie: construction happens at application startup, lookup on every request.
URL captures
class Typeable a => UrlCapture a where Source #
A type that can appear as a URL path segment.
Instances provide a parseCapture for decoding a raw segment bytestring
(post-URL-decoding) into a typed value, and renderCapture for the
reverse direction when generating URLs via pathTo.
Example:
>>>parseCapture @Int "42"Just 42
>>>renderCapture (42 :: Int)"42"
Methods
parseCapture :: ByteString -> Maybe a Source #
Parse a single URL segment (already URL-decoded) into a typed value.
Returns Nothing if the segment cannot be interpreted as this type.
renderCapture :: a -> Text Source #
Render a typed value as URL-ready text. The caller is responsible for URL-encoding if needed.
Instances
| UrlCapture Segment Source # | |
Defined in IHP.Router.Capture Methods parseCapture :: ByteString -> Maybe Segment Source # renderCapture :: Segment -> Text Source # | |
| UrlCapture Text Source # | |
Defined in IHP.Router.Capture | |
| UrlCapture Day Source # | |
Defined in IHP.Router.Capture | |
| UrlCapture UUID Source # | |
Defined in IHP.Router.Capture | |
| UrlCapture Integer Source # | |
Defined in IHP.Router.Capture Methods parseCapture :: ByteString -> Maybe Integer Source # renderCapture :: Integer -> Text Source # | |
| UrlCapture Bool Source # | |
Defined in IHP.Router.Capture | |
| UrlCapture Int Source # | |
Defined in IHP.Router.Capture | |
A URL path segment guaranteed to be non-empty.
Useful when a capture must not match an empty string. Plain Text captures
happily match "" (the segment between two consecutive slashes); Segment
rejects that case, which is often what you want for splat captures or
required path pieces.
URL generation
class HasPath controller where Source #
Type class for types that can be converted to URL paths.
This is used by IHP's routing system to generate URLs for controller actions.
Example:
>>>pathTo UsersAction"/Users"
>>>pathTo ShowUserAction { userId = "a32913dd-ef80-4f3e-9a91-7879e17b2ece" }"/ShowUser?userId=a32913dd-ef80-4f3e-9a91-7879e17b2ece"