packages feed

servant-serialization-0.2.0: lib/Servant/API/ContentTypes/ShowRead.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Servant.API.ContentTypes.ShowRead where

import Data.Proxy (Proxy(..))
import Network.HTTP.Media ((//))
import qualified Data.List.NonEmpty as NonEmpty

import Control.Monad ((<=<))
import Data.Text.Lazy (pack, unpack)
import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8')
import Data.Text.Encoding.Error (UnicodeException(..))
import Text.Read (readEither)

import Servant.API.ContentTypes

-- | Content-type for instances of the 'Show' and 'Read' classes encoded as
-- UTF-8 data. This is probably slow.
data ShowRead

-- | Mime-type using the phrases "haskell" and "showread".
instance Accept ShowRead where
    contentTypes Proxy = NonEmpty.fromList
        [ "application" // "x-haskell-showread"
        , "application" // "vnd.haskell.showread"
        ]

instance Show a => MimeRender ShowRead a where
    mimeRender Proxy = encodeUtf8 . pack . show

-- $setup
-- >>> :set -XOverloadedStrings

-- | Decode UTF-8 data and then with 'Read' instance.
--
-- >>> mimeUnrender (Proxy :: Proxy ShowRead) "1e5" :: Either String Double
-- Right 100000.0
--
-- >>> mimeUnrender (Proxy :: Proxy ShowRead) "hello" :: Either String Double
-- Left "Prelude.read: no parse"
--
-- >>> mimeUnrender (Proxy :: Proxy ShowRead) "hello\xc3\x28" :: Either String Double
-- Left "Data.Text.Internal.Encoding.streamDecodeUtf8With: Invalid UTF-8 stream: invalid byte-value: 195"
instance Read a => MimeUnrender ShowRead a where
    mimeUnrender Proxy = readEither . unpack <=< mapLeft prettyErr . decodeUtf8'
      where
        mapLeft f = either (Left . f) Right
        prettyErr (DecodeError err byteVal) =
            err ++ maybe "" ((": invalid byte-value: " ++) . show) byteVal
        prettyErr _ = "unknown error" -- TODO: when 'text' removes deprecated 'EncodeError' constructor, remove this case