packages feed

textocat-api-0.1.0.0: src/Network/Textocat/Monadic.hs

{- |
Module      : Network.Textocat.Monadic
Description : Monadic binding for Textocat API
Copyright   : (c) Mansur Ziatdinov, 2014--2015
License     : Apache-2.0
Maintainer  : mz+textocat-api@fosslabs.ru
Stability   : experimental
Portability : POSIX

There are two kind of functions in this module. Functions that have apostrophe
in their names send data to server every time you call them. Functions without
them call server only if necessary (e.g. you requested it)

This monadic interface is experimental. If you know how to improve it, please
contact author
-}
{-# LANGUAGE DeriveFunctor #-}

module Network.Textocat.Monadic
       ( -- * Monad wrappers
         -- ** General monad transformer
         TextocatM
       , runTextocatM
         -- ** Monad over IO
       , Textocat
       , runTextocat
         -- * API operations with explicit calls
       , queue'
       , request'
       , retrieve'
       , search'
       , status
         -- ** Helpers
       , wait'
         -- * API operations with implicit calls
       , queue
       , queue1
       , force
       , request
       , retrieve
       ) where

import Data.Textocat
import Network.Textocat.Simple

import Control.Applicative
import Control.Arrow
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.RWS
import Data.ByteString (ByteString)
import Data.Either
import Data.Text (Text)

initialSt :: [Document]
initialSt = []

-- | TextocatM is a monad transformer to use Textocat API
-- over some monad @m@ and return result @a@
newtype TextocatM m a = TextocatM
    { unTextocatM :: RWST Config () [Document] m a
    } deriving (Functor)

instance (Functor m, Monad m) => Applicative (TextocatM m) where
  pure x = TextocatM $ pure x
  TextocatM f <*> TextocatM x = TextocatM $ f <*> x

instance (Functor m, MonadIO m) => Monad (TextocatM m) where
  return = pure
  TextocatM ma >>= f = TextocatM $ ma >>= unTextocatM . f

instance MonadTrans TextocatM where
  lift action = TextocatM $ lift action

instance (Functor m, MonadIO m) => MonadIO (TextocatM m) where
  liftIO = TextocatM . liftIO

type Textocat = TextocatM IO

-- | Executes TextocatM actions with given API key
runTextocatM :: (Functor m, MonadIO m)
             => ByteString -- ^ API key
             -> TextocatM m a -- ^ TextocatM action
             -> m a
runTextocatM apiKey ma = fmap fst $ evalRWST (unTextocatM ma) (mkConfig apiKey) initialSt

-- | Executes Textocat actions with given API key
runTextocat :: ByteString -- ^ API key
            -> Textocat a -- ^ Textocat action
            -> IO a
runTextocat = runTextocatM

-- | Run function that depends on 'Config' in TextocatM monad,
-- reading 'Config' from Reader
withConfig :: (Functor m, MonadIO m)
           => (Config -> b -> IO a) -- ^ Function that depends on Config and something else
           -> b -- ^ Something else
           -> TextocatM m a
withConfig f b = TextocatM $ ask >>= liftIO . flip f b

-- | Queues given documents. No requests go to server until 'force',
-- 'request' of 'retrieve' is called
queue :: (Functor m, MonadIO m)
      => [Document] -- ^ Documents to queue
      -> TextocatM m ()
queue docs = TextocatM $ modify (docs++)

-- | Queues single document. No requests go to server until 'force',
-- 'request' of 'retrieve' is called
queue1 :: (Functor m, MonadIO m)
       => Document -- ^ Document to queue
       -> TextocatM m ()
queue1 doc = TextocatM $ modify (doc:)

-- | Queues documents. Actually, makes call to 'entityQueue' with
-- collections divided by 50 documents at most
queue' :: (Functor m, MonadIO m) => [Document] -> TextocatM m [Either ErrorMsg BatchStatus]
queue' docs | null docs = return []
            | length docs <= 50 = fmap pure $ withConfig entityQueue docs
            | otherwise = do
  let (d,rest) = splitAt 50 docs
  b <- withConfig entityQueue d
  fmap (b:) $ queue' rest

-- | Actually sends documents that were added by 'queue' and 'queue1' calls
-- to the API server.
-- If there are more than 50 documents, they will be sent in several calls,
-- each call will have 50 document at most (API limitation)
force :: (Functor m, MonadIO m) => TextocatM m [Either ErrorMsg BatchStatus]
force = TextocatM $ get >>= unTextocatM . queue'

-- | Requests batch status
request' :: (Functor m, MonadIO m)
        => BatchID -- ^ Batch
        -> TextocatM m (Either ErrorMsg BatchStatus)
request' = withConfig entityRequest

-- | Retrieves batches
retrieve' :: (Functor m, MonadIO m)
         => [BatchID] -- ^ Batches
         -> TextocatM m (Either ErrorMsg Batch)
retrieve' = withConfig entityRetrieve

-- | Search all collections
search' :: (Functor m, MonadIO m)
       => Text -- ^ Search query
       -> TextocatM m (Either ErrorMsg SearchResult)
search' = withConfig entitySearch

-- | Waits while batch processing is finished
wait' :: (Functor m, MonadIO m)
         => BatchID
         -> TextocatM m ()
wait' = withConfig waitForFinished

-- | Check service status
status :: (Functor m, MonadIO m) => TextocatM m ServiceStatus
status = TextocatM $ ask >>= liftIO . serviceStatus

-- | Request all documents that were added by 'queue' and 'queue1' calls.
-- Can create several batches, if there are more than 50 documents
request :: (Functor m, MonadIO m) => TextocatM m [Either ErrorMsg BatchStatus]
request = force >>= mapM makeRequest
  where makeRequest (Left error) = return $ Left error
        makeRequest (Right batchStatus) = request' $ getBatchID batchStatus

-- | Retrieve all documents that were added by 'queue' and 'queue1' calls.
-- Can create several batches, if there are more than 50 documents.
-- Blocks until processing of all batches is finished
retrieve :: (Functor m, MonadIO m) => TextocatM m [Either ErrorMsg Batch]
retrieve = request >>= mapM makeWait >>= uncurry (liftM2 (++)) . ret
  where
    ret = (return . map Left *** fmap pure . retrieve') . partitionEithers
    makeWait (Left error) = return $
        Left error
    makeWait (Right batchStatus) = let bId = getBatchID batchStatus
        in wait' bId >> return (Right bId)