{-| Description: the monad and all of its support you build your api using this stuff. -} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE CPP #-} module Web.Respond.Monad ( -- * the monad interface MonadRespond(..), -- ** an implementation RespondT, runRespondT, mapRespondT, -- * handling errors FailureHandlers(..), -- ** Getters for each handler unsupportedMethod, unmatchedPath, bodyParseFailed, authFailed, accessDenied, caughtException, unacceptableResponse ) where import Data.Monoid import Control.Applicative import Network.Wai import Network.HTTP.Types.Method import Control.Monad.Trans.Reader (ReaderT(ReaderT), runReaderT, mapReaderT) import qualified Control.Monad.Trans.Identity as Identity import qualified Control.Monad.Trans.Maybe as Maybe import qualified Control.Monad.Trans.List as List import qualified Control.Monad.Trans.Except as Except import qualified Control.Monad.Trans.Cont as Cont import qualified Control.Monad.Trans.State.Lazy as LazyState import qualified Control.Monad.Trans.State.Strict as StrictState import qualified Control.Monad.Trans.Writer.Lazy as LazyWriter import qualified Control.Monad.Trans.Writer.Strict as StrictWriter import qualified Control.Monad.Trans.RWS.Lazy as LazyRWS import qualified Control.Monad.Trans.RWS.Strict as StrictRWS import Control.Monad.Reader.Class import qualified Control.Monad.Cont.Class as Mtl import qualified Control.Monad.Error.Class as Mtl import qualified Control.Monad.State.Class as Mtl import qualified Control.Monad.Writer.Class as Mtl import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Base (MonadBase, liftBase, liftBaseDefault) import Control.Monad.Trans.Control (MonadTransControl, StT, liftWith, restoreT, defaultLiftWith, defaultRestoreT, MonadBaseControl, StM, liftBaseWith, defaultLiftBaseWith, restoreM, defaultRestoreM, ComposeSt) import Control.Monad.Trans.Class import Control.Monad.Logger import Control.Monad.Catch import Control.Lens ((%~), (.~), makeLenses, view) import Web.Respond.Types -- | this class is the api for building your handler. class (Functor m, MonadIO m) => MonadRespond m where -- | perform the WAI application respond action (after converting the -- value to a response) respond :: Response -> m ResponseReceived -- | get out the request. getRequest :: m Request -- | run the inner route with a modified request withRequest :: Request -> m a -> m a -- | get the 'FailureHandlers'. getHandlers :: m FailureHandlers -- | run an inner action that will see an updates set of error -- handlers. this is useful when you know that inner actions will need -- to do resource cleanup or something. withHandlers :: (FailureHandlers -> FailureHandlers) -> m a -> m a -- | get the path as it's been consumed so far. getPath :: m PathConsumer -- | run the inner action with an updated path state. withPath :: (PathConsumer -> PathConsumer) -> m a -> m a #define MRESPDEF(mapfun) {\ respond = lift . respond; \ getRequest = lift getRequest; \ withRequest = mapfun . withRequest; \ getHandlers = lift getHandlers; \ withHandlers = mapfun . withHandlers; \ getPath = lift getPath; \ withPath = mapfun . withPath; \ } instance MonadRespond m => MonadRespond (ReaderT a m) where MRESPDEF(mapReaderT) instance MonadRespond m => MonadRespond (Identity.IdentityT m) where MRESPDEF(Identity.mapIdentityT) instance MonadRespond m => MonadRespond (Maybe.MaybeT m) where MRESPDEF(Maybe.mapMaybeT) instance MonadRespond m => MonadRespond (List.ListT m) where MRESPDEF(List.mapListT) instance MonadRespond m => MonadRespond (Except.ExceptT e m) where MRESPDEF(Except.mapExceptT) instance MonadRespond m => MonadRespond (Cont.ContT r m) where MRESPDEF(Cont.mapContT) instance MonadRespond m => MonadRespond (LazyState.StateT s m) where MRESPDEF(LazyState.mapStateT) instance MonadRespond m => MonadRespond (StrictState.StateT s m) where MRESPDEF(StrictState.mapStateT) instance (MonadRespond m, Monoid w) => MonadRespond (LazyWriter.WriterT w m) where MRESPDEF(LazyWriter.mapWriterT) instance (MonadRespond m, Monoid w) => MonadRespond (StrictWriter.WriterT w m) where MRESPDEF(StrictWriter.mapWriterT) instance (Monoid w, MonadRespond m) => MonadRespond (LazyRWS.RWST r w s m) where MRESPDEF(LazyRWS.mapRWST) instance (Monoid w, MonadRespond m) => MonadRespond (StrictRWS.RWST r w s m) where MRESPDEF(StrictRWS.mapRWST) rmapLoggingT :: (m a -> n b) -> LoggingT m a -> LoggingT n b rmapLoggingT f = LoggingT . (f .) . runLoggingT rmapNoLoggingT :: (m a -> n b) -> NoLoggingT m a -> NoLoggingT n b rmapNoLoggingT f = NoLoggingT . f . runNoLoggingT instance MonadRespond m => MonadRespond (LoggingT m) where MRESPDEF(rmapLoggingT) instance MonadRespond m => MonadRespond (NoLoggingT m) where MRESPDEF(rmapNoLoggingT) -- | record containing responders that request matching tools can use when -- failures occur. data FailureHandlers = FailureHandlers { -- | what to do if the request method is not supported _unsupportedMethod :: MonadRespond m => [StdMethod] -> Method -> m ResponseReceived, -- | what to do if the request path has no matches _unmatchedPath :: MonadRespond m => m ResponseReceived, -- | what to do if the body failed to parse _bodyParseFailed :: (MonadRespond m, ReportableError e) => e -> m ResponseReceived, -- | what to do when authentication fails _authFailed :: (MonadRespond m, ReportableError e) => e -> m ResponseReceived, -- | what to do when authorization fails _accessDenied :: (MonadRespond m, ReportableError e) => e -> m ResponseReceived, -- | what to do when an exception has been caught _caughtException :: (MonadRespond m, ReportableError e) => e -> m ResponseReceived, -- | what to do when no media type is acceptable _unacceptableResponse :: (MonadRespond m) => m ResponseReceived } makeLenses ''FailureHandlers -- | this is the environment data used by RespondT. you probably don't want -- to mess with this. data RespondData = RespondData { _handlers :: FailureHandlers, _request :: Request, _responder :: Responder, _pathConsumer :: PathConsumer } makeLenses ''RespondData -- | RespondT is a monad transformer that provides an implementation of -- MonadRespond. you build your application using this. newtype RespondT m a = RespondT { unRespondT :: ReaderT RespondData m a } deriving (Functor, Applicative, Monad, MonadReader RespondData) instance (Functor m, MonadIO m) => MonadRespond (RespondT m) where respond v = view responder >>= \r -> liftIO . r $ v getRequest = view request withRequest r = local (request .~ r) getHandlers = view handlers withHandlers h = local (handlers %~ h) getPath = view pathConsumer withPath f = local (pathConsumer %~ f) runRespondTBase :: RespondT m a -> RespondData -> m a runRespondTBase = runReaderT . unRespondT -- | run the RespondT action with failure handlers and request information. runRespondT :: RespondT m a -> FailureHandlers -> Request -> Responder -> m a runRespondT act h req res = runRespondTBase act $ RespondData h req res (mkPathConsumer $ pathInfo req) mapRespondT :: (m a -> n b) -> RespondT m a -> RespondT n b mapRespondT f = RespondT . mapReaderT f . unRespondT instance MonadTrans RespondT where lift act = RespondT $ lift act instance MonadIO m => MonadIO (RespondT m) where liftIO act = RespondT $ liftIO act instance MonadThrow m => MonadThrow (RespondT m) where throwM = lift . throwM instance MonadCatch m => MonadCatch (RespondT m) where catch act h = RespondT $ catch (unRespondT act) (\e -> unRespondT (h e)) --these next three son of a gun all need UndecidableInstances instance MonadBase b m => MonadBase b (RespondT m) where liftBase = liftBaseDefault -- and these two demand TypeFamilies instance MonadTransControl RespondT where #if MIN_VERSION_monad_control(1, 0, 0) type StT RespondT a = StT (ReaderT RespondData) a liftWith = defaultLiftWith RespondT unRespondT restoreT = defaultRestoreT RespondT #else newtype StT RespondT a = StRespond { unStRespond :: StT (ReaderT RespondData) a } liftWith = defaultLiftWith RespondT unRespondT StRespond restoreT = defaultRestoreT RespondT unStRespond #endif instance MonadBaseControl b m => MonadBaseControl b (RespondT m) where #if MIN_VERSION_monad_control(1, 0, 0) type StM (RespondT m) a = ComposeSt RespondT m a liftBaseWith = defaultLiftBaseWith restoreM = defaultRestoreM #else newtype StM (RespondT m) a = StMT { unStMT :: ComposeSt RespondT m a} liftBaseWith = defaultLiftBaseWith StMT restoreM = defaultRestoreM unStMT #endif instance Mtl.MonadWriter w m => Mtl.MonadWriter w (RespondT m) where writer = lift . Mtl.writer tell = lift . Mtl.tell listen = mapRespondT Mtl.listen pass = mapRespondT Mtl.pass instance Mtl.MonadError e m => Mtl.MonadError e (RespondT m) where throwError = lift . Mtl.throwError catchError m h = RespondT $ Mtl.catchError (unRespondT m) (unRespondT . h) instance Mtl.MonadCont m => Mtl.MonadCont (RespondT m) where callCC = liftCallCC Mtl.callCC where liftCallCC callCC f = RespondT $ ReaderT $ \ r -> callCC $ \ c -> runRespondTBase (f (RespondT . ReaderT . const . c)) r instance Mtl.MonadState s m => Mtl.MonadState s (RespondT m) where get = lift Mtl.get put = lift . Mtl.put state = lift . Mtl.state instance MonadLogger m => MonadLogger (RespondT m) where monadLoggerLog loc src level msg = lift $ monadLoggerLog loc src level msg