module Servant.Server.Internal.RoutingApplication where
import Control.Applicative (Applicative(..), Alternative(..), (<$>))
import Control.Monad (ap, liftM)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Trans.Class
import qualified Data.ByteString as B
import qualified Data.ByteString.Builder as Builder
import qualified Data.ByteString.Lazy as BL
import Data.CaseInsensitive (CI)
import qualified Data.List as L
import Data.Proxy (Proxy(..))
import Network.HTTP.Types (Status(..))
import qualified System.IO.Streams as Streams
import Servant.Server.Internal.SnapShims
import Servant.Server.Internal.ServantErr
import Snap.Core
import Snap.Internal.Http.Types (setResponseBody)
type RoutingApplication m =
Request
-> (RouteResult Response -> m Response) -> m Response
data RouteResult a =
Fail ServantErr
| FailFatal !ServantErr
| Route !a
deriving (Eq, Show, Read, Functor)
toApplication :: forall m. MonadSnap m => RoutingApplication m -> Application m
toApplication ra request respond = do
snapReq <- getRequest
r <- ra request routingRespond
snapResp <- getResponse
rspnd <- respond (r `addHeaders` headers snapResp)
return rspnd
where
routingRespond (Fail err) = case errHTTPCode err of
404 -> pass
_ -> respond $ responseServantErr err
routingRespond (FailFatal err) = respond $ responseServantErr err
routingRespond (Route v) = respond v
responseLBS :: Status -> [(CI B.ByteString, B.ByteString)] -> BL.ByteString -> Response
responseLBS (Status code msg) hs body =
setResponseStatus code msg
. (\r -> L.foldl' (\r' (h,h') -> addHeader h h' r') r hs)
. setResponseBody (\out -> do
Streams.write (Just $ Builder.lazyByteString body) out
return out)
$ emptyResponse
runAction :: MonadSnap m
=> Delayed m env (m a)
-> env
-> Request
-> (RouteResult Response -> m r)
-> (a -> RouteResult Response)
-> m r
runAction action env req respond k = do
runDelayed action env req >>= go >>= respond
where
go (Fail e) = return $ Fail e
go (FailFatal e) = return $ FailFatal e
go (Route a) = do
e <- a
return $ k e
data Delayed m env c where
Delayed :: { capturesD :: env -> DelayedM m captures
, methodD :: DelayedM m ()
, authD :: DelayedM m auth
, bodyD :: DelayedM m body
, serverD :: captures -> auth -> body -> Request -> RouteResult c
} -> Delayed m env c
instance Functor (Delayed m env) where
fmap f Delayed{..} =
Delayed
{ serverD = \ c a b req -> f <$> serverD c a b req
, ..
}
newtype DelayedM m a = DelayedM { runDelayedM :: Request -> m (RouteResult a) }
instance Monad m => Functor (DelayedM m) where
fmap = liftM
instance Monad m => Applicative (DelayedM m) where
pure = return
(<*>) = ap
instance Monad m => Monad (DelayedM m) where
return x = DelayedM (const $ return (Route x))
DelayedM m >>= f =
DelayedM $ \ req -> do
r <- m req
case r of
Fail e -> return $ Fail e
FailFatal e -> return $ FailFatal e
Route a -> runDelayedM (f a) req
instance MonadIO m => MonadIO (DelayedM m) where
liftIO m = DelayedM (const . liftIO $ Route <$> m)
instance (Monad m, MonadSnap m) => Alternative (DelayedM m) where
empty = DelayedM $ \_ -> return (Fail err404)
a <|> b = DelayedM $ \req -> do
respA <- runDelayedM a req
case respA of
Route a' -> return $ Route a'
_ -> runDelayedM b req
instance MonadTrans DelayedM where
lift f = DelayedM $ \_ -> do
a <- f
return $ Route a
emptyDelayed :: Monad m => Proxy (m :: * -> *) -> RouteResult a -> Delayed m env a
emptyDelayed (Proxy :: Proxy m) result =
Delayed (const r) r r r (\ _ _ _ _ -> result)
where
r :: DelayedM m ()
r = return ()
delayedFail :: Monad m => ServantErr -> DelayedM m a
delayedFail err = DelayedM (const $ return $ Fail err)
delayedFailFatal :: Monad m => ServantErr -> DelayedM m a
delayedFailFatal err = DelayedM (const $ return $ FailFatal err)
withRequest :: (Request -> DelayedM m a) -> DelayedM m a
withRequest f = DelayedM (\ req -> runDelayedM (f req) req)
addCapture :: forall env a b captured m. Monad m => Delayed m env (a -> b)
-> (captured -> DelayedM m a)
-> Delayed m (captured, env) b
addCapture Delayed{..} new =
Delayed
{ capturesD = \ (txt, env) -> (,) <$> capturesD env <*> new txt
, serverD = \ (x, v) a b req -> ($ v) <$> serverD x a b req
, ..
}
addMethodCheck :: Monad m
=> Delayed m env a
-> DelayedM m ()
-> Delayed m env a
addMethodCheck Delayed{..} new =
Delayed
{ methodD = methodD <* new
, ..
}
addAuthCheck :: Monad m
=> Delayed m env (a -> b)
-> DelayedM m a
-> Delayed m env b
addAuthCheck Delayed{..} new =
Delayed
{ authD = (,) <$> authD <*> new
, serverD = \ c (y, v) b req -> ($ v) <$> serverD c y b req
, ..
}
addBodyCheck :: Monad m
=> Delayed m env (a -> b)
-> DelayedM m a
-> Delayed m env b
addBodyCheck Delayed{..} new =
Delayed
{ bodyD = (,) <$> bodyD <*> new
, serverD = \ c a (z, v) req -> ($ v) <$> serverD c a z req
, ..
}
addAcceptCheck :: Monad m
=> Delayed m env a
-> DelayedM m ()
-> Delayed m env a
addAcceptCheck Delayed{..} new =
Delayed
{ bodyD = new *> bodyD
, ..
}
passToServer :: Delayed m env (a -> b) -> (Request -> a) -> Delayed m env b
passToServer Delayed{..} x =
Delayed
{ serverD = \ c a b req -> ($ x req) <$> serverD c a b req
, ..
}
runDelayed :: Monad m
=> Delayed m env a
-> env
-> Request
-> m (RouteResult a)
runDelayed Delayed{..} env = runDelayedM $ do
c <- capturesD env
methodD
a <- authD
b <- bodyD
DelayedM (\ req -> return $ serverD c a b req)