servant-polysemy-0.1.0: src/Servant/Polysemy/Client.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-|
Module : Servant.Polysemy.Client
Copyright : (c) 2020 Alex Chapman
License : BSD3
Maintainer : alex@farfromthere.net
Stability : experimental
Portability : GHC
Description : A Polysemy effect for running Servant commands (ClientM).
This module allows you to act as a client of a Servant API, within a Polysemy 'Sem'.
Use the servant-client package to generate your clients, which return in the 'ClientM' monad.
You can then use 'runClient' (or 'runClientStreaming') to run your client in 'Sem', and 'runServantClient' (or 'runServantClientStreaming') to interpret the effect.
See <example/Client.hs> for a simple example that can interact with the example servers in the same directory.
-}
module Servant.Polysemy.Client
(
-- * Effects
-- ** Non-Streaming
ServantClient
, runClient'
, runClient
-- ** Streaming
, ServantClientStreaming
, runClientStreaming
-- * Interpreters
-- ** Non-Streaming
, runServantClientUrl
, runServantClient
-- ** Streaming
, runServantClientStreamingUrl
, runServantClientStreaming
-- * Re-exported from Servant
, ClientError
) where
import Control.DeepSeq (NFData)
import Control.Monad ((>=>))
import Network.HTTP.Client (newManager)
import Network.HTTP.Client.TLS (tlsManagerSettings)
import Polysemy
import Polysemy.Cont
import Polysemy.Error
import Servant.Client.Streaming
( BaseUrl
, ClientError
, ClientM
, mkClientEnv
, parseBaseUrl
, runClientM
, withClientM
)
-- | The 'ServantClient' effect allows you to run a 'ClientM' as automatically generated for your API by the servant-client package.
data ServantClient m a where
RunClient' :: NFData o => ClientM o -> ServantClient m (Either ClientError o)
makeSem ''ServantClient
-- | Run this 'ClientM' in the 'Sem' monad.
runClient
:: (Members '[ServantClient, Error ClientError] r, NFData o)
=> ClientM o -> Sem r o
runClient = runClient' >=> fromEither
-- | Interpret the 'ServantClient' effect by running any calls to 'RunClient'' against the given 'BaseUrl'.
runServantClientUrl
:: Member (Embed IO) r
=> BaseUrl -> Sem (ServantClient ': r) a -> Sem r a
runServantClientUrl server m = do
manager <- embed $ newManager tlsManagerSettings
let env = mkClientEnv manager server
interpret (\case
RunClient' client ->
embed $ runClientM client env
) m
-- | Parse the given string as a URL and then behave as 'runServantClientUrl' does.
runServantClient
:: Member (Embed IO) r
=> String -> Sem (ServantClient ': r) a -> Sem r a
runServantClient server m = do
server' <- embed $ parseBaseUrl server
runServantClientUrl server' m
-- | The 'ServantClientStreaming' effect is just like the 'ServantClient' effect,
-- but allows streaming connections.
data ServantClientStreaming m a where
RunClientStreaming :: ClientM o -> ServantClientStreaming m o
makeSem ''ServantClientStreaming
-- | Interpret the 'ServantClientStreaming' effect by running any calls to 'RunClientStreaming' against the given URL.
-- Note that this adds a 'Cont' effect, which you can interpret using 'runContM', probably just before your call to 'runM'.
runServantClientStreamingUrl
:: Members
'[ Cont ref
, Embed IO
, Error ClientError
] r
=> BaseUrl -> Sem (ServantClientStreaming ': r) a -> Sem r a
runServantClientStreamingUrl server m = do
manager <- embed $ newManager tlsManagerSettings
let env = mkClientEnv manager server
interpret (\case
RunClientStreaming client ->
subst (\continue ->
withLowerToIO $ \unliftIO _ ->
withClientM client env (unliftIO . jump continue)
) fromEither
) m
-- | Parse the given string as a URL and then behave as 'runServantClientStreamingUrl'.
runServantClientStreaming
:: Members
'[ Cont ref
, Embed IO
, Error ClientError
] r
=> String -> Sem (ServantClientStreaming ': r) a -> Sem r a
runServantClientStreaming server m = do
server' <- embed $ parseBaseUrl server
runServantClientStreamingUrl server' m