{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TupleSections #-} module Boots.Health( -- ** Health Check Health(..) , HealthStatus(..) , HasHealth(..) , emptyHealth , registerHealth ) where import Boots.Prelude import Control.Exception (SomeException, catch) import Control.Monad.Factory import qualified Data.HashMap.Strict as HM import Data.Text (Text, pack) import GHC.Generics -- | Health status. data HealthStatus = UP | DOWN deriving (Eq, Show, Generic) -- | Health detail. data Health = Health { status :: !HealthStatus , errMsg :: !(Maybe Text) , details :: !(HM.HashMap Text Health) } deriving (Eq, Show, Generic) -- | Default health detail. {-# INLINE emptyHealth #-} emptyHealth :: IO Health emptyHealth = return (Health UP Nothing HM.empty) -- | Environment values with health checker `IO Health`. class HasHealth env where askHealth :: Lens' env (IO Health) instance HasHealth (IO Health) where askHealth = id type CheckHealth = (Text, IO HealthStatus) insertHealth :: CheckHealth -> IO Health -> IO Health insertHealth (na, ios) ior = do (err,s) <- ((Nothing,) <$> ios) `catch` (\(e :: SomeException) -> return (Just (pack $ show e), DOWN)) Health{..} <- ior return (Health (if s == DOWN then s else status) Nothing $ HM.insert na (Health s err HM.empty) details) -- combineHealth :: [CheckHealth] -> IO Health -> IO Health -- combineHealth = flip (foldr insertHealth) -- | Register a health checker. registerHealth :: (MonadMask n, HasHealth env) => Text -- ^ Component name. -> IO HealthStatus -- ^ Check action. -> Factory n env () registerHealth name status = modifyEnv $ over askHealth $ insertHealth (name, status)