packages feed

rio-app-0.1.0.0: src/RIO/App.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE MultiParamTypeClasses #-}

module RIO.App
  ( App(..)
  , SetupApp
  , appMain
  , nestApp
  ) where

import RIO

import Control.Monad.Trans.Resource (MonadResource(..))
import qualified Control.Monad.Trans.Resource as ResourceT
import RIO.Process (HasProcessContext(..), ProcessContext, mkDefaultProcessContext)

data App env st = App
  { appLogFunc        :: LogFunc
  , appProcessContext :: ProcessContext
  , appResources      :: ResourceT.InternalState
  , appEnv            :: env
  , appState          :: SomeRef st
  }

instance HasLogFunc (App env st) where
  logFuncL =
    lens
      appLogFunc
      \x y -> x{appLogFunc = y}

instance HasProcessContext (App env st) where
  processContextL =
    lens
      appProcessContext
      \x y -> x{appProcessContext = y}

instance MonadResource (RIO (App env st)) where
  liftResourceT action =
    asks appResources >>=
      liftIO . ResourceT.runInternalState action

instance HasStateRef st (App env st) where
  stateRefL =
    lens
      appState
      \x y -> x{appState = y}

{- | Bootstrap environment.

Has logging and hold permanent resources, but no env and state of its own.
Used to setup initial environment and state, that can be derived from allocated resources.
-}
type SetupApp = App () ()

appMain
  :: IO options
  -> (options -> Bool)
  -> (options -> RIO SetupApp (env, st))
  -> RIO (App env st) ()
  -> IO ()
appMain parseOptions verbose setup run = do
  options <- parseOptions

  logFunc <- logOptionsHandle stderr (verbose options)
  appProcessContext <- mkDefaultProcessContext
  stateRef <- newSomeRef ()

  withLogFunc logFunc \appLogFunc ->
    bracket ResourceT.createInternalState ResourceT.closeInternalState \appResources -> do
      let
        setupApp = App
          { appEnv   = ()
          , appState = stateRef
          , ..
          }
      runRIO setupApp $
        nestApp (setup options) run

nestApp
  :: (MonadUnliftIO m, MonadReader (App env st) m)
  => RIO (App env st) (innerEnv, innerSt)
  -> RIO (App innerEnv innerSt) a
  -> m a
nestApp setupNext action =
  bracket ResourceT.createInternalState ResourceT.closeInternalState \nestedResources -> do
    oldApp <- ask
    (innerEnv, innerState) <- runRIO
      oldApp{appResources=nestedResources}
      setupNext
    innerStateRef <- newSomeRef innerState
    let
      nextApp = oldApp
        { appResources = nestedResources
        , appEnv       = innerEnv
        , appState     = innerStateRef
        }
    runRIO nextApp action