packages feed

nova-nix-0.9.0.0: src/Nix/Eval/SourceError.hs

-- | The messages upstream gives a failed read of a source path.
--
-- Nix 2.24.9 reads source paths through @PosixSourceAccessor@, which
-- names the access that failed and the path, then appends @strerror@ as
-- every @SysError@ does.  GHC raises the same failures as 'IOException's
-- whose text names the Haskell function instead, so each access the
-- evaluator makes is tagged and its failure reworded here.
module Nix.Eval.SourceError
  ( SourceAccess (..),
    sourceErrorMessage,
  )
where

import Data.Maybe (isNothing)
import Data.Text (Text)
import qualified Data.Text as T
import Foreign.C.Error (Errno (..), eISDIR, eNOTDIR)
import GHC.IO.Exception (IOErrorType (InappropriateType), IOException (..))
import System.IO.Error (isDoesNotExistError)

-- | The access to a source path that failed.
data SourceAccess
  = -- | An lstat.  An absent path (@ENOENT@ or @ENOTDIR@, which
    -- @maybeLstat@ in file-system.cc treats alike) is
    -- @SourceAccessor::lstat@'s @path '%s' does not exist@, anything else
    -- @maybeLstat@'s @getting status of '%s'@.
    StatPath
  | -- | Opening a file and reading it (@PosixSourceAccessor::readFile@):
    -- @opening file '%s'@, or @reading from file '%s'@ for a directory,
    -- which upstream opens and then fails to read.
    ReadFile
  | -- | Listing a directory (@PosixSourceAccessor::readDirectory@):
    -- @reading directory %s@, the path unquoted.
    ReadDirectory
  deriving (Eq, Show)

-- | Upstream's message for an access to a path that raised an exception.
sourceErrorMessage :: SourceAccess -> Text -> IOException -> Text
sourceErrorMessage access path err = case access of
  StatPath
    | isDoesNotExistError err || errnoIs eNOTDIR -> "path '" <> path <> "' does not exist"
    | otherwise -> "getting status of '" <> path <> "': " <> reason
  ReadFile
    | isDirectory -> "reading from file '" <> path <> "': " <> isDirectoryReason
    | otherwise -> "opening file '" <> path <> "': " <> reason
  ReadDirectory -> "reading directory " <> path <> ": " <> reason
  where
    errnoIs code = fmap Errno (ioe_errno err) == Just code
    -- An errno-derived IOException carries strerror as its description.
    reason = T.pack (ioe_description err)
    -- GHC's openFile refuses a directory itself, with no errno and its own
    -- lower-case wording; upstream's open succeeds and the read fails
    -- with EISDIR.
    isDirectory =
      errnoIs eISDIR || (ioe_type err == InappropriateType && isNothing (ioe_errno err) && ioe_description err == ghcIsDirectory)
    ghcIsDirectory = "is a directory"
    isDirectoryReason = "Is a directory"