Web Errorhandling with ExceptT

This commit is contained in:
qvalentin 2023-01-29 12:08:50 +01:00
parent 59dcb2a2e7
commit 76d6dfc263
4 changed files with 91 additions and 54 deletions

View File

@ -31,6 +31,7 @@ dependencies:
- bytestring - bytestring
- uuid - uuid
- lifted-base - lifted-base
- mtl
ghc-options: ghc-options:
- -Wall - -Wall

View File

@ -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")]
""

View File

@ -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
response <-
liftIO $ liftIO $
respond' $ respond' $
responseLBS responseLBS
status404 status404
[("Content-Type", "text/plain")] [("Content-Type", "text/plain")]
"404 - Not Found" "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 ::

View File

@ -1,20 +1,16 @@
{-# 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