{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -- | Integrate Waargonaut with Servant, including support for the tagged typeclass encoder/decoder functionality. module Servant.API.ContentTypes.Waargonaut ( WaargJSON ) where import Control.Category ((.)) import Prelude (show) import Data.Bifunctor (first) import Data.Function (($)) import Data.Proxy (Proxy (..)) import Data.Typeable (Typeable) import qualified Data.List.NonEmpty as NE import qualified Network.HTTP.Media as M import Servant.API.ContentTypes (Accept (..), MimeRender (..), MimeUnrender (..)) import qualified Data.ByteString.Lazy as BL import qualified Text.PrettyPrint.Annotated.WL as WL import Waargonaut.Attoparsec (pureDecodeAttoparsecByteString) import qualified Waargonaut.Decode as D import qualified Waargonaut.Encode as E import Waargonaut.Generic (JsonDecode, JsonEncode) import qualified Waargonaut.Generic as G -- | Replacement for 'Servant.API.ContentTypes.JSON' that will use the relevant instances from -- Waargonaut that are tagged with the type @t@. -- -- This allows you to have separate typeclass implementations for the same type for different routes -- and have it be evident in the types. Without the need for creating a 'newtype' for each one. -- -- Where you would use 'JSON' to use 'aeson' for encoding or decoding, you use 'WaargJSON t', with -- the @t@ denoting the tag type. Refer to the -- package for more information about why this is so. -- -- A hello world example: -- -- >>> -- GET /hello/world -- >>> -- returning a JSON encoded World value -- >>> data MyTag = MyTag -- >>> type MyApi = "hello" :> "world" :> Get '[WaargJSON MyTag] World -- data WaargJSON t deriving Typeable instance Accept (WaargJSON t) where contentTypes _ = "application" M.// "json" M./: ("charset", "utf-8") NE.:| [ "application" M.// "json" ] instance JsonDecode t a => MimeUnrender (WaargJSON t) a where mimeUnrender _ = first handleErr . pureDecodeAttoparsecByteString (G.proxy G.mkDecoder (Proxy :: Proxy t)) . BL.toStrict where handleErr (dErr, hist) = WL.display . WL.renderPrettyDefault $ WL.text (show dErr) WL.<##> D.ppCursorHistory hist instance JsonEncode t a => MimeRender (WaargJSON t) a where mimeRender _ = E.simplePureEncodeByteStringNoSpaces (G.proxy G.mkEncoder (Proxy :: Proxy t))