packages feed

glue-common-0.7.0: src/Glue/Types.hs

{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Module containing the root types and some support functionality.
module Glue.Types
  ( BasicService
  , MultiGetService
  , MultiGetRequest
  , MultiGetResponse
  , ResultVar
  , MToIO
  , FailOrSuccess
  , multiGetToBasic
  , basicToMultiGet
  , getResult
  , makeCall
  ) where

import           Data.Hashable       (Hashable)
import qualified Data.HashMap.Strict as M
import qualified Data.HashSet        as S
import           UnliftIO            (MonadIO, MonadUnliftIO, SomeException)
import           UnliftIO.Exception  (throwIO, tryAny)
import           UnliftIO.MVar       (MVar, readMVar)

-- | Type alias for the most basic form of a service supported.
type BasicService m a b = a -> m b

-- | Type alias for the request portion of a 'MultiGetService'.
type MultiGetRequest a = S.HashSet a

-- | Type alias for the response portion of a 'MultiGetService'.
type MultiGetResponse a b = M.HashMap a b

-- | Type alias for a service that looks up multiple values and returns multiple results.
type MultiGetService m a b = BasicService m (MultiGetRequest a) (MultiGetResponse a b)

-- | Type alias for the common container of a result used in asynchronous calls.
type ResultVar a = MVar (Either SomeException a)

-- | Run the @m@ into an 'IO' instance for a response.
type MToIO m = forall a. m a -> IO a

-- | Type alias for either a failure or a successful response.
type FailOrSuccess a b = Either SomeException (MultiGetResponse a b)

-- | Convert a 'MultiGetService' into a 'BasicService' that looks up a single key,
-- returning a 'Maybe' which determines if the value was present.
multiGetToBasic :: (Hashable a, Functor m) => MultiGetService m a b -> BasicService m a (Maybe b)
multiGetToBasic service r = M.lookup r <$> service (S.singleton r)

-- | Convert a 'BasicService' into a 'MultiGetService'.
basicToMultiGet :: (Hashable a, Applicative m) => BasicService m a b -> MultiGetService m a b
basicToMultiGet service =
  let callService resultMap request = flip (M.insert request) <$> resultMap <*> service request
  in  S.foldl' callService (pure M.empty)

-- | Obtain a result from a 'ResultVar' in the 'MonadIO' of your choice.
getResult :: MonadIO m => ResultVar a -> m a
getResult var = do
  result <- readMVar var
  either throwIO pure result

-- | Makes a multi-get call and handles synchronous errors by bundling them up inside an 'Either'.
makeCall :: MonadUnliftIO m => MultiGetService m a b -> S.HashSet a -> m (FailOrSuccess a b)
makeCall service requests = tryAny (service requests)