Skip to content

Commit

Permalink
Added FileDoesNotExistError constructor
Browse files Browse the repository at this point in the history
Improves the error situation when dealing with file paths. Before all IO
related errors where wrapped around the constructor FileIOError. Now the
existence of the file is checked and if it does not exist a
FileDoesNotExistError is thrown.

Added `fileIOExceptT` function that abstracts away this existence check,
so we can get read of all the FileIOError wraps.
  • Loading branch information
bolt12 committed Jun 30, 2023
1 parent 6673841 commit a0b4040
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 39 deletions.
18 changes: 18 additions & 0 deletions cardano-api/internal/Cardano/Api/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ module Cardano.Api.Error
, throwErrorAsException
, ErrorAsException(..)
, FileError(..)
, fileIOExceptT
) where

import Control.Exception (Exception (..), IOException, throwIO)
import System.IO (Handle)
import Control.Monad.Trans.Except (ExceptT)
import Control.Monad.Trans.Except.Extra (handleIOExceptT)
import Control.Monad.IO.Class (MonadIO)
import System.Directory (doesFileExist)
import Control.Monad.Except (throwError)


class Show e => Error e where
Expand Down Expand Up @@ -46,6 +52,7 @@ data FileError e = FileError FilePath e
FilePath
-- ^ Temporary path
Handle
| FileDoesNotExistError FilePath
| FileIOError FilePath IOException
deriving (Show, Eq, Functor)

Expand All @@ -54,6 +61,8 @@ instance Error e => Error (FileError e) where
"Error creating temporary file at: " ++ tempPath ++
"/n" ++ "Target path: " ++ targetPath ++
"/n" ++ "Handle: " ++ show h
displayError (FileDoesNotExistError path) =
"Error file not found at: " ++ path
displayError (FileIOError path ioe) =
path ++ ": " ++ displayException ioe
displayError (FileError path e) =
Expand All @@ -62,3 +71,12 @@ instance Error e => Error (FileError e) where
instance Error IOException where
displayError = show

fileIOExceptT :: MonadIO m
=> FilePath
-> (FilePath -> IO s)
-> ExceptT (FileError e) m s
fileIOExceptT fp readFile' = do
fileExists <- handleIOExceptT (FileIOError fp) $ doesFileExist fp
if fileExists then handleIOExceptT (FileIOError fp) $ readFile' fp
else throwError (FileDoesNotExistError fp)

21 changes: 10 additions & 11 deletions cardano-api/internal/Cardano/Api/IO.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,12 @@ module Cardano.Api.IO
, writeSecrets
) where

import Cardano.Api.Error (FileError (..))
import Cardano.Api.Error (FileError (..), fileIOExceptT)
import Cardano.Api.IO.Base
import Cardano.Api.IO.Compat

import Control.Monad.Except (runExceptT)
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.Trans.Except.Extra (handleIOExceptT)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Char8 as BSC
Expand All @@ -54,29 +53,29 @@ readByteStringFile :: ()
=> File content In
-> m (Either (FileError e) ByteString)
readByteStringFile fp = runExceptT $
handleIOExceptT (FileIOError (unFile fp)) $ BS.readFile (unFile fp)
fileIOExceptT (unFile fp) BS.readFile

readLazyByteStringFile :: ()
=> MonadIO m
=> File content In
-> m (Either (FileError e) LBS.ByteString)
readLazyByteStringFile fp = runExceptT $
handleIOExceptT (FileIOError (unFile fp)) $ LBS.readFile (unFile fp)
fileIOExceptT (unFile fp) LBS.readFile

readTextFile :: ()
=> MonadIO m
=> File content In
-> m (Either (FileError e) Text)
readTextFile fp = runExceptT $
handleIOExceptT (FileIOError (unFile fp)) $ Text.readFile (unFile fp)
fileIOExceptT (unFile fp) Text.readFile

writeByteStringFile :: ()
=> MonadIO m
=> File content Out
-> ByteString
-> m (Either (FileError e) ())
writeByteStringFile fp bs = runExceptT $
handleIOExceptT (FileIOError (unFile fp)) $ BS.writeFile (unFile fp) bs
fileIOExceptT (unFile fp) (`BS.writeFile` bs)

writeByteStringFileWithOwnerPermissions
:: FilePath
Expand All @@ -93,7 +92,7 @@ writeByteStringOutput :: ()
-> m (Either (FileError e) ())
writeByteStringOutput mOutput bs = runExceptT $
case mOutput of
Just fp -> handleIOExceptT (FileIOError (unFile fp)) $ BS.writeFile (unFile fp) bs
Just fp -> fileIOExceptT (unFile fp) (`BS.writeFile` bs)
Nothing -> liftIO $ BSC.putStr bs

writeLazyByteStringFile :: ()
Expand All @@ -102,7 +101,7 @@ writeLazyByteStringFile :: ()
-> LBS.ByteString
-> m (Either (FileError e) ())
writeLazyByteStringFile fp bs = runExceptT $
handleIOExceptT (FileIOError (unFile fp)) $ LBS.writeFile (unFile fp) bs
fileIOExceptT (unFile fp) (`LBS.writeFile` bs)

writeLazyByteStringFileWithOwnerPermissions
:: File content Out
Expand All @@ -119,7 +118,7 @@ writeLazyByteStringOutput :: ()
-> m (Either (FileError e) ())
writeLazyByteStringOutput mOutput bs = runExceptT $
case mOutput of
Just fp -> handleIOExceptT (FileIOError (unFile fp)) $ LBS.writeFile (unFile fp) bs
Just fp -> fileIOExceptT (unFile fp) (`LBS.writeFile` bs)
Nothing -> liftIO $ LBSC.putStr bs

writeTextFile :: ()
Expand All @@ -128,7 +127,7 @@ writeTextFile :: ()
-> Text
-> m (Either (FileError e) ())
writeTextFile fp t = runExceptT $
handleIOExceptT (FileIOError (unFile fp)) $ Text.writeFile (unFile fp) t
fileIOExceptT (unFile fp) (`Text.writeFile` t)

writeTextFileWithOwnerPermissions
:: File content Out
Expand All @@ -145,7 +144,7 @@ writeTextOutput :: ()
-> m (Either (FileError e) ())
writeTextOutput mOutput t = runExceptT $
case mOutput of
Just fp -> handleIOExceptT (FileIOError (unFile fp)) $ Text.writeFile (unFile fp) t
Just fp -> fileIOExceptT (unFile fp) (`Text.writeFile` t)
Nothing -> liftIO $ Text.putStr t

mapFile :: (FilePath -> FilePath) -> File content direction -> File content direction
Expand Down
6 changes: 3 additions & 3 deletions cardano-api/internal/Cardano/Api/IO/Compat/Posix.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ module Cardano.Api.IO.Compat.Posix

#ifdef UNIX

import Cardano.Api.Error (FileError (..))
import Cardano.Api.Error (FileError (..), fileIOExceptT)
import Cardano.Api.IO.Base

import Control.Exception (IOException, bracket, bracketOnError, try)
import Control.Monad (forM_, when)
import Control.Monad.Except (ExceptT, runExceptT)
import Control.Monad.IO.Class
import Control.Monad.Trans.Except.Extra (handleIOExceptT, left)
import Control.Monad.Trans.Except.Extra (left)
import qualified Data.ByteString as BS
import System.Directory ()
import System.FilePath ((</>))
Expand Down Expand Up @@ -62,7 +62,7 @@ handleFileForWritingWithOwnerPermissionImpl path f = do
bracket
(fdToHandle fd)
IO.hClose
(runExceptT . handleIOExceptT (FileIOError path) . f)
(runExceptT . fileIOExceptT path . const . f)

writeSecretsImpl :: FilePath -> [Char] -> [Char] -> (a -> BS.ByteString) -> [a] -> IO ()
writeSecretsImpl outDir prefix suffix secretOp xs =
Expand Down
14 changes: 3 additions & 11 deletions cardano-api/internal/Cardano/Api/Keys/Read.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ import Cardano.Api.SerialiseBech32
import Cardano.Api.SerialiseTextEnvelope
import Cardano.Api.Utils

import Control.Exception
import Data.Bifunctor
import Data.ByteString as BS
import Data.List.NonEmpty (NonEmpty)
import Control.Monad.Except (runExceptT)

-- | Read a cryptographic key from a file.
--
Expand All @@ -33,14 +32,11 @@ readKeyFile
-> FilePath
-> IO (Either (FileError InputDecodeError) a)
readKeyFile asType acceptedFormats path = do
eContent <- fmap Right (readFileBlocking path) `catches` [Handler handler]
eContent <- runExceptT $ fileIOExceptT path readFileBlocking
case eContent of
Left e -> return $ Left e
Right content ->
return . first (FileError path) $ deserialiseInput asType acceptedFormats content
where
handler :: IOException -> IO (Either (FileError InputDecodeError) BS.ByteString)
handler e = return . Left $ FileIOError path e

-- | Read a cryptographic key from a file.
--
Expand All @@ -65,12 +61,8 @@ readKeyFileAnyOf
-> File content In
-> IO (Either (FileError InputDecodeError) b)
readKeyFileAnyOf bech32Types textEnvTypes path = do
eContent <- fmap Right (readFileBlocking (unFile path)) `catches` [Handler handler]
eContent <- runExceptT $ fileIOExceptT (unFile path) readFileBlocking
case eContent of
Left e -> return $ Left e
Right content ->
return . first (FileError (unFile path)) $ deserialiseInputAnyOf bech32Types textEnvTypes content
where
handler :: IOException -> IO (Either (FileError InputDecodeError) BS.ByteString)
handler e = return . Left $ FileIOError (unFile path) e

7 changes: 3 additions & 4 deletions cardano-api/internal/Cardano/Api/SerialiseJSON.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import Cardano.Api.Error
import Cardano.Api.HasTypeProxy

import Control.Monad.Trans.Except (runExceptT)
import Control.Monad.Trans.Except.Extra (firstExceptT, handleIOExceptT, hoistEither)
import Control.Monad.Trans.Except.Extra (firstExceptT, hoistEither)
import Data.Aeson (FromJSON (..), FromJSONKey, ToJSON (..), ToJSONKey)
import qualified Data.Aeson as Aeson
import Data.Aeson.Encode.Pretty (encodePretty)
Expand Down Expand Up @@ -56,7 +56,7 @@ readFileJSON :: FromJSON a
-> IO (Either (FileError JsonDecodeError) a)
readFileJSON ttoken path =
runExceptT $ do
content <- handleIOExceptT (FileIOError path) $ BS.readFile path
content <- fileIOExceptT path BS.readFile
firstExceptT (FileError path) $ hoistEither $
deserialiseFromJSON ttoken content

Expand All @@ -66,6 +66,5 @@ writeFileJSON :: ToJSON a
-> IO (Either (FileError ()) ())
writeFileJSON path x =
runExceptT $
handleIOExceptT (FileIOError path) $
BS.writeFile path (serialiseToJSON x)
fileIOExceptT path (`BS.writeFile` serialiseToJSON x)

9 changes: 4 additions & 5 deletions cardano-api/internal/Cardano/Api/SerialiseLedgerCddl.hs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import Cardano.Api.Utils
import Cardano.Ledger.Binary (DecoderError)
import qualified Cardano.Ledger.Binary as CBOR

import Control.Monad.Trans.Except.Extra (firstExceptT, handleIOExceptT, hoistEither,
import Control.Monad.Trans.Except.Extra (firstExceptT, hoistEither,
newExceptT, runExceptT)
import Data.Aeson
import qualified Data.Aeson as Aeson
Expand Down Expand Up @@ -222,7 +222,7 @@ writeTxFileTextEnvelopeCddl
-> IO (Either (FileError ()) ())
writeTxFileTextEnvelopeCddl path tx =
runExceptT $ do
handleIOExceptT (FileIOError (unFile path)) $ LBS.writeFile (unFile path) txJson
fileIOExceptT (unFile path) (`LBS.writeFile` txJson)
where
txJson = encodePretty' textEnvelopeCddlJSONConfig (serialiseTxLedgerCddl tx) <> "\n"

Expand All @@ -233,7 +233,7 @@ writeTxWitnessFileTextEnvelopeCddl
-> IO (Either (FileError ()) ())
writeTxWitnessFileTextEnvelopeCddl sbe path w =
runExceptT $ do
handleIOExceptT (FileIOError (unFile path)) $ LBS.writeFile (unFile path) txJson
fileIOExceptT (unFile path) (`LBS.writeFile` txJson)
where
txJson = encodePretty' textEnvelopeCddlJSONConfig (serialiseWitnessLedgerCddl sbe w) <> "\n"

Expand Down Expand Up @@ -325,7 +325,6 @@ readTextEnvelopeCddlFromFile
-> IO (Either (FileError TextEnvelopeCddlError) TextEnvelopeCddl)
readTextEnvelopeCddlFromFile path =
runExceptT $ do
bs <- handleIOExceptT (FileIOError path) $
readFileBlocking path
bs <- fileIOExceptT path readFileBlocking
firstExceptT (FileError path . TextEnvelopeCddlAesonDecodeError path)
. hoistEither $ Aeson.eitherDecodeStrict' bs
9 changes: 4 additions & 5 deletions cardano-api/internal/Cardano/Api/SerialiseTextEnvelope.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import Cardano.Binary (DecoderError)

import Control.Monad (unless)
import Control.Monad.Trans.Except (ExceptT (..), runExceptT)
import Control.Monad.Trans.Except.Extra (firstExceptT, handleIOExceptT, hoistEither)
import Control.Monad.Trans.Except.Extra (firstExceptT, hoistEither)
import Data.Aeson (FromJSON (..), ToJSON (..), object, withObject, (.:), (.=))
import qualified Data.Aeson as Aeson
import Data.Aeson.Encode.Pretty (Config (..), defConfig, encodePretty', keyOrder)
Expand Down Expand Up @@ -225,7 +225,7 @@ readFileTextEnvelope :: HasTextEnvelope a
-> IO (Either (FileError TextEnvelopeError) a)
readFileTextEnvelope ttoken path =
runExceptT $ do
content <- handleIOExceptT (FileIOError (unFile path)) $ readFileBlocking (unFile path)
content <- fileIOExceptT (unFile path) readFileBlocking
firstExceptT (FileError (unFile path)) $ hoistEither $ do
te <- first TextEnvelopeAesonDecodeError $ Aeson.eitherDecodeStrict' content
deserialiseFromTextEnvelope ttoken te
Expand All @@ -236,7 +236,7 @@ readFileTextEnvelopeAnyOf :: [FromSomeType HasTextEnvelope b]
-> IO (Either (FileError TextEnvelopeError) b)
readFileTextEnvelopeAnyOf types path =
runExceptT $ do
content <- handleIOExceptT (FileIOError (unFile path)) $ readFileBlocking (unFile path)
content <- fileIOExceptT (unFile path) readFileBlocking
firstExceptT (FileError (unFile path)) $ hoistEither $ do
te <- first TextEnvelopeAesonDecodeError $ Aeson.eitherDecodeStrict' content
deserialiseFromTextEnvelopeAnyOf types te
Expand All @@ -246,8 +246,7 @@ readTextEnvelopeFromFile :: FilePath
-> IO (Either (FileError TextEnvelopeError) TextEnvelope)
readTextEnvelopeFromFile path =
runExceptT $ do
bs <- handleIOExceptT (FileIOError path) $
readFileBlocking path
bs <- fileIOExceptT path readFileBlocking
firstExceptT (FileError path . TextEnvelopeAesonDecodeError)
. hoistEither $ Aeson.eitherDecodeStrict' bs

Expand Down

0 comments on commit a0b4040

Please sign in to comment.