packages feed

hix-0.9.0: lib/Hix/Error.hs

module Hix.Error (
  module Hix.Data.Error,
  module Hix.Error,
) where

import Control.Monad.Trans.Except (ExceptT, throwE, withExceptT)
import Exon (exon)
import Path (Path, toFilePath)
import System.IO.Error (tryIOError)

import qualified Hix.Console as Console
import Hix.Console (errorMessage, withErrorChevrons)
import Hix.Data.AppContext (AppContext (..))
import Hix.Data.Error (Error (..), ErrorContext (..), ErrorMessage (..))
import Hix.Data.LogLevel (LogLevel)

pathText :: Path b t -> Text
pathText =
  toText . toFilePath

prefixedError ::
  MonadIO m =>
  Text ->
  Text ->
  m ()
prefixedError desc msg =
  Console.err (errorMessage [exon|#{desc}: #{msg}|])

printPreprocError ::
  MonadIO m =>
  Text ->
  m ()
printPreprocError =
  prefixedError "Preprocessor generator failed"

printEnvError ::
  MonadIO m =>
  Text ->
  m ()
printEnvError =
  prefixedError "Invalid env config"

printGhciError ::
  MonadIO m =>
  Text ->
  m ()
printGhciError =
  prefixedError "Invalid ghci config"

printNewError ::
  MonadIO m =>
  Text ->
  m ()
printNewError =
  prefixedError "Can't create new project"

printBootstrapError ::
  MonadIO m =>
  Text ->
  m ()
printBootstrapError =
  prefixedError "Can't bootstrap project"

printFatalError ::
  MonadIO m =>
  Text ->
  m ()
printFatalError =
  prefixedError "Fatal error"

printFatalWhenError ::
  MonadIO m =>
  Text ->
  Text ->
  m ()
printFatalWhenError context =
  prefixedError [exon|Fatal error when #{context}|]

sourceError :: Text -> Path b t -> Text
sourceError reason source =
  [exon|#{reason} the source file '#{pathText source}'|]

throwMessage :: ErrorMessage -> ExceptT Error IO a
throwMessage message =
  throwE Error {message, level = Nothing, context = []}

catchIO ::
  (Text -> ExceptT e IO a) ->
  IO a ->
  ExceptT e IO a
catchIO handleError ma =
  liftIO (tryIOError ma) >>= \case
    Right a -> pure a
    Left err -> handleError (show err)

tryIOWith ::
  (Text -> e) ->
  IO a ->
  ExceptT e IO a
tryIOWith mkErr ma =
  liftIO (tryIOError ma) >>= \case
    Right a -> pure a
    Left err -> throwE (mkErr (show err))

tryIOContext ::
  ErrorContext ->
  IO a ->
  ExceptT Error IO a
tryIOContext context =
  tryIOWith \ message -> Error {message = Fatal message, level = Nothing, ..}

tryIO :: IO a -> ExceptT Error IO a
tryIO = tryIOContext []

errorLevel ::
  LogLevel ->
  ExceptT Error IO a ->
  ExceptT Error IO a
errorLevel new =
  withExceptT \ Error {..} -> Error {level = Just new, ..}

formatError :: ErrorMessage -> Text
formatError = \case
  Fatal msg -> [exon|Fatal: #{msg}|]
  FatalExternal msg -> [exon|Fatal: #{msg}|]
  Client msg -> msg

printError ::
  MonadIO m =>
  LogLevel ->
  Error ->
  m ()
printError logLevel Error {..}
  | Just messageLevel <- level
  , messageLevel < logLevel
  = unit
  | ErrorContext ctxLines <- context
  = do
    for_ (reverse ctxLines) \ ctx ->
      when (ctx.level >= logLevel) do
        Console.err (withErrorChevrons [exon|While #{ctx.description}|])
    Console.err (errorMessage (formatError message))