Completely replaced Handler type
This commit is contained in:
parent
002f6ef788
commit
77dc6ed78b
@ -112,6 +112,9 @@ instance ConvertSuccess HtmlObject JsonObject where
|
|||||||
instance ConvertSuccess HtmlObject JsonDoc where
|
instance ConvertSuccess HtmlObject JsonDoc where
|
||||||
convertSuccess = cs . (cs :: HtmlObject -> JsonObject)
|
convertSuccess = cs . (cs :: HtmlObject -> JsonObject)
|
||||||
|
|
||||||
|
instance ToObject Html String Html where
|
||||||
|
toObject = Scalar
|
||||||
|
|
||||||
instance ToSElem HtmlObject where
|
instance ToSElem HtmlObject where
|
||||||
toSElem (Scalar h) = STR $ TL.unpack $ cs h
|
toSElem (Scalar h) = STR $ TL.unpack $ cs h
|
||||||
toSElem (Sequence hs) = LI $ map toSElem hs
|
toSElem (Sequence hs) = LI $ map toSElem hs
|
||||||
|
|||||||
@ -25,6 +25,7 @@ module Yesod.Application
|
|||||||
import Web.Encodings
|
import Web.Encodings
|
||||||
import Data.Enumerable
|
import Data.Enumerable
|
||||||
import Control.Monad (when)
|
import Control.Monad (when)
|
||||||
|
import Data.Object.Html
|
||||||
|
|
||||||
import qualified Hack
|
import qualified Hack
|
||||||
import Hack.Middleware.CleanPath
|
import Hack.Middleware.CleanPath
|
||||||
@ -40,6 +41,7 @@ import Yesod.Handler
|
|||||||
import Yesod.Definitions
|
import Yesod.Definitions
|
||||||
import Yesod.Constants
|
import Yesod.Constants
|
||||||
import Yesod.Resource
|
import Yesod.Resource
|
||||||
|
import Yesod.Rep
|
||||||
|
|
||||||
import Data.Convertible.Text
|
import Data.Convertible.Text
|
||||||
import Control.Arrow ((***))
|
import Control.Arrow ((***))
|
||||||
@ -60,7 +62,7 @@ class ResourceName a => RestfulApp a where
|
|||||||
]
|
]
|
||||||
|
|
||||||
-- | Output error response pages.
|
-- | Output error response pages.
|
||||||
errorHandler :: Monad m => a -> RawRequest -> ErrorResult -> [RepT m] -- FIXME better type sig?
|
errorHandler :: a -> RawRequest -> ErrorResult -> HtmlObject -- FIXME better type sig?
|
||||||
|
|
||||||
-- | Whether or not we should check for overlapping resource names.
|
-- | Whether or not we should check for overlapping resource names.
|
||||||
checkOverlaps :: a -> Bool
|
checkOverlaps :: a -> Bool
|
||||||
@ -100,12 +102,12 @@ takeJusts (Just x:rest) = x : takeJusts rest
|
|||||||
|
|
||||||
toHackApplication :: RestfulApp resourceName
|
toHackApplication :: RestfulApp resourceName
|
||||||
=> resourceName
|
=> resourceName
|
||||||
-> (resourceName -> Verb -> Handler)
|
-> (resourceName -> Verb -> Handler [(ContentType, Content)])
|
||||||
-> Hack.Application
|
-> Hack.Application
|
||||||
toHackApplication sampleRN hm env = do
|
toHackApplication sampleRN hm env = do
|
||||||
-- The following is safe since we run cleanPath as middleware
|
-- The following is safe since we run cleanPath as middleware
|
||||||
let (Right resource) = splitPath $ Hack.pathInfo env
|
let (Right resource) = splitPath $ Hack.pathInfo env
|
||||||
let (handler :: Handler, urlParams') =
|
let (handler, urlParams') =
|
||||||
case findResourceNames resource of
|
case findResourceNames resource of
|
||||||
[] -> (notFound, [])
|
[] -> (notFound, [])
|
||||||
((rn, urlParams''):_) ->
|
((rn, urlParams''):_) ->
|
||||||
@ -113,7 +115,7 @@ toHackApplication sampleRN hm env = do
|
|||||||
in (hm rn verb, urlParams'')
|
in (hm rn verb, urlParams'')
|
||||||
let rr = envToRawRequest urlParams' env
|
let rr = envToRawRequest urlParams' env
|
||||||
let rawHttpAccept = tryLookup "" "Accept" $ Hack.http env
|
let rawHttpAccept = tryLookup "" "Accept" $ Hack.http env
|
||||||
ctypes' = parseHttpAccept rawHttpAccept
|
ctypes' = map TypeOther $ parseHttpAccept rawHttpAccept
|
||||||
r <-
|
r <-
|
||||||
runHandler handler rr ctypes' >>=
|
runHandler handler rr ctypes' >>=
|
||||||
either (applyErrorHandler sampleRN rr ctypes') return
|
either (applyErrorHandler sampleRN rr ctypes') return
|
||||||
@ -126,20 +128,19 @@ applyErrorHandler :: (RestfulApp ra, Monad m)
|
|||||||
-> (ErrorResult, [Header])
|
-> (ErrorResult, [Header])
|
||||||
-> m Response
|
-> m Response
|
||||||
applyErrorHandler ra rr cts (er, headers) = do
|
applyErrorHandler ra rr cts (er, headers) = do
|
||||||
let (ct, c) = chooseRep cts (errorHandler ra rr er)
|
let (ct, c) = chooseRep (errorHandler ra rr er) cts
|
||||||
c' <- c
|
|
||||||
return $ Response
|
return $ Response
|
||||||
(getStatus er)
|
(getStatus er)
|
||||||
(getHeaders er ++ headers)
|
(getHeaders er ++ headers)
|
||||||
ct
|
ct
|
||||||
c'
|
c
|
||||||
|
|
||||||
responseToHackResponse :: [String] -- ^ language list
|
responseToHackResponse :: [String] -- ^ language list
|
||||||
-> Response -> IO Hack.Response
|
-> Response -> IO Hack.Response
|
||||||
responseToHackResponse ls (Response sc hs ct c) = do
|
responseToHackResponse _FIXMEls (Response sc hs ct c) = do
|
||||||
hs' <- mapM toPair hs
|
hs' <- mapM toPair hs
|
||||||
let hs'' = ("Content-Type", ct) : hs'
|
let hs'' = ("Content-Type", show ct) : hs'
|
||||||
let asLBS = runContent ls c
|
let asLBS = unContent c
|
||||||
return $ Hack.Response sc hs'' asLBS
|
return $ Hack.Response sc hs'' asLBS
|
||||||
|
|
||||||
envToRawRequest :: [(ParamName, ParamValue)] -> Hack.Env -> RawRequest
|
envToRawRequest :: [(ParamName, ParamValue)] -> Hack.Env -> RawRequest
|
||||||
|
|||||||
101
Yesod/Handler.hs
101
Yesod/Handler.hs
@ -18,13 +18,10 @@
|
|||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
module Yesod.Handler
|
module Yesod.Handler
|
||||||
( -- * Handler monad
|
( -- * Handler monad
|
||||||
HandlerT
|
Handler
|
||||||
, HandlerT' -- FIXME
|
|
||||||
, HandlerIO
|
|
||||||
, Handler
|
|
||||||
, runHandler
|
, runHandler
|
||||||
, liftIO
|
, liftIO
|
||||||
, ToHandler (..)
|
--, ToHandler (..)
|
||||||
-- * Special handlers
|
-- * Special handlers
|
||||||
, redirect
|
, redirect
|
||||||
, notFound
|
, notFound
|
||||||
@ -36,54 +33,76 @@ module Yesod.Handler
|
|||||||
|
|
||||||
import Yesod.Request
|
import Yesod.Request
|
||||||
import Yesod.Response
|
import Yesod.Response
|
||||||
|
import Yesod.Rep
|
||||||
|
|
||||||
import Control.Exception hiding (Handler)
|
import Control.Exception hiding (Handler)
|
||||||
|
import Control.Applicative
|
||||||
|
|
||||||
import Control.Monad.Reader
|
|
||||||
import Control.Monad.Writer
|
import Control.Monad.Writer
|
||||||
import Control.Monad.Attempt
|
import Control.Monad.Attempt
|
||||||
|
|
||||||
import Data.Typeable
|
--import Data.Typeable
|
||||||
|
|
||||||
------ Handler monad
|
------ Handler monad
|
||||||
type HandlerT m =
|
newtype Handler a = Handler {
|
||||||
ReaderT RawRequest (
|
unHandler :: RawRequest -> IO ([Header], HandlerContents a)
|
||||||
AttemptT (
|
}
|
||||||
WriterT [Header] m
|
data HandlerContents a =
|
||||||
)
|
forall e. Exception e => HCError e
|
||||||
)
|
| HCSpecial ErrorResult
|
||||||
type HandlerIO = HandlerT IO
|
| HCContent a
|
||||||
type Handler = HandlerIO [RepT HandlerIO]
|
|
||||||
type HandlerT' m a =
|
|
||||||
ReaderT RawRequest (
|
|
||||||
AttemptT (
|
|
||||||
WriterT [Header] m
|
|
||||||
)
|
|
||||||
) a
|
|
||||||
|
|
||||||
-- FIXME shouldn't call error here...
|
instance Functor Handler where
|
||||||
instance MonadRequestReader HandlerIO where
|
fmap = liftM
|
||||||
askRawRequest = ask
|
instance Applicative Handler where
|
||||||
|
pure = return
|
||||||
|
(<*>) = ap
|
||||||
|
instance Monad Handler where
|
||||||
|
fail = failureString -- We want to catch all exceptions anyway
|
||||||
|
return x = Handler $ \_ -> return ([], HCContent x)
|
||||||
|
(Handler handler) >>= f = Handler $ \rr -> do
|
||||||
|
(headers, c) <- handler rr
|
||||||
|
(headers', c') <-
|
||||||
|
case c of
|
||||||
|
(HCError e) -> return $ ([], HCError e)
|
||||||
|
(HCSpecial e) -> return $ ([], HCSpecial e)
|
||||||
|
(HCContent a) -> unHandler (f a) rr
|
||||||
|
return (headers ++ headers', c')
|
||||||
|
instance MonadIO Handler where
|
||||||
|
liftIO i = Handler $ \_ -> i >>= \i' -> return ([], HCContent i')
|
||||||
|
instance Exception e => Failure e Handler where
|
||||||
|
failure e = Handler $ \_ -> return ([], HCError e)
|
||||||
|
instance MonadRequestReader Handler where
|
||||||
|
askRawRequest = Handler $ \rr -> return ([], HCContent rr)
|
||||||
invalidParam _pt _pn _pe = error "invalidParam"
|
invalidParam _pt _pn _pe = error "invalidParam"
|
||||||
authRequired = error "authRequired"
|
authRequired = error "authRequired"
|
||||||
instance Exception e => Failure e HandlerIO where
|
|
||||||
failure = error "HandlerIO failure"
|
|
||||||
|
|
||||||
|
-- FIXME this is a stupid signature
|
||||||
|
runHandler :: HasReps a
|
||||||
|
=> Handler a
|
||||||
|
-> RawRequest
|
||||||
|
-> [ContentType]
|
||||||
|
-> IO (Either (ErrorResult, [Header]) Response)
|
||||||
|
runHandler (Handler handler) rr cts = do
|
||||||
|
(headers, contents) <- handler rr
|
||||||
|
case contents of
|
||||||
|
HCError e -> return $ Left (InternalError $ show e, headers)
|
||||||
|
HCSpecial e -> return $ Left (e, headers)
|
||||||
|
HCContent a ->
|
||||||
|
let (ct, c) = chooseRep a cts
|
||||||
|
in return $ Right $ Response 200 headers ct c
|
||||||
|
{- FIXME
|
||||||
class ToHandler a where
|
class ToHandler a where
|
||||||
toHandler :: a -> Handler
|
toHandler :: a -> Handler
|
||||||
|
|
||||||
{- FIXME
|
|
||||||
instance (Request r, ToHandler h) => ToHandler (r -> h) where
|
instance (Request r, ToHandler h) => ToHandler (r -> h) where
|
||||||
toHandler f = parseRequest >>= toHandler . f
|
toHandler f = parseRequest >>= toHandler . f
|
||||||
-}
|
|
||||||
|
|
||||||
instance ToHandler Handler where
|
instance ToHandler Handler where
|
||||||
toHandler = id
|
toHandler = id
|
||||||
|
|
||||||
{- FIXME
|
|
||||||
instance HasReps r HandlerIO => ToHandler (HandlerIO r) where
|
instance HasReps r HandlerIO => ToHandler (HandlerIO r) where
|
||||||
toHandler = fmap reps
|
toHandler = fmap reps
|
||||||
-}
|
|
||||||
|
|
||||||
runHandler :: Handler
|
runHandler :: Handler
|
||||||
-> RawRequest
|
-> RawRequest
|
||||||
@ -124,6 +143,7 @@ joinHandler cts rs = do
|
|||||||
let (ct, c) = chooseRep cts rs'
|
let (ct, c) = chooseRep cts rs'
|
||||||
c' <- c
|
c' <- c
|
||||||
return (ct, c')
|
return (ct, c')
|
||||||
|
-}
|
||||||
|
|
||||||
{-
|
{-
|
||||||
runHandler :: (ErrorResult -> Reps)
|
runHandler :: (ErrorResult -> Reps)
|
||||||
@ -151,33 +171,32 @@ runHandler eh wrapper ctypesAll (HandlerT inside) rr = do
|
|||||||
-}
|
-}
|
||||||
|
|
||||||
------ Special handlers
|
------ Special handlers
|
||||||
errorResult :: ErrorResult -> HandlerIO a
|
errorResult :: ErrorResult -> Handler a
|
||||||
errorResult = lift . failure -- FIXME more instances in Attempt?
|
errorResult er = Handler $ \_ -> return ([], HCSpecial er)
|
||||||
|
|
||||||
-- | Redirect to the given URL.
|
-- | Redirect to the given URL.
|
||||||
redirect :: String -> HandlerIO a
|
redirect :: String -> Handler a
|
||||||
redirect = errorResult . Redirect
|
redirect = errorResult . Redirect
|
||||||
|
|
||||||
-- | Return a 404 not found page. Also denotes no handler available.
|
-- | Return a 404 not found page. Also denotes no handler available.
|
||||||
notFound :: HandlerIO a
|
notFound :: Handler a
|
||||||
notFound = errorResult NotFound
|
notFound = errorResult NotFound
|
||||||
|
|
||||||
------- Headers
|
------- Headers
|
||||||
-- | Set the cookie on the client.
|
-- | Set the cookie on the client.
|
||||||
addCookie :: Monad m
|
addCookie :: Int -- ^ minutes to timeout
|
||||||
=> Int -- ^ minutes to timeout
|
|
||||||
-> String -- ^ key
|
-> String -- ^ key
|
||||||
-> String -- ^ value
|
-> String -- ^ value
|
||||||
-> HandlerT m ()
|
-> Handler ()
|
||||||
addCookie a b = addHeader . AddCookie a b
|
addCookie a b = addHeader . AddCookie a b
|
||||||
|
|
||||||
-- | Unset the cookie on the client.
|
-- | Unset the cookie on the client.
|
||||||
deleteCookie :: Monad m => String -> HandlerT m ()
|
deleteCookie :: String -> Handler ()
|
||||||
deleteCookie = addHeader . DeleteCookie
|
deleteCookie = addHeader . DeleteCookie
|
||||||
|
|
||||||
-- | Set an arbitrary header on the client.
|
-- | Set an arbitrary header on the client.
|
||||||
header :: Monad m => String -> String -> HandlerT m ()
|
header :: String -> String -> Handler ()
|
||||||
header a = addHeader . Header a
|
header a = addHeader . Header a
|
||||||
|
|
||||||
addHeader :: Monad m => Header -> HandlerT m ()
|
addHeader :: Header -> Handler ()
|
||||||
addHeader = lift . lift . tell . return
|
addHeader h = Handler $ \_ -> return ([h], HCContent ())
|
||||||
|
|||||||
@ -19,7 +19,8 @@ module Yesod.Helpers.AtomFeed
|
|||||||
, AtomFeedEntry (..)
|
, AtomFeedEntry (..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yesod.Response
|
import Yesod.Rep
|
||||||
|
import Data.Convertible.Text (cs)
|
||||||
|
|
||||||
import Data.Time.Clock
|
import Data.Time.Clock
|
||||||
import Web.Encodings
|
import Web.Encodings
|
||||||
@ -31,9 +32,9 @@ data AtomFeed = AtomFeed
|
|||||||
, atomUpdated :: UTCTime
|
, atomUpdated :: UTCTime
|
||||||
, atomEntries :: [AtomFeedEntry]
|
, atomEntries :: [AtomFeedEntry]
|
||||||
}
|
}
|
||||||
instance Monad m => HasReps AtomFeed m where
|
instance HasReps AtomFeed where
|
||||||
reps e =
|
reps =
|
||||||
[ ("application/atom+xml", return $ toContent $ show e)
|
[ (TypeAtom, cs . show)
|
||||||
]
|
]
|
||||||
|
|
||||||
data AtomFeedEntry = AtomFeedEntry
|
data AtomFeedEntry = AtomFeedEntry
|
||||||
|
|||||||
@ -26,6 +26,9 @@ import qualified Web.Authenticate.Rpxnow as Rpxnow
|
|||||||
import qualified Web.Authenticate.OpenId as OpenId
|
import qualified Web.Authenticate.OpenId as OpenId
|
||||||
import Data.Enumerable
|
import Data.Enumerable
|
||||||
|
|
||||||
|
import Data.Object.Html
|
||||||
|
import Data.Convertible.Text (cs)
|
||||||
|
|
||||||
import Yesod
|
import Yesod
|
||||||
import Yesod.Constants
|
import Yesod.Constants
|
||||||
|
|
||||||
@ -57,7 +60,7 @@ instance Enumerable AuthResource where
|
|||||||
|
|
||||||
newtype RpxnowApiKey = RpxnowApiKey String
|
newtype RpxnowApiKey = RpxnowApiKey String
|
||||||
|
|
||||||
authHandler :: Maybe RpxnowApiKey -> AuthResource -> Verb -> Handler
|
authHandler :: Maybe RpxnowApiKey -> AuthResource -> Verb -> Handler HtmlObject
|
||||||
authHandler _ Check Get = authCheck
|
authHandler _ Check Get = authCheck
|
||||||
authHandler _ Logout Get = authLogout
|
authHandler _ Logout Get = authLogout
|
||||||
authHandler _ Openid Get = authOpenidForm
|
authHandler _ Openid Get = authOpenidForm
|
||||||
@ -85,7 +88,7 @@ instance Show OIDFormReq where
|
|||||||
show (OIDFormReq (Just s) _) = "<p class='message'>" ++ encodeHtml s ++
|
show (OIDFormReq (Just s) _) = "<p class='message'>" ++ encodeHtml s ++
|
||||||
"</p>"
|
"</p>"
|
||||||
|
|
||||||
authOpenidForm :: Handler
|
authOpenidForm :: Handler HtmlObject
|
||||||
authOpenidForm = do
|
authOpenidForm = do
|
||||||
m@(OIDFormReq _ dest) <- parseRequest
|
m@(OIDFormReq _ dest) <- parseRequest
|
||||||
let html =
|
let html =
|
||||||
@ -97,9 +100,9 @@ authOpenidForm = do
|
|||||||
case dest of
|
case dest of
|
||||||
Just dest' -> addCookie 120 "DEST" dest'
|
Just dest' -> addCookie 120 "DEST" dest'
|
||||||
Nothing -> return ()
|
Nothing -> return ()
|
||||||
return $ htmlResponse html
|
return $ toHtmlObject $ Html $ cs html
|
||||||
|
|
||||||
authOpenidForward :: Handler
|
authOpenidForward :: Handler HtmlObject
|
||||||
authOpenidForward = do
|
authOpenidForward = do
|
||||||
oid <- getParam "openid"
|
oid <- getParam "openid"
|
||||||
env <- parseEnv
|
env <- parseEnv
|
||||||
@ -112,7 +115,7 @@ authOpenidForward = do
|
|||||||
redirect
|
redirect
|
||||||
res
|
res
|
||||||
|
|
||||||
authOpenidComplete :: Handler
|
authOpenidComplete :: Handler HtmlObject
|
||||||
authOpenidComplete = do
|
authOpenidComplete = do
|
||||||
gets' <- rawGetParams <$> askRawRequest
|
gets' <- rawGetParams <$> askRawRequest
|
||||||
dest <- cookieParam "DEST"
|
dest <- cookieParam "DEST"
|
||||||
@ -138,7 +141,7 @@ chopHash ('#':rest) = rest
|
|||||||
chopHash x = x
|
chopHash x = x
|
||||||
|
|
||||||
rpxnowLogin :: String -- ^ api key
|
rpxnowLogin :: String -- ^ api key
|
||||||
-> Handler
|
-> Handler HtmlObject
|
||||||
rpxnowLogin apiKey = do
|
rpxnowLogin apiKey = do
|
||||||
token <- anyParam "token"
|
token <- anyParam "token"
|
||||||
postDest <- postParam "dest"
|
postDest <- postParam "dest"
|
||||||
@ -154,24 +157,17 @@ rpxnowLogin apiKey = do
|
|||||||
header authCookieName $ Rpxnow.identifier ident
|
header authCookieName $ Rpxnow.identifier ident
|
||||||
redirect dest
|
redirect dest
|
||||||
|
|
||||||
authCheck :: Handler
|
authCheck :: Handler HtmlObject
|
||||||
authCheck = error "authCheck"
|
|
||||||
|
|
||||||
authLogout :: Handler
|
|
||||||
authLogout = error "authLogout"
|
|
||||||
{- FIXME
|
|
||||||
authCheck :: Handler
|
|
||||||
authCheck = do
|
authCheck = do
|
||||||
ident <- maybeIdentifier
|
ident <- maybeIdentifier
|
||||||
case ident of
|
case ident of
|
||||||
Nothing -> return $ objectResponse [("status", "notloggedin")]
|
Nothing -> return $ toHtmlObject [("status", "notloggedin")]
|
||||||
Just i -> return $ objectResponse
|
Just i -> return $ toHtmlObject
|
||||||
[ ("status", "loggedin")
|
[ ("status", "loggedin")
|
||||||
, ("ident", i)
|
, ("ident", i)
|
||||||
]
|
]
|
||||||
|
|
||||||
authLogout :: Handler
|
authLogout :: Handler HtmlObject
|
||||||
authLogout = do
|
authLogout = do
|
||||||
deleteCookie authCookieName
|
deleteCookie authCookieName
|
||||||
return $ objectResponse [("status", "loggedout")]
|
return $ toHtmlObject [("status", "loggedout")]
|
||||||
-}
|
|
||||||
|
|||||||
@ -24,11 +24,12 @@ module Yesod.Helpers.Sitemap
|
|||||||
|
|
||||||
import Yesod.Definitions
|
import Yesod.Definitions
|
||||||
import Yesod.Handler
|
import Yesod.Handler
|
||||||
import Yesod.Response
|
import Yesod.Rep
|
||||||
import Web.Encodings
|
import Web.Encodings
|
||||||
import qualified Hack
|
import qualified Hack
|
||||||
import Yesod.Request
|
import Yesod.Request
|
||||||
import Data.Time (UTCTime)
|
import Data.Time (UTCTime)
|
||||||
|
import Data.Convertible.Text (cs)
|
||||||
|
|
||||||
data SitemapLoc = AbsLoc String | RelLoc String
|
data SitemapLoc = AbsLoc String | RelLoc String
|
||||||
data SitemapChangeFreq = Always
|
data SitemapChangeFreq = Always
|
||||||
@ -55,7 +56,7 @@ data SitemapUrl = SitemapUrl
|
|||||||
}
|
}
|
||||||
data SitemapRequest = SitemapRequest String Int
|
data SitemapRequest = SitemapRequest String Int
|
||||||
data SitemapResponse = SitemapResponse SitemapRequest [SitemapUrl]
|
data SitemapResponse = SitemapResponse SitemapRequest [SitemapUrl]
|
||||||
instance Show SitemapResponse where
|
instance Show SitemapResponse where -- FIXME very ugly, use Text instead
|
||||||
show (SitemapResponse (SitemapRequest host port) urls) =
|
show (SitemapResponse (SitemapRequest host port) urls) =
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ++
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ++
|
||||||
"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">" ++
|
"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">" ++
|
||||||
@ -80,19 +81,19 @@ instance Show SitemapResponse where
|
|||||||
showLoc (AbsLoc s) = s
|
showLoc (AbsLoc s) = s
|
||||||
showLoc (RelLoc s) = prefix ++ s
|
showLoc (RelLoc s) = prefix ++ s
|
||||||
|
|
||||||
instance Monad m => HasReps SitemapResponse m where
|
instance HasReps SitemapResponse where
|
||||||
reps res =
|
reps =
|
||||||
[ ("text/xml", return $ toContent $ show res)
|
[ (TypeXml, cs . show)
|
||||||
]
|
]
|
||||||
|
|
||||||
sitemap :: IO [SitemapUrl] -> Handler
|
sitemap :: IO [SitemapUrl] -> Handler SitemapResponse
|
||||||
sitemap urls' = do
|
sitemap urls' = do
|
||||||
env <- parseEnv
|
env <- parseEnv
|
||||||
-- FIXME
|
-- FIXME
|
||||||
let req = SitemapRequest (Hack.serverName env) (Hack.serverPort env)
|
let req = SitemapRequest (Hack.serverName env) (Hack.serverPort env)
|
||||||
urls <- liftIO urls'
|
urls <- liftIO urls'
|
||||||
return $ reps $ SitemapResponse req urls
|
return $ SitemapResponse req urls
|
||||||
|
|
||||||
robots :: Approot -> Handler
|
robots :: Approot -> Handler Plain
|
||||||
robots (Approot ar) = do
|
robots (Approot ar) = do
|
||||||
return $ genResponse "text/plain" $ "Sitemap: " ++ ar ++ "sitemap.xml"
|
return $ plain $ "Sitemap: " ++ ar ++ "sitemap.xml"
|
||||||
|
|||||||
@ -22,11 +22,12 @@ module Yesod.Helpers.Static
|
|||||||
, fileLookupDir
|
, fileLookupDir
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.ByteString as B
|
import qualified Data.ByteString.Lazy as B
|
||||||
import System.Directory (doesFileExist)
|
import System.Directory (doesFileExist)
|
||||||
import Control.Applicative ((<$>))
|
import Control.Applicative ((<$>))
|
||||||
|
|
||||||
import Yesod
|
import Yesod
|
||||||
|
import Yesod.Rep
|
||||||
|
|
||||||
type FileLookup = FilePath -> IO (Maybe B.ByteString)
|
type FileLookup = FilePath -> IO (Maybe B.ByteString)
|
||||||
|
|
||||||
@ -39,30 +40,30 @@ fileLookupDir dir fp = do
|
|||||||
then Just <$> B.readFile fp'
|
then Just <$> B.readFile fp'
|
||||||
else return Nothing
|
else return Nothing
|
||||||
|
|
||||||
serveStatic :: FileLookup -> Verb -> Handler
|
serveStatic :: FileLookup -> Verb -> Handler [(ContentType, Content)]
|
||||||
serveStatic fl Get = getStatic fl
|
serveStatic fl Get = getStatic fl
|
||||||
serveStatic _ _ = notFound
|
serveStatic _ _ = notFound
|
||||||
|
|
||||||
getStatic :: FileLookup -> Handler
|
getStatic :: FileLookup -> Handler [(ContentType, Content)]
|
||||||
getStatic fl = do
|
getStatic fl = do
|
||||||
fp <- urlParam "filepath" -- FIXME check for ..
|
fp <- urlParam "filepath" -- FIXME check for ..
|
||||||
content <- liftIO $ fl fp
|
content <- liftIO $ fl fp
|
||||||
case content of
|
case content of
|
||||||
Nothing -> notFound
|
Nothing -> notFound
|
||||||
Just bs -> return [(mimeType $ ext fp, return $ toContent bs)]
|
Just bs -> return [(mimeType $ ext fp, Content bs)]
|
||||||
|
|
||||||
mimeType :: String -> String
|
mimeType :: String -> ContentType
|
||||||
mimeType "jpg" = "image/jpeg"
|
mimeType "jpg" = TypeJpeg
|
||||||
mimeType "jpeg" = "image/jpeg"
|
mimeType "jpeg" = TypeJpeg
|
||||||
mimeType "js" = "text/javascript"
|
mimeType "js" = TypeJavascript
|
||||||
mimeType "css" = "text/css"
|
mimeType "css" = TypeCss
|
||||||
mimeType "html" = "text/html"
|
mimeType "html" = TypeHtml
|
||||||
mimeType "png" = "image/png"
|
mimeType "png" = TypePng
|
||||||
mimeType "gif" = "image/gif"
|
mimeType "gif" = TypeGif
|
||||||
mimeType "txt" = "text/plain"
|
mimeType "txt" = TypePlain
|
||||||
mimeType "flv" = "video/x-flv"
|
mimeType "flv" = TypeFlv
|
||||||
mimeType "ogv" = "video/ogg"
|
mimeType "ogv" = TypeOgv
|
||||||
mimeType _ = "application/octet-stream"
|
mimeType _ = TypeOctet
|
||||||
|
|
||||||
ext :: String -> String
|
ext :: String -> String
|
||||||
ext = reverse . fst . break (== '.') . reverse
|
ext = reverse . fst . break (== '.') . reverse
|
||||||
|
|||||||
71
Yesod/Rep.hs
71
Yesod/Rep.hs
@ -1,6 +1,8 @@
|
|||||||
{-# LANGUAGE CPP #-}
|
{-# LANGUAGE CPP #-}
|
||||||
{-# LANGUAGE TypeSynonymInstances #-}
|
{-# LANGUAGE TypeSynonymInstances #-}
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
-- | Representations of data. A representation is basically how you display
|
-- | Representations of data. A representation is basically how you display
|
||||||
-- information in a certain mime-type. For example, tree-style data can easily
|
-- information in a certain mime-type. For example, tree-style data can easily
|
||||||
-- be displayed as both JSON and Yaml.
|
-- be displayed as both JSON and Yaml.
|
||||||
@ -26,12 +28,15 @@
|
|||||||
module Yesod.Rep
|
module Yesod.Rep
|
||||||
(
|
(
|
||||||
ContentType (..)
|
ContentType (..)
|
||||||
, Content
|
, Content (..)
|
||||||
, Rep
|
, Rep
|
||||||
, Reps
|
, Reps
|
||||||
, HasReps (..)
|
, HasReps (..)
|
||||||
, chooseRep
|
, chooseRep
|
||||||
-- FIXME TemplateFile or some such...
|
-- FIXME TemplateFile or some such...
|
||||||
|
-- * Specific types of representations
|
||||||
|
, Plain (..)
|
||||||
|
, plain
|
||||||
#if TEST
|
#if TEST
|
||||||
, testSuite
|
, testSuite
|
||||||
#endif
|
#endif
|
||||||
@ -58,21 +63,46 @@ import Test.HUnit hiding (Test)
|
|||||||
|
|
||||||
data ContentType =
|
data ContentType =
|
||||||
TypeHtml
|
TypeHtml
|
||||||
|
| TypePlain
|
||||||
| TypeJson
|
| TypeJson
|
||||||
|
| TypeXml
|
||||||
|
| TypeAtom
|
||||||
|
| TypeJpeg
|
||||||
|
| TypePng
|
||||||
|
| TypeGif
|
||||||
|
| TypeJavascript
|
||||||
|
| TypeCss
|
||||||
|
| TypeFlv
|
||||||
|
| TypeOgv
|
||||||
|
| TypeOctet
|
||||||
| TypeOther String
|
| TypeOther String
|
||||||
deriving Eq
|
|
||||||
instance Show ContentType where
|
instance Show ContentType where
|
||||||
show TypeHtml = "text/html"
|
show TypeHtml = "text/html"
|
||||||
|
show TypePlain = "text/plain"
|
||||||
show TypeJson = "application/json"
|
show TypeJson = "application/json"
|
||||||
|
show TypeXml = "text/xml"
|
||||||
|
show TypeAtom = "application/atom+xml"
|
||||||
|
show TypeJpeg = "image/jpeg"
|
||||||
|
show TypePng = "image/png"
|
||||||
|
show TypeGif = "image/gif"
|
||||||
|
show TypeJavascript = "text/javascript"
|
||||||
|
show TypeCss = "text/css"
|
||||||
|
show TypeFlv = "video/x-flv"
|
||||||
|
show TypeOgv = "video/ogg"
|
||||||
|
show TypeOctet = "application/octet-stream"
|
||||||
show (TypeOther s) = s
|
show (TypeOther s) = s
|
||||||
|
instance Eq ContentType where
|
||||||
|
x == y = show x == show y
|
||||||
|
|
||||||
newtype Content = Content ByteString
|
newtype Content = Content { unContent :: ByteString }
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
instance ConvertSuccess Text Content where
|
instance ConvertSuccess Text Content where
|
||||||
convertSuccess = Content . cs
|
convertSuccess = Content . cs
|
||||||
instance ConvertSuccess ByteString Content where
|
instance ConvertSuccess ByteString Content where
|
||||||
convertSuccess = Content
|
convertSuccess = Content
|
||||||
|
instance ConvertSuccess String Content where
|
||||||
|
convertSuccess = Content . cs
|
||||||
|
|
||||||
type Rep a = (ContentType, a -> Content)
|
type Rep a = (ContentType, a -> Content)
|
||||||
type Reps a = [Rep a]
|
type Reps a = [Rep a]
|
||||||
@ -81,25 +111,32 @@ type Reps a = [Rep a]
|
|||||||
-- one representation for each type.
|
-- one representation for each type.
|
||||||
class HasReps a where
|
class HasReps a where
|
||||||
reps :: Reps a
|
reps :: Reps a
|
||||||
|
instance HasReps [(ContentType, Content)] where
|
||||||
|
reps = [(TypeOther "FIXME", const $ Content $ cs "FIXME")]
|
||||||
|
|
||||||
chooseRep :: (Applicative f, HasReps a)
|
-- FIXME done badly, needs cleanup
|
||||||
=> f a
|
chooseRep :: HasReps a
|
||||||
|
=> a
|
||||||
-> [ContentType]
|
-> [ContentType]
|
||||||
-> f (ContentType, Content)
|
-> (ContentType, Content)
|
||||||
chooseRep fa ts =
|
chooseRep a ts =
|
||||||
let choices = rs' ++ rs
|
let choices = rs' ++ rs
|
||||||
helper2 (ct, f) =
|
helper2 (ct, f) = (ct, f a)
|
||||||
let fbs = f `fmap` fa
|
|
||||||
in pure (\bs -> (ct, bs)) <*> fbs
|
|
||||||
in if null rs
|
in if null rs
|
||||||
then error "Invalid empty reps"
|
then error "Invalid empty reps"
|
||||||
else helper2 (head choices)
|
else helper2 $ head choices
|
||||||
where
|
where
|
||||||
rs = reps
|
rs = reps
|
||||||
rs' = filter (\r -> fst r `elem` ts) rs
|
rs' = filter (\r -> fst r `elem` ts) rs
|
||||||
-- for type signature stuff
|
-- for type signature stuff
|
||||||
_ignored = pure (undefined :: Content) `asTypeOf`
|
_ignored = pure (undefined :: Content) `asTypeOf`
|
||||||
(snd (head rs) `fmap` fa)
|
(snd (head rs) )
|
||||||
|
|
||||||
|
newtype Plain = Plain Text
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
plain :: ConvertSuccess x Text => x -> Plain
|
||||||
|
plain = Plain . cs
|
||||||
|
|
||||||
-- Useful instances of HasReps
|
-- Useful instances of HasReps
|
||||||
instance HasReps HtmlObject where
|
instance HasReps HtmlObject where
|
||||||
@ -112,13 +149,13 @@ instance HasReps HtmlObject where
|
|||||||
caseChooseRep :: Assertion
|
caseChooseRep :: Assertion
|
||||||
caseChooseRep = do
|
caseChooseRep = do
|
||||||
let content = "IGNOREME"
|
let content = "IGNOREME"
|
||||||
a = Just $ toHtmlObject content
|
a = toHtmlObject content
|
||||||
htmlbs = Content . cs . unHtmlDoc . cs $ toHtmlObject content
|
htmlbs = Content . cs . unHtmlDoc . cs $ toHtmlObject content
|
||||||
jsonbs = Content . cs $ "\"" ++ content ++ "\""
|
jsonbs = Content . cs $ "\"" ++ content ++ "\""
|
||||||
chooseRep a [TypeHtml] @?= Just (TypeHtml, htmlbs)
|
chooseRep a [TypeHtml] @?= (TypeHtml, htmlbs)
|
||||||
chooseRep a [TypeJson] @?= Just (TypeJson, jsonbs)
|
chooseRep a [TypeJson] @?= (TypeJson, jsonbs)
|
||||||
chooseRep a [TypeHtml, TypeJson] @?= Just (TypeHtml, htmlbs)
|
chooseRep a [TypeHtml, TypeJson] @?= (TypeHtml, htmlbs)
|
||||||
chooseRep a [TypeOther "foo", TypeJson] @?= Just (TypeJson, jsonbs)
|
chooseRep a [TypeOther "foo", TypeJson] @?= (TypeJson, jsonbs)
|
||||||
|
|
||||||
testSuite :: Test
|
testSuite :: Test
|
||||||
testSuite = testGroup "Yesod.Rep"
|
testSuite = testGroup "Yesod.Rep"
|
||||||
|
|||||||
@ -36,6 +36,12 @@ import Data.List (intercalate)
|
|||||||
import Data.Enumerable
|
import Data.Enumerable
|
||||||
import Data.Char (isDigit)
|
import Data.Char (isDigit)
|
||||||
|
|
||||||
|
#if TEST
|
||||||
|
import Yesod.Rep hiding (testSuite)
|
||||||
|
#else
|
||||||
|
import Yesod.Rep
|
||||||
|
#endif
|
||||||
|
|
||||||
#if TEST
|
#if TEST
|
||||||
import Control.Monad (replicateM, when)
|
import Control.Monad (replicateM, when)
|
||||||
import Test.Framework (testGroup, Test)
|
import Test.Framework (testGroup, Test)
|
||||||
@ -86,7 +92,7 @@ class (Show a, Enumerable a) => ResourceName a where
|
|||||||
resourcePattern :: a -> String
|
resourcePattern :: a -> String
|
||||||
|
|
||||||
-- | Find the handler for each resource name/verb pattern.
|
-- | Find the handler for each resource name/verb pattern.
|
||||||
getHandler :: a -> Verb -> Handler
|
getHandler :: a -> Verb -> Handler [(ContentType, Content)] -- FIXME
|
||||||
|
|
||||||
type SMap = [(String, String)]
|
type SMap = [(String, String)]
|
||||||
|
|
||||||
|
|||||||
@ -19,15 +19,6 @@
|
|||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
module Yesod.Response
|
module Yesod.Response
|
||||||
( Response (..)
|
( Response (..)
|
||||||
-- * Representations
|
|
||||||
, RepT
|
|
||||||
, chooseRep
|
|
||||||
, HasReps (..)
|
|
||||||
, ContentType
|
|
||||||
-- * Content
|
|
||||||
, Content
|
|
||||||
, ToContent (..)
|
|
||||||
, runContent
|
|
||||||
-- * Abnormal responses
|
-- * Abnormal responses
|
||||||
, ErrorResult (..)
|
, ErrorResult (..)
|
||||||
, getHeaders
|
, getHeaders
|
||||||
@ -35,21 +26,19 @@ module Yesod.Response
|
|||||||
-- * Header
|
-- * Header
|
||||||
, Header (..)
|
, Header (..)
|
||||||
, toPair
|
, toPair
|
||||||
-- * Generic responses
|
|
||||||
, genResponse
|
|
||||||
, htmlResponse
|
|
||||||
#if TEST
|
#if TEST
|
||||||
-- * Tests
|
-- * Tests
|
||||||
, testSuite
|
, testSuite
|
||||||
#endif
|
#endif
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Yesod.Definitions
|
#if TEST
|
||||||
|
import Yesod.Rep hiding (testSuite)
|
||||||
|
#else
|
||||||
|
import Yesod.Rep
|
||||||
|
#endif
|
||||||
|
|
||||||
import Data.Time.Clock
|
import Data.Time.Clock
|
||||||
import qualified Data.ByteString as SBS
|
|
||||||
import qualified Data.ByteString.Lazy as LBS
|
|
||||||
import qualified Data.Text as ST
|
|
||||||
import qualified Data.Text.Lazy as LT
|
|
||||||
|
|
||||||
import Web.Encodings (formatW3)
|
import Web.Encodings (formatW3)
|
||||||
|
|
||||||
@ -59,62 +48,9 @@ import Test.Framework (testGroup, Test)
|
|||||||
|
|
||||||
import Data.Generics
|
import Data.Generics
|
||||||
import Control.Exception (Exception)
|
import Control.Exception (Exception)
|
||||||
import Data.Maybe (fromJust)
|
|
||||||
import Data.Convertible.Text
|
|
||||||
|
|
||||||
import Data.Text.Lazy (Text)
|
|
||||||
|
|
||||||
data Response = Response Int [Header] ContentType Content
|
data Response = Response Int [Header] ContentType Content
|
||||||
|
|
||||||
type ContentType = String
|
|
||||||
|
|
||||||
-- | FIXME: Lazy in theory is better, but kills actual programs
|
|
||||||
data Content = ByteString SBS.ByteString
|
|
||||||
| Text ST.Text
|
|
||||||
| TransText ([Language] -> ST.Text)
|
|
||||||
|
|
||||||
runContent :: [Language] -> Content -> LBS.ByteString
|
|
||||||
runContent _ (ByteString sbs) = convertSuccess sbs
|
|
||||||
runContent _ (Text lt) = convertSuccess lt
|
|
||||||
runContent ls (TransText t) = convertSuccess $ t ls
|
|
||||||
|
|
||||||
class ToContent a where
|
|
||||||
toContent :: a -> Content
|
|
||||||
instance ToContent SBS.ByteString where
|
|
||||||
toContent = ByteString
|
|
||||||
instance ToContent LBS.ByteString where
|
|
||||||
toContent = ByteString . convertSuccess
|
|
||||||
instance ToContent String where
|
|
||||||
toContent = Text . convertSuccess
|
|
||||||
instance ToContent Text where
|
|
||||||
toContent = Text . convertSuccess
|
|
||||||
instance ToContent ([Language] -> String) where
|
|
||||||
toContent f = TransText $ convertSuccess . f
|
|
||||||
|
|
||||||
type RepT m = (ContentType, m Content)
|
|
||||||
|
|
||||||
chooseRep :: Monad m
|
|
||||||
=> [ContentType]
|
|
||||||
-> [RepT m]
|
|
||||||
-> RepT m
|
|
||||||
chooseRep cs' rs
|
|
||||||
| null rs = error "All reps must have at least one representation" -- FIXME
|
|
||||||
| otherwise = do
|
|
||||||
let availCs = map fst rs
|
|
||||||
case filter (`elem` availCs) cs' of
|
|
||||||
[] -> head rs
|
|
||||||
[ctype] -> (ctype, fromJust $ lookup ctype rs) -- FIXME
|
|
||||||
_ -> error "Overlapping representations" -- FIXME just take the first?
|
|
||||||
|
|
||||||
-- | Something which can be represented as multiple content types.
|
|
||||||
-- Each content type is called a representation of the data.
|
|
||||||
class Monad m => HasReps a m where
|
|
||||||
-- | Provide an ordered list of possible representations, depending on
|
|
||||||
-- content type. If the user asked for a specific response type (like
|
|
||||||
-- text/html), then that will get priority. If not, then the first
|
|
||||||
-- element in this list will be used.
|
|
||||||
reps :: a -> [RepT m]
|
|
||||||
|
|
||||||
-- | Abnormal return codes.
|
-- | Abnormal return codes.
|
||||||
data ErrorResult =
|
data ErrorResult =
|
||||||
Redirect String
|
Redirect String
|
||||||
@ -155,19 +91,6 @@ toPair (DeleteCookie key) = return
|
|||||||
key ++ "=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT")
|
key ++ "=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT")
|
||||||
toPair (Header key value) = return (key, value)
|
toPair (Header key value) = return (key, value)
|
||||||
|
|
||||||
------ Generic responses
|
|
||||||
-- FIXME move these to Handler?
|
|
||||||
-- | Return a response with an arbitrary content type.
|
|
||||||
genResponse :: (Monad m, ToContent t)
|
|
||||||
=> ContentType
|
|
||||||
-> t
|
|
||||||
-> [RepT m]
|
|
||||||
genResponse ct t = [(ct, return $ toContent t)]
|
|
||||||
|
|
||||||
-- | Return a response with a text/html content type.
|
|
||||||
htmlResponse :: (Monad m, ToContent t) => t -> [RepT m]
|
|
||||||
htmlResponse = genResponse "text/html"
|
|
||||||
|
|
||||||
#if TEST
|
#if TEST
|
||||||
----- Testing
|
----- Testing
|
||||||
testSuite :: Test
|
testSuite :: Test
|
||||||
|
|||||||
@ -7,7 +7,7 @@ module Yesod.Yesod
|
|||||||
|
|
||||||
import Yesod.Rep
|
import Yesod.Rep
|
||||||
import Data.Object.Html (toHtmlObject)
|
import Data.Object.Html (toHtmlObject)
|
||||||
import Yesod.Response hiding (reps, ContentType, Content, chooseRep)
|
import Yesod.Response
|
||||||
import Yesod.Request
|
import Yesod.Request
|
||||||
import Yesod.Constants
|
import Yesod.Constants
|
||||||
--import Yesod.Definitions
|
--import Yesod.Definitions
|
||||||
@ -43,7 +43,7 @@ class Yesod a where
|
|||||||
]
|
]
|
||||||
|
|
||||||
-- | Output error response pages.
|
-- | Output error response pages.
|
||||||
errorHandler :: a -> RawRequest -> ErrorResult -> [ContentType] -> MyIdentity (ContentType, Content) -- FIXME better type sig?
|
errorHandler :: a -> RawRequest -> ErrorResult -> [ContentType] -> (ContentType, Content) -- FIXME better type sig?
|
||||||
errorHandler = defaultErrorHandler
|
errorHandler = defaultErrorHandler
|
||||||
-- | Whether or not we should check for overlapping resource names.
|
-- | Whether or not we should check for overlapping resource names.
|
||||||
checkOverlaps :: a -> Bool
|
checkOverlaps :: a -> Bool
|
||||||
@ -60,20 +60,20 @@ defaultErrorHandler :: a
|
|||||||
-> RawRequest
|
-> RawRequest
|
||||||
-> ErrorResult
|
-> ErrorResult
|
||||||
-> [ContentType]
|
-> [ContentType]
|
||||||
-> MyIdentity (ContentType, Content)
|
-> (ContentType, Content)
|
||||||
defaultErrorHandler _ rr NotFound = chooseRep $ pure . toHtmlObject $
|
defaultErrorHandler _ rr NotFound = chooseRep $ toHtmlObject $
|
||||||
"Not found: " ++ show rr
|
"Not found: " ++ show rr
|
||||||
defaultErrorHandler _ _ (Redirect url) =
|
defaultErrorHandler _ _ (Redirect url) =
|
||||||
chooseRep $ pure . toHtmlObject $ "Redirect to: " ++ url
|
chooseRep $ toHtmlObject $ "Redirect to: " ++ url
|
||||||
defaultErrorHandler _ _ (InternalError e) =
|
defaultErrorHandler _ _ (InternalError e) =
|
||||||
chooseRep $ pure . toHtmlObject $ "Internal server error: " ++ e
|
chooseRep $ toHtmlObject $ "Internal server error: " ++ e
|
||||||
defaultErrorHandler _ _ (InvalidArgs ia) =
|
defaultErrorHandler _ _ (InvalidArgs ia) =
|
||||||
chooseRep $ pure $ toHtmlObject
|
chooseRep $ toHtmlObject
|
||||||
[ ("errorMsg", toHtmlObject "Invalid arguments")
|
[ ("errorMsg", toHtmlObject "Invalid arguments")
|
||||||
, ("messages", toHtmlObject ia)
|
, ("messages", toHtmlObject ia)
|
||||||
]
|
]
|
||||||
defaultErrorHandler _ _ PermissionDenied =
|
defaultErrorHandler _ _ PermissionDenied =
|
||||||
chooseRep $ pure $ toHtmlObject "Permission denied"
|
chooseRep $ toHtmlObject "Permission denied"
|
||||||
|
|
||||||
toHackApp :: Yesod y => y -> Hack.Application
|
toHackApp :: Yesod y => y -> Hack.Application
|
||||||
toHackApp a env = do
|
toHackApp a env = do
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user