assumpta-core-0.1.0.0: src/Network/Mail/Assumpta/Trans/Smtp.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
#if __GLASGOW_HASKELL__ >= 801
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
#endif
{-# OPTIONS_HADDOCK show-extensions #-}
{- |
This module provides a template for creating implementations
of 'MonadSmtp' over some abstract connection type.
Operations on a connection can throw IO- and network-based errors,
and we don't handle those in any particular way
ourselves. However, we throw 'SmtpError's if server responses
are unparseable or unexpected.
-}
module Network.Mail.Assumpta.Trans.Smtp
(
-- * Abstract connections
module Conn
-- * SMTP operations
--
-- A monad transformer, 'SmtpT', which provides the ability to
-- send SMTP commands and parse replies, plus operations
-- on the transformer.
, SmtpT(..)
, liftSmtpT
, mapSmtpT
, MonadSmtp
-- ** run SmptT actions
, runSmtpEither
, runSmtp
, withSmtpConnection
-- * Utility functions
, rethrow
)
where
import Control.Monad.Catch (bracket, MonadMask)
import Control.Monad.Except
import Control.Monad.Reader
import Network.Mail.Assumpta.Connection as Conn
import Network.Mail.Assumpta.Types
import Network.Mail.Assumpta.MonadSmtp as MonadSmtp
import Network.Mail.Assumpta.ParseResponse as P (getReply)
{-# ANN module ("HLint: ignore Eta reduce" :: String) #-}
-- | Monad transformer that adds the ability to send SMTP
-- commands and receive server replies over some abstract
-- communications channel, \'conn'.
newtype SmtpT conn m a = SmtpT {
unSmtpT :: ReaderT conn (ExceptT SmtpError m) a
}
deriving (Functor, Applicative, Monad, MonadIO, MonadFix
, MonadError SmtpError, MonadReader conn
)
-- | An instance of 'MonadSmtp' communicating over
-- some 'Connection' type, @conn@.
instance (Connection conn, cstr ~ Cstrt conn, Monad m, cstr (SmtpT conn m))
=> MonadSmtp.MonadSmtp (SmtpT conn m)
where
send bs = ask >>= (`Conn.send` bs)
getReply = asks recv >>= P.getReply
tlsUpgrade = ask >>= upgrade
-- | 'lift', specialised to the 'SmtpT' transformer.
liftSmtpT :: Monad m => m a -> SmtpT conn m a
liftSmtpT = SmtpT . lift . lift
instance MonadTrans (SmtpT conn) where
lift = liftSmtpT
-- | convert an ExceptT into a MonadError
rethrow :: MonadError e m => ExceptT e m b -> m b
rethrow = (>>= either throwError return) . runExceptT
-- | Lifted 'mapExceptT'.
mapSmtpT ::
(m1 (Either SmtpError a1) -> m2 (Either SmtpError a2))
-> SmtpT conn m1 a1 -> SmtpT conn m2 a2
mapSmtpT f (SmtpT x) = SmtpT (mapBoth f x)
where
mapBoth = mapReaderT . mapExceptT
-- | @runSmtpEither c a@
--
-- Run an 'SmtpT' computation @a@ using some connection @c@,
-- and return the result as an 'Either'.
runSmtpEither :: conn -> SmtpT conn m a -> m (Either SmtpError a)
runSmtpEither c = runExceptT . flip runReaderT c . unSmtpT
-- | @runSmtp c a@
--
-- 'runSmtpEither' generalized to 'MonadError', so
-- a caller can
-- use 'Maybe' or or 'MonadError' instances as they choose.
runSmtp :: MonadError SmtpError m => conn -> SmtpT conn m b -> m b
runSmtp c =
rethrow . flip runReaderT c . unSmtpT
-- | 'withConnection', specialized to only run
-- 'MonadSmtp.MonadSmtp' actions.
withSmtpConnection
:: (Cstrt c m, MonadMask m, Connection c, MonadSmtp.MonadSmtp m) =>
Params c -> (c -> m b) -> m b
withSmtpConnection = withConnection
-- | @withSmtpRunner params r a@
--
-- A variant on 'withSmtpConnection',
-- where the caller supplies a \'runner' @r@, which can
-- run 'MonadSmtp.MonadSmtp' actions @a@ and return a result
-- in the current monad.
withSmtpRunner ::
(MonadMask m, Cstrt conn m, Connection conn) =>
Params conn -> (conn -> (forall n . MonadSmtp.MonadSmtp n => n b) -> m b) -> (forall n . MonadSmtp.MonadSmtp n => n b) -> m b
withSmtpRunner params f a =
bracket acquire release (`f` a)
where
acquire = open params
release = close