packages feed

eved-0.0.2.0: src/Web/Eved/Client.hs

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

module Web.Eved.Client
    where

import           Control.Monad.Reader
import qualified Data.CaseInsensitive as CI
import           Data.List.NonEmpty   (NonEmpty (..))
import           Data.Maybe           (mapMaybe)
import           Data.Text            (Text)
import qualified Data.Text            as T
import           Data.Text.Encoding   (encodeUtf8)
import qualified Network.HTTP.Client  as HttpClient
import           Network.HTTP.Types   (hContentType, parseQuery,
                                       queryTextToQuery, queryToQueryText,
                                       renderQuery, renderStdMethod)
import qualified Web.Eved.ContentType as CT
import qualified Web.Eved.Header      as H
import           Web.Eved.Internal
import qualified Web.Eved.QueryParam  as QP
import qualified Web.Eved.UrlElement  as UE
import qualified Web.HttpApiData      as HttpApiData

newtype ClientM a = ClientM { unClientM :: ReaderT HttpClient.Manager IO a }

runClientIO :: ClientM a -> IO a
runClientIO m = do
    HttpClient.newManager HttpClient.defaultManagerSettings
        >>= runReaderT (runClient m)

runClient :: (MonadIO m, MonadReader env m, HttpClient.HasHttpManager env) => ClientM a -> m a
runClient (ClientM m) =
    asks HttpClient.getHttpManager >>= (liftIO . runReaderT m)

newtype EvedClient a = EvedClient
    { client :: HttpClient.Request -> a
    }

getClient :: EvedClient a -> Text -> a
getClient (EvedClient f) = f . HttpClient.parseRequest_ . T.unpack

instance Eved EvedClient ClientM where
    l .<|> r = EvedClient $ \req ->
        client l req :<|> client r req

    lit s next = EvedClient $ \req ->
        client next req{ HttpClient.path = HttpClient.path req <> encodeUtf8 (HttpApiData.toUrlPiece s) <> "/"}
    capture s el next = EvedClient $ \req a ->
        client next req{ HttpClient.path = HttpClient.path req <> encodeUtf8 (UE.toUrlPiece el a) <> "/" }
    reqBody (ctype:|_) next = EvedClient $ \req a ->
        client next req{ HttpClient.requestBody = HttpClient.RequestBodyLBS (snd $ CT.toContentType ctype a)
                       , HttpClient.requestHeaders = CT.contentTypeHeader ctype:HttpClient.requestHeaders req
                       }
    queryParam argName el next = EvedClient $ \req val ->
        client next req{HttpClient.queryString =
            let query = parseQuery $ HttpClient.queryString req
                queryText = queryToQueryText query
                newArgs = (\v -> (HttpApiData.toUrlPiece argName, Just v)) <$> QP.toQueryParam el val
            in renderQuery False $ queryTextToQuery (newArgs <> queryText)}


    header headerName el next = EvedClient $ \req val ->
        let headers = HttpClient.requestHeaders req
            ciHeaderName = CI.mk (encodeUtf8 headerName)
            newHeaders = maybe headers (\v -> (ciHeaderName, v):headers) (H.toHeaderValue el val)
        in client next req{HttpClient.requestHeaders = newHeaders}


    verb method _status ctypes = EvedClient $ \req -> ClientM $ do
        let reqWithMethod = req{ HttpClient.method = renderStdMethod method
                               , HttpClient.requestHeaders = CT.acceptHeader ctypes:HttpClient.requestHeaders req
                               }
        manager <- ask
        resp <- liftIO $ HttpClient.httpLbs reqWithMethod manager
        let mBodyParser = CT.chooseContentCType ctypes mempty =<< lookup hContentType (HttpClient.responseHeaders resp)
        case mBodyParser of
          Just bodyParser  -> case bodyParser (HttpClient.responseBody resp) of
                                Right a -> pure a
                                Left _ -> error "Unimplemented: Content-Type matched but parse failed"
          Nothing -> error "Unimplemented: No Matching Content-Type"