diff options
author | Jacques Comeaux <jacquesrcomeaux@gmail.com> | 2022-05-29 16:07:59 -0500 |
---|---|---|
committer | Jacques Comeaux <jacquesrcomeaux@gmail.com> | 2022-05-29 16:07:59 -0500 |
commit | 8a06c083fab9c66e754c7ed5a75dd4d89131c43e (patch) | |
tree | 28098c165bce0d8ebb5dda2d5390e63ddca1ab1e | |
parent | 399ab3ee1653bbc0da69e2cc299aa2a313e765b4 (diff) |
Improve error reporting
-rw-r--r-- | app/Main.hs | 42 | ||||
-rw-r--r-- | src/Tomato/App.hs | 32 | ||||
-rw-r--r-- | src/Tomato/Bot.hs | 19 | ||||
-rw-r--r-- | src/Tomato/Data/Except.hs | 9 | ||||
-rw-r--r-- | src/Tomato/Data/Message.hs (renamed from src/Data/Message.hs) | 2 | ||||
-rw-r--r-- | src/Tomato/Data/Tomato.hs (renamed from src/Data/Tomato.hs) | 2 | ||||
-rw-r--r-- | src/Tomato/Post.hs | 36 | ||||
-rw-r--r-- | src/Tomato/Retrieve.hs | 34 | ||||
-rw-r--r-- | src/Tomato/Validate.hs | 3 |
9 files changed, 109 insertions, 70 deletions
diff --git a/app/Main.hs b/app/Main.hs index 27dcbae..d464aa8 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -6,52 +6,22 @@ import RIO import Data.Conduit (runConduit, (.|)) import Data.Conduit.Attoparsec (sinkParser) -import Network.HTTP.Req (defaultHttpConfig, runReq) import Network.HTTP.Types (status200, status500) import Network.Wai (responseBuilder, responseLBS, Response) import Network.Wai.Conduit (sourceRequestBody) import Network.Wai.Handler.Warp (run) import System.Environment (getArgs) -import Data.Message (InMessage (..)) -import Tomato.Post (postTomato) +import Tomato.App (runApp) +import Tomato.Bot (tomatoBot) +import Tomato.Data.Except (DecodeException (..)) +import Tomato.Data.Message (InMessage (..)) import Tomato.Validate (isTomato) -import Tomato.Retrieve (randomTomato) import qualified Data.Aeson as Ae import qualified Data.ByteString.Lazy.Char8 as BL8 import qualified RIO.List.Partial as L' - - -data App = App - { appLogFunc :: !LogFunc - , appToken :: !Text - , appBotId :: !Text - } - -instance HasLogFunc App where - logFuncL = lens appLogFunc (\x y -> x { appLogFunc = y }) - -runApp :: RIO App a -> IO a -runApp inner = do - logOptions' <- logOptionsHandle stderr False - let logOptions = setLogUseTime True $ setLogUseLoc True logOptions' - -- let logOptions = logOptions' - withLogFunc logOptions $ \logFunc -> do - let app = App - { appLogFunc = logFunc - , appToken = "placeholder" - , appBotId = "placeholder" - } - runRIO app inner - -tomatoBot :: RIO App () -tomatoBot = do - logInfo $ "Fetching tomato" - runReq defaultHttpConfig randomTomato - logInfo $ "Posting tomato" - runReq defaultHttpConfig postTomato - logInfo $ "Done" +import qualified RIO.Text as T runServer :: Int -> IO () runServer port = run port $ \request send -> do @@ -61,7 +31,7 @@ runServer port = run port $ \request send -> do .| sinkParser Ae.json case Ae.fromJSON val of Ae.Success r -> return r - Ae.Error _s -> error "handle this later" + Ae.Error s -> throwM $ DecodeException $ T.pack s case eres of Left e -> send $ errorResponse e Right inMes -> do diff --git a/src/Tomato/App.hs b/src/Tomato/App.hs new file mode 100644 index 0000000..14bf61a --- /dev/null +++ b/src/Tomato/App.hs @@ -0,0 +1,32 @@ +module Tomato.App + ( App (..) + , runApp + ) where + +import RIO + + +data App = App + { appLogFunc :: !LogFunc + , appToken :: !ByteString + , appBotId :: !Text + , appFile :: !FilePath + , appClientId :: !Text + } + +instance HasLogFunc App where + logFuncL = lens appLogFunc (\x y -> x { appLogFunc = y }) + +runApp :: RIO App a -> IO a +runApp inner = do + logOptions' <- logOptionsHandle stderr False + let logOptions = setLogUseTime True $ setLogUseLoc True logOptions' + withLogFunc logOptions $ \logFunc -> do + let app = App + { appLogFunc = logFunc + , appToken = "t2YhlxxwZmn2cWfkAomjMc6BgVPMaC5NRkqHzGQl" + , appBotId = "713bcb4a4604006c944804552c" + , appFile = "tomato.png" + , appClientId = "FbzqI-oR7277JwL1ZGsyUw7yG1F5U0U3WhQ3kOW71Do" + } + runRIO app inner diff --git a/src/Tomato/Bot.hs b/src/Tomato/Bot.hs new file mode 100644 index 0000000..8d4cf6d --- /dev/null +++ b/src/Tomato/Bot.hs @@ -0,0 +1,19 @@ +module Tomato.Bot + ( tomatoBot + ) where + +import RIO + +import Tomato.App (App) +import Tomato.Post (postTomato) +import Tomato.Retrieve (randomTomato) + + +-- | Fetch and post a tomato +tomatoBot :: RIO App () +tomatoBot = do + logInfo $ "Fetching tomato" + randomTomato + logInfo $ "Posting tomato" + postTomato + logInfo $ "Done" diff --git a/src/Tomato/Data/Except.hs b/src/Tomato/Data/Except.hs new file mode 100644 index 0000000..30212cf --- /dev/null +++ b/src/Tomato/Data/Except.hs @@ -0,0 +1,9 @@ +module Tomato.Data.Except + ( DecodeException (..) + ) where + +import RIO + +data DecodeException = DecodeException !Text + deriving (Show, Typeable) +instance Exception DecodeException diff --git a/src/Data/Message.hs b/src/Tomato/Data/Message.hs index c218cdc..0f650db 100644 --- a/src/Data/Message.hs +++ b/src/Tomato/Data/Message.hs @@ -1,5 +1,5 @@ {-# LANGUAGE DeriveGeneric #-} -module Data.Message +module Tomato.Data.Message ( InMessage (..) , GMIUrl (..) , OutMessage (..) diff --git a/src/Data/Tomato.hs b/src/Tomato/Data/Tomato.hs index fc9b8b5..4b75adf 100644 --- a/src/Data/Tomato.hs +++ b/src/Tomato/Data/Tomato.hs @@ -1,4 +1,4 @@ -module Data.Tomato +module Tomato.Data.Tomato ( Links (..) , Tomato (..) , Urls (..) diff --git a/src/Tomato/Post.hs b/src/Tomato/Post.hs index c3bd847..4adcdbb 100644 --- a/src/Tomato/Post.hs +++ b/src/Tomato/Post.hs @@ -10,6 +10,9 @@ import Network.HTTP.Req ( jsonResponse , ignoreResponse , req + , Req + , defaultHttpConfig + , runReq , ReqBodyFile (..) , ReqBodyJson (..) , (/:) @@ -20,46 +23,47 @@ import Network.HTTP.Req , responseBody ) -import Data.Message (GMIUrl (..), OutMessage (..)) +import Tomato.Data.Except (DecodeException (..)) +import Tomato.Data.Message (GMIUrl (..), OutMessage (..)) +import Tomato.App (App (..)) import qualified Data.Aeson as Ae import qualified Data.Aeson.KeyMap as Ae +import qualified RIO.Text as T --- TODO global config reader monad -tomatoFile :: FilePath -tomatoFile = "tomato.png" - -accessToken :: ByteString -accessToken = "t2YhlxxwZmn2cWfkAomjMc6BgVPMaC5NRkqHzGQl" - -botId :: Text -botId = "713bcb4a4604006c944804552c" -- | Post the current tomato -postTomato :: Req () +postTomato :: RIO App () postTomato = do url <- uploadTomato postMessage $ OutMessage url -- | Upload the current tomato to the GroupMe image server -uploadTomato :: Req GMIUrl +uploadTomato :: RIO App GMIUrl uploadTomato = do + tomatoFile <- asks appFile + accessToken <- asks appToken let url = https "image.groupme.com" /: "pictures" - js <- req POST url (ReqBodyFile tomatoFile) jsonResponse $ + js <- rr $ req POST url (ReqBodyFile tomatoFile) jsonResponse $ header "X-Access-Token" accessToken <> header "Content-Type" "image/png" gmURL <- case Ae.fromJSON (responseBody js) of Ae.Success r -> pure r - Ae.Error _s -> error "deal with this later" -- TODO error monad + Ae.Error s -> throwM $ DecodeException $ T.pack s return gmURL -- | Post a message to the group with image as attachment -postMessage :: OutMessage -> Req () +postMessage :: OutMessage -> RIO App () postMessage outMes = do + botId <- asks appBotId + accessToken <- asks appToken let url = https "api.groupme.com" /: "v3" /: "bots" /: "post" outMes' = case Ae.toJSON outMes of Ae.Object o -> Ae.Object $ Ae.insert "bot_id" (Ae.toJSON botId) o _ -> error "exceptional" - req POST url (ReqBodyJson outMes') ignoreResponse $ + rr $ req POST url (ReqBodyJson outMes') ignoreResponse $ header "X-Access-Token" accessToken return () + +rr :: Req a -> RIO App a +rr = runReq defaultHttpConfig diff --git a/src/Tomato/Retrieve.hs b/src/Tomato/Retrieve.hs index 80bdac6..2f7f20b 100644 --- a/src/Tomato/Retrieve.hs +++ b/src/Tomato/Retrieve.hs @@ -10,49 +10,53 @@ import Network.HTTP.Req ( jsonResponse , bsResponse , req + , Req + , runReq + , defaultHttpConfig , NoReqBody (..) , (/:) , (=:) - , Req , GET (..) , https , responseBody ) -import Data.Tomato (Tomato (..), Links (..)) +import Tomato.App (App (..)) +import Tomato.Data.Except (DecodeException (..)) +import Tomato.Data.Tomato (Tomato (..), Links (..)) import qualified Data.Aeson as Ae import qualified RIO.ByteString as B +import qualified RIO.Text as T --- TODO global config reader monad -tomatoFile :: FilePath -tomatoFile = "tomato.png" - -clientId :: Text -clientId = "FbzqI-oR7277JwL1ZGsyUw7yG1F5U0U3WhQ3kOW71Do" - -- | Get a random tomato -randomTomato :: Req () +randomTomato :: RIO App () randomTomato = queryTomato >>= downloadTomato -- | Ask for a tomato -queryTomato :: Req Tomato +queryTomato :: RIO App Tomato queryTomato = do + clientId <- asks appClientId let url = https "api.unsplash.com" /: "photos" /: "random" - js <- req GET url NoReqBody jsonResponse $ + js <- rr $ req GET url NoReqBody jsonResponse $ "query" =: ("tomato" :: Text) <> "client_id" =: clientId tomato <- case Ae.fromJSON (responseBody js) of Ae.Success r -> pure r - Ae.Error _s -> error "deal with this later" + Ae.Error s -> throwM $ DecodeException $ T.pack s return tomato -- | Download a specific tomato -downloadTomato :: Tomato -> Req () +downloadTomato :: Tomato -> RIO App () downloadTomato tom = do + tomatoFile <- asks appFile + clientId <- asks appClientId let url = download (links tom) - bs <- req GET url NoReqBody bsResponse $ + bs <- rr $ req GET url NoReqBody bsResponse $ "query" =: ("tomato" :: Text) <> "client_id" =: clientId B.writeFile tomatoFile (responseBody bs) + +rr :: Req a -> RIO App a +rr = runReq defaultHttpConfig diff --git a/src/Tomato/Validate.hs b/src/Tomato/Validate.hs index 0afc4b5..fe0d82a 100644 --- a/src/Tomato/Validate.hs +++ b/src/Tomato/Validate.hs @@ -4,9 +4,10 @@ module Tomato.Validate import RIO -import Data.Message (InMessage (..)) +import Tomato.Data.Message (InMessage (..)) +-- | Check if a message is a tomato request isTomato :: InMessage -> Bool isTomato m = not (hasAttach m) && |