PageRenderTime 65ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Network/Wai/Middleware/Routes/Routes.hs

https://bitbucket.org/ajnsit/wai-routes
Haskell | 299 lines | 174 code | 44 blank | 81 comment | 0 complexity | 1fefdfa657d8ffc86f2bb036bb5e418a MD5 | raw file
  1. {-# LANGUAGE MultiParamTypeClasses #-}
  2. {-# LANGUAGE TypeFamilies #-}
  3. {-# LANGUAGE OverloadedStrings #-}
  4. {-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
  5. {-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
  6. {-# LANGUAGE RankNTypes #-}
  7. {-# LANGUAGE CPP #-}
  8. {- |
  9. Module : Network.Wai.Middleware.Routes.Routes
  10. Copyright : (c) Anupam Jain 2013
  11. License : MIT (see the file LICENSE)
  12. Maintainer : ajnsit@gmail.com
  13. Stability : experimental
  14. Portability : non-portable (uses ghc extensions)
  15. This package provides typesafe URLs for Wai applications.
  16. -}
  17. module Network.Wai.Middleware.Routes.Routes
  18. ( -- * Quasi Quoters
  19. parseRoutes -- | Parse Routes declared inline
  20. , parseRoutesFile -- | Parse routes declared in a file
  21. , parseRoutesNoCheck -- | Parse routes declared inline, without checking for overlaps
  22. , parseRoutesFileNoCheck -- | Parse routes declared in a file, without checking for overlaps
  23. -- * Template Haskell methods
  24. , mkRoute
  25. , mkRouteSub
  26. -- * Dispatch
  27. , routeDispatch
  28. , customRouteDispatch
  29. -- * URL rendering and parsing
  30. , showRoute
  31. , showRouteQuery
  32. , readRoute
  33. -- * Application Handlers
  34. , Handler
  35. , HandlerS
  36. -- * As of Wai 3, Application datatype now follows continuation passing style
  37. -- A `ResponseHandler` represents a continuation passed to the application
  38. , ResponseHandler
  39. -- * Generated Datatypes
  40. , Routable(..) -- | Used internally. However needs to be exported for TH to work.
  41. , RenderRoute(..) -- | A `RenderRoute` instance for your site datatype is automatically generated by `mkRoute`
  42. , ParseRoute(..) -- | A `ParseRoute` instance for your site datatype is automatically generated by `mkRoute`
  43. , RouteAttrs(..) -- | A `RouteAttrs` instance for your site datatype is automatically generated by `mkRoute`
  44. -- * Accessing Request Data
  45. , Env(..)
  46. , RequestData -- | An abstract representation of the request data. You can get the wai request object by using `waiReq`
  47. , waiReq -- | Extract the wai `Request` object from `RequestData`
  48. , nextApp -- | Extract the next Application in the stack
  49. , currentRoute -- | Extract the current `Route` from `RequestData`
  50. , runNext -- | Run the next application in the stack
  51. -- * Not exported outside wai-routes
  52. , runHandler
  53. , readQueryString
  54. )
  55. where
  56. -- Wai
  57. import Network.Wai (ResponseReceived, Middleware, Application, pathInfo, requestMethod, requestMethod, Response, Request(..))
  58. import Network.HTTP.Types (Query, decodePath, encodePath, queryTextToQuery, queryToQueryText)
  59. -- Network.Wai.Middleware.Routes
  60. import Network.Wai.Middleware.Routes.Class (Route, RenderRoute(..), ParseRoute(..), RouteAttrs(..))
  61. import Network.Wai.Middleware.Routes.Parse (parseRoutes, parseRoutesNoCheck, parseRoutesFile, parseRoutesFileNoCheck, parseType)
  62. import Network.Wai.Middleware.Routes.TH (mkRenderRouteInstance, mkParseRouteInstance, mkRouteAttrsInstance, mkDispatchClause, ResourceTree(..), MkDispatchSettings(..), defaultGetHandler)
  63. -- Text and Bytestring
  64. import Data.ByteString (ByteString)
  65. import Data.Text (Text)
  66. import Data.Text.Encoding (encodeUtf8, decodeUtf8)
  67. import Blaze.ByteString.Builder (toByteString)
  68. -- TH
  69. import Language.Haskell.TH.Syntax
  70. -- Convenience
  71. import Control.Arrow (second)
  72. import Data.Maybe (fromMaybe)
  73. -- An abstract request
  74. data RequestData master = RequestData
  75. { waiReq :: Request
  76. , nextApp :: Application
  77. , currentRoute :: Maybe (Route master)
  78. }
  79. -- AJ: Experimental
  80. type ResponseHandler = (Response -> IO ResponseReceived) -> IO ResponseReceived
  81. -- Wai uses Application :: Wai.Request -> ResponseHandler
  82. -- However, instead of Request, we use RequestData which has more information
  83. type App master = RequestData master -> ResponseHandler
  84. data Env sub master = Env
  85. { envMaster :: master
  86. , envSub :: sub
  87. , envToMaster :: Route sub -> Route master
  88. }
  89. -- | Run the next application in the stack
  90. runNext :: App master
  91. runNext req = nextApp req $ waiReq req
  92. -- | A `Handler` generates an App from the master datatype
  93. type Handler sub = forall master. RenderRoute master => HandlerS sub master
  94. type HandlerS sub master = Env sub master -> App sub
  95. -- | Generates everything except actual dispatch
  96. mkRouteData :: String -> [ResourceTree String] -> Q [Dec]
  97. mkRouteData typName routes = do
  98. let typ = parseType typName
  99. let rname = mkName $ "_resources" ++ typName
  100. let resourceTrees = map (fmap parseType) routes
  101. eres <- lift routes
  102. let resourcesDec =
  103. [ SigD rname $ ListT `AppT` (ConT ''ResourceTree `AppT` ConT ''String)
  104. , FunD rname [Clause [] (NormalB eres) []]
  105. ]
  106. rinst <- mkRenderRouteInstance typ resourceTrees
  107. pinst <- mkParseRouteInstance typ resourceTrees
  108. ainst <- mkRouteAttrsInstance typ resourceTrees
  109. return $ concat [ [ainst]
  110. , [pinst]
  111. , resourcesDec
  112. , rinst
  113. ]
  114. -- | Generates a 'Routable' instance and dispatch function
  115. mkRouteDispatch :: String -> [ResourceTree String] -> Q [Dec]
  116. mkRouteDispatch typName routes = do
  117. let typ = parseType typName
  118. disp <- mkRouteDispatchClause routes
  119. return [InstanceD []
  120. (ConT ''Routable `AppT` typ `AppT` typ)
  121. [FunD (mkName "dispatcher") [disp]]]
  122. -- | Same as mkRouteDispatch but for subsites
  123. mkRouteSubDispatch :: String -> String -> [ResourceTree a] -> Q [Dec]
  124. mkRouteSubDispatch typName constraint routes = do
  125. let typ = parseType typName
  126. disp <- mkRouteDispatchClause routes
  127. master <- newName "master"
  128. -- We don't simply use parseType for GHC 7.8 (TH-2.9) compatibility
  129. -- ParseType only works on Type (not Pred)
  130. -- In GHC 7.10 (TH-2.10) onwards, Pred is aliased to Type
  131. className <- lookupTypeName constraint
  132. -- Check if this is a classname or a type
  133. let contract = maybe (error $ "Unknown typeclass " ++ show constraint) (getContract master) className
  134. return [InstanceD [contract]
  135. (ConT ''Routable `AppT` typ `AppT` VarT master)
  136. [FunD (mkName "dispatcher") [disp]]]
  137. where
  138. getContract master className =
  139. #if MIN_VERSION_template_haskell(2,10,0)
  140. ConT className `AppT` VarT master
  141. #else
  142. ClassP className [VarT master]
  143. #endif
  144. -- Helper that creates the dispatch clause
  145. mkRouteDispatchClause :: [ResourceTree a] -> Q Clause
  146. mkRouteDispatchClause =
  147. mkDispatchClause MkDispatchSettings
  148. { mdsRunHandler = [| runHandler |]
  149. , mdsSubDispatcher = [| subDispatcher |]
  150. , mdsGetPathInfo = [| getPathInfo |]
  151. , mdsMethod = [| getReqMethod |]
  152. , mdsSetPathInfo = [| setPathInfo |]
  153. , mds404 = [| app404 |]
  154. , mds405 = [| app405 |]
  155. , mdsGetHandler = defaultGetHandler
  156. , mdsUnwrapper = return
  157. }
  158. -- | Generates all the things needed for efficient routing.
  159. -- Including your application's `Route` datatype,
  160. -- `RenderRoute`, `ParseRoute`, `RouteAttrs`, and `Routable` instances.
  161. -- Use this for everything except subsites
  162. mkRoute :: String -> [ResourceTree String] -> Q [Dec]
  163. mkRoute typName routes = do
  164. dat <- mkRouteData typName routes
  165. disp <- mkRouteDispatch typName routes
  166. return (disp++dat)
  167. -- TODO: Also allow using the master datatype name directly, instead of a constraint class
  168. -- | Same as mkRoute, but for subsites
  169. mkRouteSub :: String -> String -> [ResourceTree String] -> Q [Dec]
  170. mkRouteSub typName constraint routes = do
  171. dat <- mkRouteData typName routes
  172. disp <- mkRouteSubDispatch typName constraint routes
  173. return (disp++dat)
  174. -- | A `Routable` instance can be used in dispatching.
  175. -- An appropriate instance for your site datatype is
  176. -- automatically generated by `mkRoute`.
  177. class Routable sub master where
  178. dispatcher :: HandlerS sub master
  179. -- | Generates the application middleware from a `Routable` master datatype
  180. routeDispatch :: Routable master master => master -> Middleware
  181. routeDispatch = customRouteDispatch dispatcher
  182. -- | Like routeDispatch but generates the application middleware from a custom dispatcher
  183. customRouteDispatch :: HandlerS master master -> master -> Middleware
  184. -- TODO: Should this have master master instead of sub master?
  185. -- TODO: Verify that this plays well with subsites
  186. -- Env master master is converted to Env sub master by subDispatcher
  187. -- Route information is filled in by runHandler
  188. customRouteDispatch customDispatcher master def req = customDispatcher (_masterToEnv master) RequestData{waiReq=req, nextApp=def, currentRoute=Nothing}
  189. -- | Render a `Route` and Query parameters to Text
  190. showRouteQuery :: RenderRoute master => Route master -> [(Text,Text)] -> Text
  191. showRouteQuery r q = uncurry _encodePathInfo $ second (map (second Just) . (++ q)) $ renderRoute r
  192. -- | Renders a `Route` as Text
  193. showRoute :: RenderRoute master => Route master -> Text
  194. showRoute = uncurry _encodePathInfo . second (map $ second Just) . renderRoute
  195. _encodePathInfo :: [Text] -> [(Text, Maybe Text)] -> Text
  196. -- Slightly hackish: Convert "" into "/"
  197. _encodePathInfo [] = _encodePathInfo [""]
  198. _encodePathInfo segments = decodeUtf8 . toByteString . encodePath segments . queryTextToQuery
  199. -- | Read a route from Text
  200. -- Returns Nothing if Route reading failed. Just route otherwise
  201. readRoute :: ParseRoute master => Text -> Maybe (Route master)
  202. readRoute = parseRoute . second readQueryString . decodePath . encodeUtf8
  203. -- | Convert a Query to the format expected by parseRoute
  204. readQueryString :: Query -> [(Text, Text)]
  205. readQueryString = map (second (fromMaybe "")) . queryToQueryText
  206. -- PRIVATE
  207. -- Get the request method from a RequestData
  208. getReqMethod :: RequestData master -> ByteString
  209. getReqMethod = requestMethod . waiReq
  210. -- Get the path info from a RequestData
  211. getPathInfo :: RequestData master -> [Text]
  212. getPathInfo = pathInfo . waiReq
  213. -- Set the path info in a RequestData
  214. setPathInfo :: [Text] -> RequestData master -> RequestData master
  215. setPathInfo p reqData = reqData { waiReq = (waiReq reqData){pathInfo=p} }
  216. -- Baked in applications that handle 404 and 405 errors
  217. -- On no matching route, skip to next application
  218. app404 :: HandlerS sub master
  219. app404 _master = runNext
  220. -- On matching route, but no matching http method, skip to next application
  221. -- This allows a later route to handle methods not implemented by the previous routes
  222. app405 :: HandlerS sub master
  223. app405 _master = runNext
  224. -- Run a route handler function
  225. -- Currently all this does is populate the route into RequestData
  226. -- But it may do more in the future
  227. runHandler
  228. :: HandlerS sub master
  229. -> Env sub master
  230. -> Maybe (Route sub)
  231. -> App sub
  232. runHandler h env route reqdata = h env reqdata{currentRoute=route}
  233. -- Run a route subsite handler function
  234. subDispatcher
  235. :: Routable sub master
  236. => (HandlerS sub master -> Env sub master -> Maybe (Route sub) -> App sub)
  237. -> (master -> sub)
  238. -> (Route sub -> Route master)
  239. -> Env master master
  240. -> App master
  241. subDispatcher _runhandler getSub toMasterRoute env reqData = dispatcher env' reqData'
  242. where
  243. env' = _envToSub getSub toMasterRoute env
  244. reqData' = reqData{currentRoute=Nothing}
  245. -- qq (k,mv) = (decodeUtf8 k, maybe "" decodeUtf8 mv)
  246. -- req = waiReq reqData
  247. _masterToEnv :: master -> Env master master
  248. _masterToEnv master = Env master master id
  249. _envToSub :: (master -> sub) -> (Route sub -> Route master) -> Env master master -> Env sub master
  250. _envToSub getSub toMasterRoute env = Env master sub toMasterRoute
  251. where
  252. master = envMaster env
  253. sub = getSub master