vigilance-0.1.0.0: src/Utils/Vigilance/Client/Client.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE QuasiQuotes #-}
module Utils.Vigilance.Client.Client ( getList
, getInfo
, pause
, unPause
, checkIn
, test
, displayList
, displayWatch
, displayWatchInfo
, displayFailedNotifications
-- for testing
, renderList
, renderWatch
, renderWatchInfo
, renderFailedNotifications
, VError(..) ) where
import ClassyPrelude
import Control.Lens
import Control.Monad.Trans.Reader (asks)
import Data.Aeson ( FromJSON
, json
, Result(..)
, fromJSON )
import Blaze.ByteString.Builder (Builder)
import Data.Ix (inRange)
import Network.Http.Client ( Method(GET, POST)
, Response
, emptyBody
, withConnection
, openConnection
, http
, setAccept
, setHeader
, buildRequest
, sendRequest
, receiveResponse
, RequestBuilder
, getStatusCode
, StatusCode )
import System.IO.Streams.Attoparsec (parseFromStream)
import qualified System.IO.Streams as S
import Text.InterpolatedString.Perl6 (qc)
import Utils.Vigilance.Client.Config
import Utils.Vigilance.Types
displayList :: [EWatch] -> IO ()
displayList = putStrLn . renderList
renderList :: [EWatch] -> Text
renderList = unlines' . map renderWatch
displayWatch :: EWatch -> IO ()
displayWatch = putStrLn . renderWatch
renderWatch :: EWatch -> Text
renderWatch w = [qc|{name} ({i}) - {interval} - {state}|]
where name = w ^. watchName . unWatchName
i = w ^. watchId . unID
interval = w ^. watchInterval
state = w ^. watchWState . to renderState
displayWatchInfo :: EWatch -> IO ()
displayWatchInfo = putStrLn . renderWatchInfo
displayFailedNotifications :: [FailedNotification] -> IO ()
displayFailedNotifications = putStrLn . renderFailedNotifications
renderFailedNotifications :: [FailedNotification] -> Text
renderFailedNotifications [] = "All notifications sent successfully."
renderFailedNotifications fns = unlines' . (header:) . map render $ fns
where header = "The following errors were encountered when testing:"
render fn = [qc|- {pref} for {wn} ({wid}): {err}|]
where pref = fn ^. failedPref . to renderNotificationPref
wn = fn ^. failedWatch . watchName . unWatchName
wid = fn ^. failedWatch . watchId . unID
err = fn ^. failedLastError . to renderNotificationError
renderNotificationPref :: NotificationPreference -> Text
renderNotificationPref (HTTPNotification u) = [qc|HTTP Notification ({u})|]
renderNotificationPref (EmailNotification (EmailAddress a)) = [qc|Email Notification ({a})|]
renderNotificationError :: NotificationError -> Text
renderNotificationError (FailedByCode c) = [qc|Failed with status code {c}|]
renderNotificationError (FailedByException e) = [qc|Failed with exception "{e}"|]
renderWatchInfo :: EWatch -> Text
renderWatchInfo w = [qc|{renderedWatch}
Notifications:
{renderedNotifications}|]
where renderedWatch = renderWatch w
notifications = w ^. watchNotifications
renderedNotifications
| null notifications = bullet "none"
| otherwise = unlines' . map (bullet . renderNotification) $ notifications
renderNotification :: NotificationPreference -> Text
renderNotification (EmailNotification (EmailAddress a)) = [qc|Email: {a}|]
renderNotification (HTTPNotification u) = [qc|HTTP: {u}|]
renderState :: WatchState -> Text
renderState (Active t) = [qc|Active {t}|]
renderState x = show x
bullet :: Text -> Text
bullet x = [qc| - {x}|]
getList :: ClientCtxT IO (VigilanceResponse [EWatch])
getList = makeRequest GET "/watches" emptyBody
getInfo :: WatchName -> ClientCtxT IO (VigilanceResponse EWatch)
getInfo n = makeRequest GET (watchRoute n) emptyBody
pause :: WatchName -> ClientCtxT IO (VigilanceResponse ())
pause n = makeRequest_ POST (watchRoute n <> "/pause") emptyBody
unPause :: WatchName -> ClientCtxT IO (VigilanceResponse ())
unPause n = makeRequest_ POST (watchRoute n <> "/unpause") emptyBody
checkIn :: WatchName -> ClientCtxT IO (VigilanceResponse ())
checkIn n = makeRequest_ POST (watchRoute n <> "/checkin") emptyBody
test :: WatchName -> ClientCtxT IO (VigilanceResponse [FailedNotification])
test n = makeRequest POST (watchRoute n <> "/test") emptyBody
watchRoute :: WatchName -> ByteString
watchRoute (WatchName n) = "/watches/" <> encodeUtf8 n
makeRequest_ :: Method
-> ByteString
-> (S.OutputStream Builder -> IO b)
-> ClientCtxT IO (VigilanceResponse ())
makeRequest_ = makeRequest' unitResponseHandler
makeRequest :: FromJSON a
=> Method
-> ByteString
-> (S.OutputStream Builder -> IO b)
-> ClientCtxT IO (VigilanceResponse a)
makeRequest = makeRequest' jsonResponseHandler
makeRequest':: (Response -> S.InputStream ByteString -> IO (VigilanceResponse a))
-> Method
-> ByteString
-> (S.OutputStream Builder -> IO b)
-> ClientCtxT IO (VigilanceResponse a)
makeRequest' handler m p body = do
host <- asks serverHost
port <- asks serverPort
lift $ withConnection (openConnection host port) $ \c -> do
req <- buildRequest $ do
http m p
setAccept "application/json"
setUserAgent defaultUserAgent
void $ sendRequest c req body
receiveResponse c handler
setUserAgent :: ByteString -> RequestBuilder ()
setUserAgent = setHeader "User-Agent"
defaultUserAgent :: ByteString
defaultUserAgent = "vigilance client"
unitResponseHandler :: Response
-> S.InputStream ByteString
-> IO (VigilanceResponse ())
unitResponseHandler = responseHandler (const $ return $ Right ())
jsonResponseHandler :: FromJSON a
=> Response
-> S.InputStream ByteString
-> IO (VigilanceResponse a)
jsonResponseHandler = responseHandler handleJSONBody
responseHandler :: (S.InputStream ByteString -> IO (VigilanceResponse a))
-> Response
-> S.InputStream ByteString
-> IO (VigilanceResponse a)
responseHandler successHandler resp stream
| responseOk = successHandler stream
| notFound = return . Left $ NotFound
| otherwise = return . Left $ StatusError statusCode
where statusCode = getStatusCode resp
responseOk = inRange (200, 299) statusCode
notFound = statusCode == 404
handleJSONBody :: FromJSON a => S.InputStream ByteString -> IO (VigilanceResponse a)
handleJSONBody stream = coerceParsed <$> parseJSONBody stream
data VError = NotFound |
ParseError Text |
StatusError StatusCode deriving (Show, Eq)
parseJSONBody :: FromJSON a => S.InputStream ByteString -> IO (Result a)
parseJSONBody = parseFromStream parser
where parser = fmap fromJSON json
type VigilanceResponse a = Either VError a
coerceParsed :: Result a -> VigilanceResponse a
coerceParsed (Success a) = Right a
coerceParsed (Error e) = Left $ ParseError $ pack e
unlines' :: [Text] -> Text
unlines' = intercalate "\n"