sockets-and-pipes-0.1: src/SocketsAndPipes/Serve/Exceptions.hs
module SocketsAndPipes.Serve.Exceptions
(
-- * Types
BindFailed (..), AddrTried (..),
-- * Functions related to the types
displayBindFailed, displayAddrTried,
-- * General functions for working with exceptions
overException, firstSuccessOrAllExceptions
) where
import Control.Exception.Safe
( Exception (displayException), SomeException, catch, throw )
import Data.Foldable ( fold )
import qualified Data.Sequence as Seq
import qualified Data.Foldable as Seq ( toList )
import qualified Data.List as List
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Builder as TB
import qualified Network.Socket as Socket
data BindFailed =
BindFailed
{ bindAddrsTried :: [AddrTried]
}
deriving Show
instance Exception BindFailed
where
displayException = LT.unpack . TB.toLazyText . displayBindFailed
data AddrTried =
AddrTried
{ addrTried :: Socket.AddrInfo
, addrException :: SomeException
}
deriving Show
instance Exception AddrTried
where
displayException = LT.unpack . TB.toLazyText . displayAddrTried
displayBindFailed :: BindFailed -> TB.Builder
displayBindFailed BindFailed{ bindAddrsTried }
| null bindAddrsTried = displayBindFailedNoAddresses
| otherwise = displayBindFailedWithAddrs bindAddrsTried
displayBindFailedNoAddresses :: TB.Builder
displayBindFailedNoAddresses =
TB.fromString
"Failed to set up a passive socket for the server \
\because no candidate addresses were found."
displayBindFailedWithAddrs :: [AddrTried] -> TB.Builder
displayBindFailedWithAddrs bindAddrsTried =
TB.fromString "Failed to set up a passive socket for the server. \
\The following addresses were tried:\n" <>
fold
(
List.intersperse
(TB.fromString "\n")
(
List.map
( \AddrTried{ addrTried, addrException } ->
TB.fromString " ❌ " <>
TB.fromString (show (Socket.addrAddress addrTried)) <>
TB.fromString " — " <>
TB.fromString (displayException addrException)
)
bindAddrsTried
)
)
displayAddrTried :: AddrTried -> TB.Builder
displayAddrTried AddrTried{ addrTried, addrException } =
TB.fromString (show addrTried) <> TB.fromString ": "
<> TB.fromString (displayException addrException)
overException :: (Exception e1, Exception e2) =>
(e1 -> e2) -- ^ How to turn the exception
-- into a different exception
-> IO a -- ^ Action that might throw the first exception
-> IO a -- ^ Action that might throw the second exception
overException f a = catch a (throw . f)
{- ^
If the action throws an exception, turn it into a different
exception. This is useful to add information that explains
the context in which the original exception occurred.
-}
firstSuccessOrAllExceptions :: (Exception e1, Exception e2) =>
([e1] -> e2) -- ^ How to collect the many exceptions into one
-> [IO a] -- ^ Many actions that may fail
-> IO a -- ^ The result from the first action that succeeds,
-- or else throws a collection of all the exceptions.
firstSuccessOrAllExceptions f = go Seq.empty
where
go exs [] = throw (f (Seq.toList exs))
go exs (a : as) = catch a (\ex -> go (exs Seq.|> ex) as)