Monad.hs 3.67 KB
{-|
Module      : Gargantext.Utils.Jobs.Monad
Description : Job monad
Copyright   : (c) CNRS, 2024
License     : AGPL + CECILL v3
Maintainer  : team@gargantext.org
Stability   : experimental
Portability : POSIX

-}


{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}

module Gargantext.Utils.Jobs.Monad (
  -- * Types and classes
    JobError(..)

  -- * Reporting errors to users in a friendly way
  , ToHumanFriendlyError(..)

  -- * Tracking jobs status
  , MonadJobStatus(..)

  -- * Functions
  , markFailedNoErr
  , markFailureNoErr
  ) where

import Control.Exception.Safe
import Data.Kind (Type)
import Data.Proxy
import Data.Text qualified as T
import Data.Void (Void)
import Gargantext.Utils.Jobs.Error
import Prelude


data JobError
  =
  -- | We expected to find a job tagged internall as \"job\", but we found the input @T.Text@ instead.
    InvalidIDType T.Text
  -- | The given ID expired.
  | IDExpired Int
  | InvalidMacID T.Text
  | UnknownJob Int
  | JobException SomeException
  deriving Show

-- | Polymorphic logger over any monad @m@.
type LoggerM m w = w -> m ()

-- | A @'Logger' w@ is a function that can do something with "messages" of type
--   @w@ in IO.
type Logger w = LoggerM IO w


--
-- Tracking jobs status
--

-- | A monad to query for the status of a particular job /and/ submit updates for in-progress jobs.
class MonadJobStatus m where

  -- | This is type family for the concrete 'JobHandle' that is associated to
  -- a job when it starts and it can be used to query for its completion status. Different environment
  -- can decide how this will look like.
  type JobHandle      m :: Type

  type JobOutputType  m :: Type
  type JobEventType   m :: Type

  -- | A job handle that doesn't do anything. Sometimes useful in all those circumstances
  -- where we need to test a function taking a 'JobHandle' as input but we are not interested
  -- in the progress tracking.
  noJobHandle :: Proxy m -> JobHandle m

  -- | Retrevies the latest 'JobEventType' from the underlying monad. It can be
  -- used to query the latest status for a particular job, given its 'JobHandle' as input.
  getLatestJobStatus :: JobHandle m -> m (JobEventType m)

  -- | Adds an extra \"tracer\" that logs events to the passed action. Produces
  -- a new 'JobHandle'.
  withTracer :: Logger (JobEventType m) -> JobHandle m -> (JobHandle m -> m a) -> m a

  -- Creating events

  -- | Start tracking a new 'JobEventType' with 'n' remaining steps.
  markStarted :: Int -> JobHandle m -> m ()

  -- | Mark 'n' steps of the job as succeeded, while simultaneously substracting this number
  -- from the remaining steps.
  markProgress :: Int -> JobHandle m -> m ()

  -- | Mark 'n' step of the job as failed, while simultaneously substracting this number
  -- from the remaining steps. Attach an optional error message to the failure.
  markFailure :: forall e. ToHumanFriendlyError e => Int -> Maybe e -> JobHandle m -> m ()

  -- | Finish tracking a job by marking all the remaining steps as succeeded.
  markComplete :: JobHandle m -> m ()

  -- | Finish tracking a job by marking all the remaining steps as failed. Attach an optional
  -- message to the failure.
  markFailed :: forall e. ToHumanFriendlyError e => Maybe e -> JobHandle m -> m ()

  -- | Add 'n' more steps to the running computation, they will be marked as remaining.
  addMoreSteps :: MonadJobStatus m => Int -> JobHandle m -> m ()

-- | Helper on top of 'markFailed' for when we don't have a diagnostic to log.
markFailedNoErr :: MonadJobStatus m => JobHandle m -> m ()
markFailedNoErr = markFailed (Nothing :: Maybe Void)

markFailureNoErr :: MonadJobStatus m => Int -> JobHandle m -> m ()
markFailureNoErr steps = markFailure steps (Nothing :: Maybe Void)