{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleContexts #-} module Managed.Agent ( fromList , toList , (!) , (!?) , invoke , ids , invokeUnsafe , describe , describeEither , describeHuman ) where import qualified Data.Map as M import Data.Managed import Control.DeepSeq (NFData, force) import Control.Exception (catch, evaluate, toException) import Control.Monad.Catch (try) import Managed.Exception import Managed.ProbeDescription import System.IO.Error (catchIOError) -- | 'Data.Map.fromList' specialized to 'Agent' fromList :: [(ProbeID, Probe e)] -> Agent e fromList = M.fromList -- | 'Data.Map.toList' specialized to 'Agent' toList :: Agent e -> [(ProbeID, Probe e)] toList = M.toList infixl 9 !, !? -- | 'Data.Map.!' specialized to 'Agent' (!) :: Agent e -> ProbeID -> Probe e (!) = (M.!) -- | 'Data.Map.!?' specialized to 'Agent' (!?) :: Agent e -> ProbeID -> Maybe (Probe e) (!?) = (M.!?) -- | Invokes (calls) a 'Probe'. -- -- This function never throws an exception, -- instead, an 'Either' is used. -- Any errors and exceptions caused by incorrect 'ProbeID', -- incorrect input parameters, or by the probe call itself -- are caught and passed as an 'AgentException'. -- -- The result is fully evaluated using 'force' -- before it's returned -- (to keep all exceptions inside the 'Either'). invoke :: (NFData (Out e)) => Agent e -- ^ Agent that contains the probe to be called -> ProbeID -- ^ ID of the probe to be called -> [In e] -- ^ Input parameters -> IO (Either AgentException (Out e)) invoke a p i = try $ invokeUnsafe a p i -- | An unsafe variant of 'invoke'. -- -- This function rethrows all exceptions and errors -- caused by probe lookup or input parameters. -- Exceptions caused by probe invocation -- are rethrown as 'ProbeRuntimeException'. invokeUnsafe :: (NFData (Out e)) => Agent e -> ProbeID -> [In e] -> IO (Out e) invokeUnsafe agent pid input = findOrThrow agent pid >>= callStrict input -- | List all Probe IDs ids :: Agent e -> [ProbeID] ids = Prelude.map fst . toList -- | Create a full description of a 'Probe' describe :: Agent e -> ProbeID -> Maybe ProbeDescription describe agent pid = mkDescription pid <$> agent !? pid -- | A variant of 'describe' that returns 'Either' instead of 'Maybe' describeEither :: Agent e -> ProbeID -> Either AgentException ProbeDescription describeEither agent pid = case describe agent pid of Nothing -> Left $ badProbeID pid Just res -> Right res -- | Create a human-readable description of a probe describeHuman :: Agent e -> ProbeID -> Maybe String describeHuman agent pid = human <$> describe agent pid -- Utility functions findOrThrow :: Agent e -> ProbeID -> IO (Probe e) findOrThrow agent pid = case agent !? pid of Nothing -> throwM $ badProbeID pid Just probe -> return probe callStrict :: (NFData (Out e)) => [In e] -> Probe e -> IO (Out e) callStrict input p = callOrThrow input p >>= evalOrThrow callOrThrow :: [In e] -> Probe e -> IO (Out e) callOrThrow input p = call p input `catchIOError` (throwM . probeRuntimeException . toException) evalOrThrow :: (NFData a) => a -> IO a evalOrThrow x = (evaluate . force) x `catch` (throwM . probeRuntimeException)