mailgun-0.2: src/Network/Mail/Mailgun/Config.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module Network.Mail.Mailgun.Config
( DomainName
, MailgunConfig(..)
, HasMailgunConfig(..)
, mailgunGetConfig
, mailgunFromEnv, mailgunFromIni
, MailgunConfigException(..)
, _MailgunApiKeyRequired, _MailgunDomainRequired
, _MailgunInvalidRegion, _MailgunIniNotFound
, _MailgunConextUnavailable
) where
import Control.Applicative
import Control.Lens
import Control.Monad.Catch
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
import qualified Data.ByteString as BS
import Data.Foldable
import Data.Ini
import Data.Maybe
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import System.Environment
import System.FilePath
data MailgunConfigException
= MailgunApiKeyRequired
| MailgunDomainRequired
| MailgunInvalidRegion
| MailgunIniNotFound
| MailgunConextUnavailable
deriving (Show)
instance Exception MailgunConfigException
makePrisms ''MailgunConfigException
type DomainName = String
-- | The configuration we use when accessing the Mailgun API.
data MailgunConfig
= MailgunConfig
{ _mailgunDomain :: DomainName
-- ^ The domain we're using mailgun with.
, _mailgunApiKey :: BS.ByteString
-- ^ Our mailgun API key.
, _mailgunApiDomain :: String
-- ^ The base URL for the mailgun API, usually "https://api.mailgun.net"
, _mailgunTestMode :: Bool
}
deriving (Show, Eq)
makeClassy ''MailgunConfig
-- | The domain for the API URL used for the US region.
usApiDomain :: String
usApiDomain = "api.mailgun.net"
-- | The domain for the API URL used for the EU region.
euApiDomain :: String
euApiDomain = "api.eu.mailgun.net"
-- | Uses the available options to discover a MaingunConfig if possible.
mailgunGetConfig :: (MonadIO m, MonadCatch m) => m MailgunConfig
mailgunGetConfig = do
ec <- runMaybeT . msum .
map (\act -> act `catch` (\(_::MailgunConfigException) -> MaybeT $ pure Nothing)) $
[ mailgunFromEnv
, mailgunFromIni
]
maybe (throwM MailgunConextUnavailable) pure ec
-- | Builds a MaingunConfig from enviromental variables.
--
-- MAILGUN_API_KEY: Required; the API key for mailgun.
-- MAILGUN_DOMAIN: Required; the domain in mailgun we're using.
-- MAILGUN_REGION: Optional; Selects teh regional API endpoint.
-- Valid values are 'US', and 'EU', defaults to 'US'.
-- MAILGUN_API_BASE: Optional; Override the base URL (primarily for testing).
-- Takes presidence over MAILGUN_REGION.
-- MAILGUN_LIVE: Optional: Unless set to True, set to test mode.
-- In test mode Mailgun accepts but does not send messages.
mailgunFromEnv :: (MonadIO m, MonadThrow m) => m MailgunConfig
mailgunFromEnv = do
apiKey <- maybe (throwM MailgunApiKeyRequired) (pure . TE.encodeUtf8 . T.pack) =<<
liftIO (lookupEnv "MAILGUN_API_KEY")
domain <- maybe (throwM MailgunDomainRequired) pure =<<
liftIO (lookupEnv "MAILGUN_DOMAIN")
testmode <- ((Just "True")==) <$> liftIO (lookupEnv "MAILGUN_DOMAIN")
apiDomain <- liftIO (lookupEnv "MAILGUN_API_BASE") >>= \case
Just ab -> pure ab
Nothing ->
liftIO (lookupEnv "MAILGUN_REGION") >>= \case
Nothing -> pure usApiDomain
Just "US" -> pure usApiDomain
Just "EU" -> pure euApiDomain
_ -> throwM MailgunInvalidRegion
pure $ MailgunConfig domain apiKey apiDomain testmode
-- | Looks for an ini format file at ".mailgun" and "~/.mailgun" in that order.
-- Credentials are read from the ini in the format:
-- @
-- [mailgun]
-- region: US
-- domain: mydomain.com
-- key: 3ax6xnjp29jd6fds4gc373sgvjxteol0
-- api_domain: api.mailgun.com
-- live: True
-- @
--
-- The API key and domain are required, other values are optional.
mailgunFromIni :: forall m . (MonadIO m, MonadThrow m) => m MailgunConfig
mailgunFromIni = do
ini <- (maybe (throwM MailgunIniNotFound) pure . asum) =<<
liftIO (sequence
[ readIniFileMay ".mailgun"
, lookupEnv "HOME" >>=
maybe (pure Nothing) (\h -> readIniFileMay (h </> ".mailgun"))
])
apiKey <- TE.encodeUtf8 . T.pack <$> lookupMailgun MailgunApiKeyRequired ini "key"
domain <- lookupMailgun MailgunApiKeyRequired ini "domain"
let testmode = (Just "True") == lookupMailgunMay ini "live"
let apiDomain = fromMaybe usApiDomain $
(lookupMailgunMay ini "api_domain")
<|> ((\case
"US" -> usApiDomain
"EU" -> euApiDomain
_ -> usApiDomain)
<$> (lookupMailgunMay ini "region"))
pure $ MailgunConfig domain apiKey apiDomain testmode
where
readIniFileMay :: FilePath -> IO (Maybe Ini)
readIniFileMay fp = (maybeRight <$> readIniFile fp) `catchIOError` (const (pure Nothing))
lookupMailgun :: MailgunConfigException -> Ini -> Text -> m String
lookupMailgun e ini key = maybe (throwM e) pure $ lookupMailgunMay ini key
lookupMailgunMay :: Ini -> Text -> Maybe String
lookupMailgunMay ini key =
either (const Nothing) (Just . T.unpack) $ lookupValue "mailgun" key ini
maybeRight :: Either a b -> Maybe b
maybeRight (Left _) = Nothing
maybeRight (Right b) = Just b