/yesod-core/Yesod/Dispatch.hs
https://github.com/maxcan/yesod · Haskell · 190 lines · 144 code · 21 blank · 25 comment · 10 complexity · 498a3c85f5ac8acbb8e082f86b30ef73 MD5 · raw file
- {-# LANGUAGE TemplateHaskell #-}
- {-# LANGUAGE CPP #-}
- {-# LANGUAGE OverloadedStrings #-}
- module Yesod.Dispatch
- ( -- * Quasi-quoted routing
- parseRoutes
- , parseRoutesNoCheck
- , parseRoutesFile
- , parseRoutesFileNoCheck
- , mkYesod
- , mkYesodSub
- -- ** More fine-grained
- , mkYesodData
- , mkYesodSubData
- , mkYesodDispatch
- , mkYesodSubDispatch
- -- ** Path pieces
- , PathPiece (..)
- , PathMultiPiece (..)
- , Texts
- -- * Convert to WAI
- , toWaiApp
- , toWaiAppPlain
- ) where
- import Data.Functor ((<$>))
- import Prelude hiding (exp)
- import Yesod.Internal.Core
- import Yesod.Handler hiding (lift)
- import Yesod.Widget (GWidget)
- import Web.PathPieces
- import Language.Haskell.TH.Syntax
- import qualified Network.Wai as W
- import Network.Wai.Middleware.Gzip
- import Network.Wai.Middleware.Autohead
- import Data.ByteString.Lazy.Char8 ()
- import Web.ClientSession
- import Data.Text (Text)
- import Data.Text.Encoding (decodeUtf8With)
- import Data.Text.Encoding.Error (lenientDecode)
- import Data.Monoid (mappend)
- import qualified Data.ByteString as S
- import qualified Blaze.ByteString.Builder
- import Network.HTTP.Types (status301)
- import Yesod.Routes.TH
- import Yesod.Content (chooseRep)
- import Yesod.Routes.Parse
- type Texts = [Text]
- -- | Generates URL datatype and site function for the given 'Resource's. This
- -- is used for creating sites, /not/ subsites. See 'mkYesodSub' for the latter.
- -- Use 'parseRoutes' to create the 'Resource's.
- mkYesod :: String -- ^ name of the argument datatype
- -> [Resource String]
- -> Q [Dec]
- mkYesod name = fmap (uncurry (++)) . mkYesodGeneral name [] [] False
- -- | Generates URL datatype and site function for the given 'Resource's. This
- -- is used for creating subsites, /not/ sites. See 'mkYesod' for the latter.
- -- Use 'parseRoutes' to create the 'Resource's. In general, a subsite is not
- -- executable by itself, but instead provides functionality to
- -- be embedded in other sites.
- mkYesodSub :: String -- ^ name of the argument datatype
- -> Cxt
- -> [Resource String]
- -> Q [Dec]
- mkYesodSub name clazzes =
- fmap (uncurry (++)) . mkYesodGeneral name' rest clazzes True
- where
- (name':rest) = words name
- -- | Sometimes, you will want to declare your routes in one file and define
- -- your handlers elsewhere. For example, this is the only way to break up a
- -- monolithic file into smaller parts. Use this function, paired with
- -- 'mkYesodDispatch', to do just that.
- mkYesodData :: String -> [Resource String] -> Q [Dec]
- mkYesodData name res = mkYesodDataGeneral name [] False res
- mkYesodSubData :: String -> Cxt -> [Resource String] -> Q [Dec]
- mkYesodSubData name clazzes res = mkYesodDataGeneral name clazzes True res
- mkYesodDataGeneral :: String -> Cxt -> Bool -> [Resource String] -> Q [Dec]
- mkYesodDataGeneral name clazzes isSub res = do
- let (name':rest) = words name
- (x, _) <- mkYesodGeneral name' rest clazzes isSub res
- let rname = mkName $ "resources" ++ name
- eres <- lift res
- let y = [ SigD rname $ ListT `AppT` (ConT ''Resource `AppT` ConT ''String)
- , FunD rname [Clause [] (NormalB eres) []]
- ]
- return $ x ++ y
- -- | See 'mkYesodData'.
- mkYesodDispatch :: String -> [Resource String] -> Q [Dec]
- mkYesodDispatch name = fmap snd . mkYesodGeneral name [] [] False
- mkYesodSubDispatch :: String -> Cxt -> [Resource String] -> Q [Dec]
- mkYesodSubDispatch name clazzes = fmap snd . mkYesodGeneral name' rest clazzes True
- where (name':rest) = words name
- mkYesodGeneral :: String -- ^ foundation type
- -> [String]
- -> Cxt -- ^ classes
- -> Bool -- ^ is subsite?
- -> [Resource String]
- -> Q ([Dec], [Dec])
- mkYesodGeneral name args clazzes isSub resS = do
- let args' = map mkName args
- arg = foldl AppT (ConT name') $ map VarT args'
- let res = map (fmap parseType) resS
- renderRouteDec <- mkRenderRouteInstance arg res
- disp <- mkDispatchClause [|yesodRunner|] [|yesodDispatch|] [|fmap chooseRep|] res
- let master = mkName "master"
- let ctx = if isSub
- then ClassP (mkName "Yesod") [VarT master] : clazzes
- else []
- let ytyp = if isSub
- then ConT ''YesodDispatch `AppT` arg `AppT` VarT master
- else ConT ''YesodDispatch `AppT` arg `AppT` arg
- let yesodDispatch' =
- InstanceD ctx ytyp [FunD (mkName "yesodDispatch") [disp]]
- return (renderRouteDec : masterTypSyns, [yesodDispatch'])
- where
- name' = mkName name
- masterTypSyns
- | isSub = []
- | otherwise =
- [ TySynD
- (mkName "Handler")
- []
- (ConT ''GHandler `AppT` ConT name' `AppT` ConT name')
- , TySynD
- (mkName "Widget")
- []
- (ConT ''GWidget `AppT` ConT name' `AppT` ConT name' `AppT` TupleT 0)
- ]
- -- | Convert the given argument into a WAI application, executable with any WAI
- -- handler. This is the same as 'toWaiAppPlain', except it includes two
- -- middlewares: GZIP compression and autohead. This is the
- -- recommended approach for most users.
- toWaiApp :: ( Yesod master
- , YesodDispatch master master
- ) => master -> IO W.Application
- toWaiApp y = gzip (gzipSettings y) . autohead <$> toWaiAppPlain y
- -- | Convert the given argument into a WAI application, executable with any WAI
- -- handler. This differs from 'toWaiApp' in that it uses no middlewares.
- toWaiAppPlain :: ( Yesod master
- , YesodDispatch master master
- ) => master -> IO W.Application
- toWaiAppPlain a = toWaiApp' a <$> encryptKey a
- toWaiApp' :: ( Yesod master
- , YesodDispatch master master
- )
- => master
- -> Maybe Key
- -> W.Application
- toWaiApp' y key' env =
- case cleanPath y $ W.pathInfo env of
- Left pieces -> sendRedirect y pieces env
- Right pieces ->
- yesodDispatch y y id app404 handler405 method pieces key' env
- where
- app404 = yesodRunner notFound y y Nothing id
- handler405 route = yesodRunner badMethod y y (Just route) id
- method = decodeUtf8With lenientDecode $ W.requestMethod env
- sendRedirect :: Yesod master => master -> [Text] -> W.Application
- sendRedirect y segments' env =
- return $ W.responseLBS status301
- [ ("Content-Type", "text/plain")
- , ("Location", Blaze.ByteString.Builder.toByteString dest')
- ] "Redirecting"
- where
- dest = joinPath y (approot y) segments' []
- dest' =
- if S.null (W.rawQueryString env)
- then dest
- else (dest `mappend`
- Blaze.ByteString.Builder.fromByteString (W.rawQueryString env))