packages feed

managed-functions-1.1.0.0: src/Managed/Agent.hs

{-# 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)