packages feed

hix-0.7.0: lib/Hix/Managed/Flow.hs

module Hix.Managed.Flow (
  Flow,
  liftM,
  runStage,
  execStage,
  evalStage,
  runStage_,
  execStatelessStage,
  evalStageState,
  runFlow,
  execFlow,
  evalFlow,
  stageError,
) where

import Control.Lens ((%=))
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State.Strict (StateT, get, gets, runStateT)
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Text as Text
import Exon (exon)

import Hix.Data.Error (Error (Fatal))
import Hix.Data.Monad (M)
import qualified Hix.Log as Log
import qualified Hix.Managed.Data.EnvContext
import Hix.Managed.Data.EnvContext (EnvContext (EnvContext))
import qualified Hix.Managed.Data.EnvRequest
import Hix.Managed.Data.EnvRequest (EnvRequest (EnvRequest))
import qualified Hix.Managed.Data.EnvResult
import Hix.Managed.Data.EnvResult (EnvResult (EnvResult))
import Hix.Managed.Data.EnvState (EnvState)
import Hix.Managed.Data.Initial (Initial (Initial))
import Hix.Managed.Data.StageContext (StageContext)
import Hix.Managed.Data.StageResult (StageFailure (FailedPrecondition), StageResult (..), StageSummary (StageFailure))
import Hix.Managed.Data.StageState (BuildStatus (Failure, Success))
import Hix.Managed.StageContext (stageContext)
import Hix.Managed.UpdateState (envStateWithMutations)
import Hix.Monad (throwM)

data FlowState =
  FlowState {
    env :: EnvRequest,
    current :: EnvState,
    summaries :: [StageSummary]
  }
  deriving stock (Generic)

type FlowM = StateT FlowState M

newtype Flow x =
  Flow (FlowM x)
  deriving newtype (Functor, Applicative, Monad)

liftM :: M a -> Flow a
liftM = Flow . lift

addResult ::
  StageResult ->
  Flow BuildStatus
addResult StageResult {state, summary} =
  Flow do
    #summaries %= (summary :)
    #current %= maybe id envStateWithMutations state
    pure status
  where
    status = case summary of
      StageFailure _ -> Failure
      _ -> Success

newContext :: Flow StageContext
newContext = do
  FlowState {env, current} <- Flow get
  pure (stageContext env current)

runStage ::
  ∀ o .
  Text ->
  (StageContext -> M (StageResult, o)) ->
  Flow (BuildStatus, o)
runStage description stage = do
  unless (Text.null description) do
    EnvRequest {context = EnvContext {env}} <- Flow (gets (.env))
    liftM (Log.debug [exon|Executing stage '#{description}' for '##{env}'|])
  context <- newContext
  (result, o) <- liftM (stage context)
  status <- addResult result
  pure (status, o)

execStage ::
  Text ->
  (StageContext -> M StageResult) ->
  Flow BuildStatus
execStage description stage =
  fst <$> runStage description (fmap (, ()) . stage)

evalStage ::
  Text ->
  (StageContext -> M (StageResult, o)) ->
  Flow o
evalStage description stage =
  snd <$> runStage description stage

runStage_ ::
  Text ->
  (StageContext -> M StageResult) ->
  Flow ()
runStage_ description stage =
  void (execStage description stage)

execStatelessStage ::
  Text ->
  (StageContext -> M StageSummary) ->
  Flow BuildStatus
execStatelessStage description f = do
  execStage description \ context -> do
    summary <- f context
    pure StageResult {state = Nothing, summary}

stageError :: Text -> Flow ()
stageError msg =
  void (execStatelessStage "" (const (pure (StageFailure (FailedPrecondition [msg])))))

evalStageState :: (EnvState -> StageContext -> a) -> Flow a
evalStageState f = do
  context <- newContext
  state <- Flow (gets (.current))
  pure (f state context)

runFlow :: EnvRequest -> Flow o -> M (o, EnvResult)
runFlow request@EnvRequest {context, state = (Initial initState)} (Flow m) =
  runStateT m FlowState {env = request, current = initState, summaries = []} >>= traverse \case
    FlowState {summaries = h : t, current = finalState} ->
      pure EnvResult {env = context.env, state = Just finalState, summaries = NonEmpty.reverse (h :| t)}
    FlowState {summaries = []} ->
      throwM (Fatal "Executed flow with no stages")

execFlow :: EnvRequest -> Flow () -> M EnvResult
execFlow env m =
  snd <$> runFlow env m

evalFlow :: EnvRequest -> Flow o -> M o
evalFlow env m =
  fst <$> runFlow env m