{-# 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