{-# LANGUAGE GeneralizedNewtypeDeriving #-} {- | This module contains data structures to describe the "warming-up" of componnts in order to ensure that they are properly configured: - createWarmup creates a warmup from an action returning a 'Result' - warmupOf takes a component name and unit action then just checks that the action executes without exception -} module Data.Registry.Warmup where import qualified Control.Monad.Catch as Catch import Data.Semigroup ((<>)) import Protolude as P hiding ((<>)) import Data.Typeable -- | A list of actions to run at startup newtype Warmup = Warmup { _warmUp :: [IO Result] } deriving (Monoid, Semigroup) -- * Creation functions -- | Create a warmup action for a given component -- The type of the component is used as the description for -- the action to execute warmupOf :: Typeable a => a -> IO () -> Warmup warmupOf a action = createWarmup $ do res <- Catch.try action :: IO (Either SomeException ()) pure $ case res of Left e -> failed $ "KO: " <> show (typeOf a) <> " -> " <> show e Right _ -> ok $ "OK: " <> show (typeOf a) -- | Create a 'Warmup' from an 'IO' action returning a 'Result' createWarmup :: IO Result -> Warmup createWarmup t = Warmup [t] -- | The empty 'Warmup' noWarmup :: Warmup noWarmup = Warmup [pure Empty] -- | Create a 'Warmup' with no action but just the type of a component declareWarmup :: Typeable a => a -> Warmup declareWarmup a = warmupOf a (pure ()) -- | Result of a 'Warmup' data Result = Empty | Ok [Text] | Failed [Text] deriving (Eq, Show) -- | Return 'True' if a 'Warmup' was successful isSuccess :: Result -> Bool isSuccess Empty = True isSuccess (Ok _) = True isSuccess (Failed _) = False -- | Create a successful 'Result' ok :: Text -> Result ok t = Ok [t] -- | Create a failed 'Result' failed :: Text -> Result failed t = Failed [t] -- | Extract the list of all the messages from a 'Result' messages :: Result -> [Text] messages Empty = [] messages (Ok ms) = ms messages (Failed ms) = ms instance Monoid Result where mempty = Empty mappend = (<>) instance Semigroup Result where r <> Empty = r Empty <> r = r Failed ts <> r = Failed (ts ++ messages r) r <> Failed ts = Failed (messages r ++ ts) Ok ts1 <> Ok ts2 = Ok (ts1 ++ ts2) -- * Run functions -- | Simple sequential warmup strategy runWarmup :: Warmup -> IO Result runWarmup (Warmup as) = foldr' runBoth (pure Empty) as -- | 'runBoth' runs both tasks and cumulate the results -- exceptions are being transformed into Failed results runBoth :: IO Result -> IO Result -> IO Result runBoth io1 io2 = do res1 <- Catch.try io1 :: IO (Either SomeException Result) res2 <- Catch.try io2 :: IO (Either SomeException Result) pure $ case (res1, res2) of (Right r1, Right r2) -> r1 `mappend` r2 (Left r1, Right r2) -> failed (show r1) `mappend` r2 (Right r1, Left r2) -> r1 `mappend` failed (show r2) (Left r1, Left r2) -> Failed [show r1, show r2]