hcheckers-0.1.0.2: src/Rest/Common.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Rest.Common where
import Control.Monad.Reader
import Control.Concurrent
import Control.Exception as E
import Control.Monad.Catch
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Data.Default
import Data.Aeson hiding (json)
import Data.String
import System.Log.Heavy
import Web.Scotty.Trans
import Network.HTTP.Types.Status
import qualified Network.HTTP.Types as H
import qualified Network.Wai as Wai
import Network.Wai.Handler.Warp
import System.Exit
import Core.Types
import Core.Json () -- import instances only
withGameContext :: GameId -> Checkers a -> Checkers a
withGameContext gameId actions = withLogVariable "game" gameId actions
liftCheckers :: GameId -> Checkers a -> Rest a
liftCheckers gameId actions = liftCheckers' (Just gameId) actions
liftCheckers_ :: Checkers a -> Rest a
liftCheckers_ actions = liftCheckers' Nothing actions
liftCheckers' :: Maybe GameId -> Checkers a -> Rest a
liftCheckers' mbId actions = do
res <- lift $ wrap $ tryC actions
case res of
Right result -> return result
Left err -> raise500 err
where
wrap r = case mbId of
Nothing -> r
Just gameId -> withGameContext gameId r
raise500 :: Error -> Rest a
raise500 err = do
text $ TL.pack $ show err
raiseStatus status500 err
error400 :: T.Text -> Rest ()
error400 message = do
json $ object ["error" .= message]
status status400
transformError :: Error -> Rest ()
transformError (Unhandled err) = do
error400 $ T.pack err
transformError (NoSuchMoveExt move side board possible) = do
json $ object [
"error" .= ("no such move" :: T.Text),
"move" .= move,
"side" .= side,
"board" .= board,
"possible" .= possible
]
status status400
transformError (InvalidGameStatus expected actual) = do
json $ object [
"error" .= ("invalid game status" :: T.Text),
"expected" .= expected,
"actual" .= actual
]
transformError err = do
error400 $ T.pack $ show err
instance Parsable Side where
parseParam "1" = Right First
parseParam "2" = Right Second
parseParam text = Left $ "unknown side"
instance ScottyError Error where
stringError str = Unhandled str
showError err = TL.pack $ show err
restOptions :: String -> Port -> Web.Scotty.Trans.Options
restOptions host port =
Options 0 $ setOnExceptionResponse errorHandler $
setHost (fromString host) $
setPort port (settings def)
errorHandler :: SomeException -> Wai.Response
errorHandler e
| Just (err :: Error) <- fromException e =
Wai.responseLBS H.internalServerError500
[(H.hContentType, "text/plain; charset=utf-8")]
(fromString $ show err)
| otherwise =
Wai.responseLBS H.internalServerError500
[(H.hContentType, "text/plain; charset=utf-8")]
(fromString $ show e)
ioErrorHandler :: IOError -> Checkers ()
ioErrorHandler err = liftIO $ do
putStrLn $ "IO error: " ++ show err
exitWith (ExitFailure 2)
runRestServer :: String -> Int -> (MVar () -> ScottyT Error Checkers ()) -> Checkers ()
runRestServer host port restServer = do
cs <- ask
let getResponse m = do
res <- runCheckersT m cs
case res of
Right response -> return response
Left err -> fail $ show err
-- portOpen <- liftIO $ checkPort host port
shutdownVar <- liftIO newEmptyMVar
forkCheckers $ handleIOError ioErrorHandler $ scottyOptsT (restOptions host (fromIntegral port)) getResponse (restServer shutdownVar)
liftIO $ takeMVar shutdownVar
-- REST thread should be able to write the response to Shutdown request.
liftIO $ threadDelay (1000 * 1000)
data BattleRq = BattleRq {
brqRules :: String
, brqAi1 :: Value
, brqAi2 :: Value
}
instance ToJSON BattleRq where
toJSON rq =
object ["rules" .= brqRules rq, "ai1" .= brqAi1 rq, "ai2" .= brqAi2 rq]
instance FromJSON BattleRq where
parseJSON (Object v) = BattleRq
<$> v .: "rules"
<*> v .: "ai1"
<*> v .: "ai2"