{-# OPTIONS_GHC -fno-warn-orphans #-} module Network.Mail.Mime.Instances ( MailHeaders(..) ) where import ClassyPrelude import Network.Mail.Mime import Data.Aeson (FromJSON(..), ToJSON(..)) import qualified Data.Aeson as Aeson import Data.Aeson.TH import Control.Monad.Fail (MonadFail(..)) import Utils.PathPiece import Utils (assertM) import qualified Data.Csv as Csv import Data.Binary (Binary) import qualified Data.ByteString.Base64 as Base64 import qualified Data.ByteString as BS import Data.Text.Encoding (decodeUtf8') import Control.Lens deriving instance Read Address deriving instance Ord Address deriving anyclass instance Hashable Address deriveToJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 1 } ''Address instance FromJSON Address where parseJSON = Aeson.withObject "Address" $ \obj -> do addressName <- assertM (not . null) <$> (obj Aeson..:? "name") addressEmail <- obj Aeson..: "email" return Address{..} instance Csv.ToNamedRecord Address where toNamedRecord Address{..} = Csv.namedRecord [ "name" Csv..= addressName , "email" Csv..= addressEmail ] instance Csv.DefaultOrdered Address where headerOrder _ = Csv.header [ "name", "email" ] newtype MailHeaders = MailHeaders Headers deriving (Eq, Ord, Read, Show, Generic, Typeable) deriving anyclass (NFData) instance ToJSON MailHeaders where toJSON (MailHeaders hs) = toJSON $ over (traverse . _1) decodeUtf8 hs instance FromJSON MailHeaders where parseJSON = fmap (MailHeaders . over (traverse . _1) encodeUtf8) . parseJSON instance Binary Encoding instance Binary Disposition instance Binary PartContent instance Binary Part instance Binary Address instance Binary Mail deriving anyclass instance NFData Encoding deriving anyclass instance NFData Disposition deriving anyclass instance NFData PartContent deriving anyclass instance NFData Part deriving anyclass instance NFData Address deriving anyclass instance NFData Mail deriveJSON defaultOptions { constructorTagModifier = camelToPathPiece } ''Encoding deriveJSON defaultOptions { constructorTagModifier = camelToPathPiece . dropSuffix "Disposition" } ''Disposition instance ToJSON PartContent where toJSON (PartContent (toStrict -> content)) | BS.all (< 0x80) content , Right content' <- decodeUtf8' content = Aeson.String content' toJSON (PartContent content) = Aeson.object [ "encoding" Aeson..= ("base64" :: String) , "content" Aeson..= decodeUtf8 (Base64.encode $ toStrict content) ] toJSON (NestedParts ps) = toJSON ps instance FromJSON PartContent where parseJSON (Aeson.String t) = return . PartContent . fromStrict $ encodeUtf8 t parseJSON (Aeson.Object o) = do encoding <- o Aeson..: "encoding" content <- o Aeson..: "content" if | encoding == "base64" -> either fail (return . PartContent . fromStrict) . Base64.decode $ encodeUtf8 content | otherwise -> fail $ "Unknown encoding: “" <> encoding <> "”" parseJSON v = NestedParts <$> parseJSON v instance ToJSON Part where toJSON Part{..} = Aeson.object [ "type" Aeson..= partType , "encoding" Aeson..= partEncoding , "disposition" Aeson..= partDisposition , "headers" Aeson..= MailHeaders partHeaders , "content" Aeson..= partContent ] instance FromJSON Part where parseJSON = Aeson.withObject "Part" $ \o -> do partType <- o Aeson..: "type" partEncoding <- o Aeson..: "encoding" partDisposition <- o Aeson..: "disposition" MailHeaders partHeaders <- o Aeson..: "headers" partContent <- o Aeson..: "content" return Part{..}