{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings     #-}

-- |
-- Module    : Servant.XML
-- Copyright : (c) Colin Woodbury, 2018 - 2024
-- License   : BSD3
-- Maintainer: Colin Woodbury <colin@fosskers.ca>
--
-- Servant support for XML.
--
-- Types with a `ToXml` instance will be automatically marshalled into XML and
-- successfully returned by Servant endpoints. Types with a `FromXml` instance
-- can be decoded from request bodies.
--
-- In implementing these typeclass instances, you can use the primitives found
-- in the /xmlbf/ library.

module Servant.XML where

import           Data.ByteString.Builder (toLazyByteString)
import           Data.ByteString.Lazy (toStrict)
import qualified Data.List.NonEmpty as NE
import qualified Network.HTTP.Media as M
import           Servant.API
import           Xmlbf (FromXml(..), ToXml(..), encode, parse)
import           Xmlbf.Xeno (fromRawXml)

---

-- | The /application\/xml/ Content-Type. To be used in Servant endpoints like:
--
-- @
-- data Foo = ...
--
-- instance ToXml Foo where
--   toXml foo = ...
--
-- type API = ...
--   :\<|\> "foo" :> Get '[XML] Foo
--   :\<|\> "foo" :> "update" :> ReqBody '[XML] Foo :> PostAccepted '[JSON] ()
-- @
data XML

instance Accept XML where
  contentTypes :: Proxy XML -> NonEmpty MediaType
contentTypes Proxy XML
_ =
     ByteString
"application" ByteString -> ByteString -> MediaType
M.// ByteString
"xml" MediaType -> (ByteString, ByteString) -> MediaType
M./: (ByteString
"charset", ByteString
"utf-8") MediaType -> [MediaType] -> NonEmpty MediaType
forall a. a -> [a] -> NonEmpty a
NE.:| [ ByteString
"application" ByteString -> ByteString -> MediaType
M.// ByteString
"xml" ]

instance ToXml a => MimeRender XML a where
  mimeRender :: Proxy XML -> a -> ByteString
mimeRender Proxy XML
_ = Builder -> ByteString
toLazyByteString (Builder -> ByteString) -> (a -> Builder) -> a -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Node] -> Builder
encode ([Node] -> Builder) -> (a -> [Node]) -> a -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> [Node]
forall a. ToXml a => a -> [Node]
toXml

instance FromXml a => MimeUnrender XML a where
  mimeUnrender :: Proxy XML -> ByteString -> Either String a
mimeUnrender Proxy XML
_ ByteString
bs = ByteString -> Either String [Node]
fromRawXml (ByteString -> ByteString
toStrict ByteString
bs) Either String [Node]
-> ([Node] -> Either String a) -> Either String a
forall a b.
Either String a -> (a -> Either String b) -> Either String b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Parser a -> [Node] -> Either String a
forall a. Parser a -> [Node] -> Either String a
parse Parser a
forall a (m :: * -> *). (FromXml a, Monad m) => ParserT m a
forall (m :: * -> *). Monad m => ParserT m a
fromXml