Web Errorhandling with ExceptT
This commit is contained in:
parent
59dcb2a2e7
commit
76d6dfc263
|
@ -31,6 +31,7 @@ dependencies:
|
||||||
- bytestring
|
- bytestring
|
||||||
- uuid
|
- uuid
|
||||||
- lifted-base
|
- lifted-base
|
||||||
|
- mtl
|
||||||
|
|
||||||
ghc-options:
|
ghc-options:
|
||||||
- -Wall
|
- -Wall
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
module RoomDataHandler
|
module RoomDataHandler
|
||||||
( roomDataHandler,
|
( roomDataHandler,
|
||||||
)
|
)
|
||||||
|
@ -5,12 +7,9 @@ where
|
||||||
|
|
||||||
import BroadcastUserData (broadcastUserData)
|
import BroadcastUserData (broadcastUserData)
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Data.Aeson
|
import Control.Monad.Except (MonadError, throwError)
|
||||||
( decode,
|
import Data.Aeson (eitherDecodeStrict)
|
||||||
decodeStrict,
|
import Data.Aeson.Types (FromJSON)
|
||||||
eitherDecodeStrict,
|
|
||||||
encode,
|
|
||||||
)
|
|
||||||
import Network.HTTP.Types (status200, status500)
|
import Network.HTTP.Types (status200, status500)
|
||||||
import Network.Wai
|
import Network.Wai
|
||||||
( ResponseReceived,
|
( ResponseReceived,
|
||||||
|
@ -24,36 +23,38 @@ import Types.RoomsState
|
||||||
updateRoomState,
|
updateRoomState,
|
||||||
)
|
)
|
||||||
import Types.WebEnv (HasWebEnv (getRequest), getRespond)
|
import Types.WebEnv (HasWebEnv (getRequest), getRespond)
|
||||||
import WebSocket (broadCastToClients)
|
|
||||||
|
|
||||||
roomDataHandler ::
|
roomDataHandler ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
HasWebEnv env,
|
HasWebEnv env,
|
||||||
HasConnectedClientState env,
|
HasConnectedClientState env,
|
||||||
MonadReader env m,
|
MonadReader env m,
|
||||||
|
MonadError ResponseReceived m,
|
||||||
HasRoomsState env
|
HasRoomsState env
|
||||||
) =>
|
) =>
|
||||||
m ResponseReceived
|
m ResponseReceived
|
||||||
roomDataHandler = do
|
roomDataHandler = do
|
||||||
|
newRoomData <- parseBodyOrBadRequest
|
||||||
|
whenM (roomStateDiffers newRoomData) $ do
|
||||||
|
updateRoomState newRoomData
|
||||||
|
broadcastUserData
|
||||||
|
success
|
||||||
|
|
||||||
|
parseBodyOrBadRequest ::
|
||||||
|
( MonadIO m,
|
||||||
|
HasWebEnv env,
|
||||||
|
MonadReader env m,
|
||||||
|
MonadError ResponseReceived m,
|
||||||
|
FromJSON a
|
||||||
|
) =>
|
||||||
|
m a
|
||||||
|
parseBodyOrBadRequest = do
|
||||||
body <- getRequestBody
|
body <- getRequestBody
|
||||||
case eitherDecodeStrict body of
|
case eitherDecodeStrict body of
|
||||||
Left errorMessage -> badRequest errorMessage
|
Left errorMessage -> do
|
||||||
Right newState -> do
|
response <- badRequest errorMessage
|
||||||
respond' <- getRespond <$> ask
|
throwError response
|
||||||
shouldAct <- roomStateDiffers newState
|
Right newState -> return newState
|
||||||
( if shouldAct
|
|
||||||
then
|
|
||||||
( do
|
|
||||||
putStrLn "b1"
|
|
||||||
updateRoomState newState
|
|
||||||
broadcastUserData
|
|
||||||
putStrLn "b2"
|
|
||||||
ans respond'
|
|
||||||
)
|
|
||||||
else ans respond'
|
|
||||||
)
|
|
||||||
where
|
|
||||||
ans respond' = liftIO $ respond' $ responseLBS status200 [("Content-Type", "text/plain")] ""
|
|
||||||
|
|
||||||
getRequestBody ::
|
getRequestBody ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
|
@ -79,5 +80,19 @@ badRequest errorMessage = do
|
||||||
responseLBS
|
responseLBS
|
||||||
status500
|
status500
|
||||||
[("Content-Type", "text/plain")]
|
[("Content-Type", "text/plain")]
|
||||||
( fromString ("Bad request. Reason: " <> errorMessage)
|
(fromString ("Bad request. Reason: " <> errorMessage))
|
||||||
)
|
|
||||||
|
success ::
|
||||||
|
( MonadIO m,
|
||||||
|
HasWebEnv env,
|
||||||
|
MonadReader env m
|
||||||
|
) =>
|
||||||
|
m ResponseReceived
|
||||||
|
success = do
|
||||||
|
respond' <- getRespond <$> ask
|
||||||
|
liftIO $
|
||||||
|
respond' $
|
||||||
|
responseLBS
|
||||||
|
status200
|
||||||
|
[("Content-Type", "text/plain")]
|
||||||
|
""
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
module WebServer (runWebServer) where
|
module WebServer (runWebServer) where
|
||||||
|
|
||||||
import ClassyPrelude hiding (decodeUtf8)
|
import ClassyPrelude hiding (decodeUtf8)
|
||||||
|
import Control.Monad.Except
|
||||||
import Data.Text.Encoding
|
import Data.Text.Encoding
|
||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
import Network.Wai
|
import Network.Wai
|
||||||
|
@ -23,15 +24,28 @@ import Types.RoomsState
|
||||||
import Types.WebEnv
|
import Types.WebEnv
|
||||||
import WebSocket (broadCastToClients)
|
import WebSocket (broadCastToClients)
|
||||||
|
|
||||||
newtype WebApp env a = WebApp {unWebApp :: env -> IO a}
|
newtype ExceptTApp e a = E {unExceptTApp :: IO (Either e a)}
|
||||||
|
deriving
|
||||||
|
( Functor,
|
||||||
|
Applicative,
|
||||||
|
Monad,
|
||||||
|
MonadIO,
|
||||||
|
MonadError e
|
||||||
|
)
|
||||||
|
via ExceptT e IO
|
||||||
|
|
||||||
|
newtype WebApp env a = WebApp {unWebApp :: env -> IO (Either ResponseReceived a)}
|
||||||
deriving
|
deriving
|
||||||
( Functor,
|
( Functor,
|
||||||
Applicative,
|
Applicative,
|
||||||
Monad,
|
Monad,
|
||||||
MonadReader env,
|
MonadReader env,
|
||||||
MonadIO
|
MonadIO,
|
||||||
|
MonadError ResponseReceived
|
||||||
)
|
)
|
||||||
via ReaderT env IO
|
via ReaderT env (ExceptTApp ResponseReceived)
|
||||||
|
|
||||||
|
-- via ExceptT ResponseReceived (ReaderT env IO)
|
||||||
|
|
||||||
getRequestPath ::
|
getRequestPath ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
|
@ -48,7 +62,8 @@ app ::
|
||||||
HasWebEnv env,
|
HasWebEnv env,
|
||||||
HasConnectedClientState env,
|
HasConnectedClientState env,
|
||||||
MonadReader env m,
|
MonadReader env m,
|
||||||
HasRoomsState env
|
HasRoomsState env,
|
||||||
|
MonadError ResponseReceived m
|
||||||
) =>
|
) =>
|
||||||
m ResponseReceived
|
m ResponseReceived
|
||||||
app = requestPathHandler
|
app = requestPathHandler
|
||||||
|
@ -58,7 +73,8 @@ requestPathHandler ::
|
||||||
HasWebEnv env,
|
HasWebEnv env,
|
||||||
HasConnectedClientState env,
|
HasConnectedClientState env,
|
||||||
MonadReader env m,
|
MonadReader env m,
|
||||||
HasRoomsState env
|
HasRoomsState env,
|
||||||
|
MonadError ResponseReceived m
|
||||||
) =>
|
) =>
|
||||||
m ResponseReceived
|
m ResponseReceived
|
||||||
requestPathHandler = do
|
requestPathHandler = do
|
||||||
|
@ -69,17 +85,20 @@ requestPathHandler = do
|
||||||
notFound ::
|
notFound ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
HasWebEnv env,
|
HasWebEnv env,
|
||||||
MonadReader env m
|
MonadReader env m,
|
||||||
|
MonadError ResponseReceived m
|
||||||
) =>
|
) =>
|
||||||
m ResponseReceived
|
m ResponseReceived
|
||||||
notFound = do
|
notFound = do
|
||||||
respond' <- getRespond <$> ask
|
respond' <- getRespond <$> ask
|
||||||
liftIO $
|
response <-
|
||||||
respond' $
|
liftIO $
|
||||||
responseLBS
|
respond' $
|
||||||
status404
|
responseLBS
|
||||||
[("Content-Type", "text/plain")]
|
status404
|
||||||
"404 - Not Found"
|
[("Content-Type", "text/plain")]
|
||||||
|
"404 - Not Found"
|
||||||
|
throwError response
|
||||||
|
|
||||||
runWebApp ::
|
runWebApp ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
|
@ -91,7 +110,13 @@ runWebApp = do
|
||||||
return
|
return
|
||||||
( \req res -> do
|
( \req res -> do
|
||||||
let webEnv = WebEnv {appEnv = env, request = req, respond = res}
|
let webEnv = WebEnv {appEnv = env, request = req, respond = res}
|
||||||
unWebApp app webEnv
|
eitherResult <- unWebApp app webEnv
|
||||||
|
case eitherResult of
|
||||||
|
Left result -> do
|
||||||
|
liftIO $ putStrLn "Some error occured"
|
||||||
|
return result
|
||||||
|
Right result -> do
|
||||||
|
return result
|
||||||
)
|
)
|
||||||
|
|
||||||
runWebServer ::
|
runWebServer ::
|
||||||
|
|
|
@ -1,26 +1,22 @@
|
||||||
{-# LANGUAGE DerivingVia #-}
|
{-# LANGUAGE DerivingVia #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
{-# LANGUAGE UndecidableInstances #-}
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
|
|
||||||
|
|
||||||
{-# HLINT ignore "Redundant bracket" #-}
|
|
||||||
|
|
||||||
module WebSocket (broadCastToClients, runWebSocketServer) where
|
module WebSocket (broadCastToClients, runWebSocketServer) where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Data.UUID (UUID)
|
import Data.UUID (UUID)
|
||||||
import Data.UUID.V4 (nextRandom)
|
import Data.UUID.V4 (nextRandom)
|
||||||
import Network.WebSockets qualified as WS
|
import qualified Network.WebSockets as WS
|
||||||
import Types.AppTypes
|
import Types.AppTypes (Env (connectedClientsState),
|
||||||
( Env (connectedClientsState),
|
HasConnectedClientState (getConnectedClientState))
|
||||||
HasConnectedClientState (getConnectedClientState),
|
import Types.ConnectionState (Client (..), ConnectedClients)
|
||||||
)
|
|
||||||
import Types.ConnectionState (Client (..), ConnectedClients)
|
|
||||||
|
|
||||||
data WSEnv = WSEnv
|
data WSEnv = WSEnv
|
||||||
{ appEnv :: Env,
|
{ appEnv :: Env,
|
||||||
connection :: WS.Connection,
|
connection :: WS.Connection,
|
||||||
clientId :: UUID
|
clientId :: UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
instance HasConnectedClientState WSEnv where
|
instance HasConnectedClientState WSEnv where
|
||||||
|
|
Loading…
Reference in New Issue