{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UnicodeSyntax #-}
module MatrixBot.MatrixApi.Client where
import Data.Bifunctor (bimap)
import Data.Binary.Builder (toLazyByteString)
import Data.ByteString.Lazy (toStrict)
import Data.Proxy
import Data.Text (Text, pack, unpack)
import Data.Text.Encoding (decodeUtf8)
import Control.Exception.Safe (MonadThrow, throwM)
import Control.Monad.IO.Class (MonadIO (..))
import qualified Control.Monad.Free as Free
import qualified Control.Monad.Logger as ML
import qualified Network.HTTP.Client as HTTP
import qualified Network.HTTP.Client.TLS as HTTP
import Servant.API
import Servant.Client
import Servant.Client.Core.Auth
import qualified Servant.Client.Core.Request as ServantRequest
import qualified Servant.Client.Free as ServantFree
import MatrixBot.Log (logDebug)
import qualified MatrixBot.SharedTypes as T
data MatrixApiClient = MatrixApiClient
{ MatrixApiClient
-> forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, Show a, MonadLogger m,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a)
runMatrixApiClient
∷ ∀ api m a .
( MonadIO m
, MonadFail m
, Show a
, ML.MonadLogger m
, HasClient (Free.Free ServantFree.ClientF) api
, HasClient ClientM api
)
⇒ Proxy api
→ (∀clientM. HasClient clientM api ⇒ Client clientM api → clientM a)
→ m (Either ClientError a)
, MatrixApiClient
-> forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, Show a, MonadLogger m,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a)
runMatrixApiClientDoNotShowReqBody
∷ ∀ api m a .
( MonadIO m
, MonadFail m
, Show a
, ML.MonadLogger m
, HasClient (Free.Free ServantFree.ClientF) api
, HasClient ClientM api
)
⇒ Proxy api
→ (∀clientM. HasClient clientM api ⇒ Client clientM api → clientM a)
→ m (Either ClientError a)
, MatrixApiClient
-> forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, Show a, MonadThrow m, MonadLogger m,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m a
runMatrixApiClient'
∷ ∀ api m a .
( MonadIO m
, MonadFail m
, Show a
, MonadThrow m
, ML.MonadLogger m
, HasClient (Free.Free ServantFree.ClientF) api
, HasClient ClientM api
)
⇒ Proxy api
→ (∀clientM. HasClient clientM api ⇒ Client clientM api → clientM a)
→ m a
, MatrixApiClient
-> forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, Show a, MonadThrow m, MonadLogger m,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m a
runMatrixApiClientDoNotShowReqBody'
∷ ∀ api m a .
( MonadIO m
, MonadFail m
, Show a
, MonadThrow m
, ML.MonadLogger m
, HasClient (Free.Free ServantFree.ClientF) api
, HasClient ClientM api
)
⇒ Proxy api
→ (∀clientM. HasClient clientM api ⇒ Client clientM api → clientM a)
→ m a
}
newtype RequestOptions = RequestOptions
{ RequestOptions -> Maybe Seconds
requestOptionsTimeout ∷ Maybe T.Seconds
}
defaultRequestOptions ∷ RequestOptions
defaultRequestOptions :: RequestOptions
defaultRequestOptions = RequestOptions
{ requestOptionsTimeout :: Maybe Seconds
requestOptionsTimeout = Maybe Seconds
forall a. Maybe a
Nothing
}
mkMatrixApiClient
∷ (MonadIO m, MonadThrow m, ML.MonadLogger m)
⇒ RequestOptions
→ T.HomeServer
→ m MatrixApiClient
mkMatrixApiClient :: forall (m :: * -> *).
(MonadIO m, MonadThrow m, MonadLogger m) =>
RequestOptions -> HomeServer -> m MatrixApiClient
mkMatrixApiClient RequestOptions
reqOpts HomeServer
homeServer = do
Manager
tlsManager ←
ManagerSettings -> m Manager
forall (m :: * -> *). MonadIO m => ManagerSettings -> m Manager
HTTP.newTlsManagerWith ManagerSettings
HTTP.tlsManagerSettings { HTTP.managerResponseTimeout = responseTimeout }
BaseUrl
baseUrl' ← String -> m BaseUrl
forall (m :: * -> *). MonadThrow m => String -> m BaseUrl
parseBaseUrl (String -> m BaseUrl)
-> (HomeServer -> String) -> HomeServer -> m BaseUrl
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
unpack (Text -> String) -> (HomeServer -> Text) -> HomeServer -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text
"https://" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>) (Text -> Text) -> (HomeServer -> Text) -> HomeServer -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HomeServer -> Text
T.unHomeServer (HomeServer -> m BaseUrl) -> HomeServer -> m BaseUrl
forall a b. (a -> b) -> a -> b
$ HomeServer
homeServer
let
clientEnv :: ClientEnv
clientEnv = Manager -> BaseUrl -> ClientEnv
mkClientEnv Manager
tlsManager BaseUrl
baseUrl'
f
∷ ∀ api m a .
( MonadIO m
, MonadFail m
, ML.MonadLogger m
, Show a
, HasClient (Free.Free ServantFree.ClientF) api
, HasClient ClientM api
)
⇒ Bool
→ Proxy api
→ (∀clientM. HasClient clientM api ⇒ Client clientM api → clientM a)
→ m (Either ClientError a)
f :: forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, MonadLogger m, Show a,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Bool
-> Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a)
f Bool
showReqBody p :: Proxy api
p@Proxy api
Proxy forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a
genericClientF = do
case Client (Free ClientF) api -> Free ClientF a
forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a
genericClientF (Client (Free ClientF) api -> Free ClientF a)
-> (Proxy api -> Client (Free ClientF) api)
-> Proxy api
-> Free ClientF a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Proxy api -> Client (Free ClientF) api
forall api.
HasClient (Free ClientF) api =>
Proxy api -> Client (Free ClientF) api
ServantFree.client (Proxy api -> Free ClientF a) -> Proxy api -> Free ClientF a
forall a b. (a -> b) -> a -> b
$ Proxy api
p of
Free.Free (ServantFree.RunRequest Request
req Response -> Free ClientF a
_responseResolver) →
let
fReqBody ∷ ServantRequest.RequestBody → Text
fReqBody :: RequestBody -> Text
fReqBody = \case
ServantRequest.RequestBodyLBS LazyByteString
x →
if Bool
showReqBody
then ByteString -> Text
decodeUtf8 (ByteString -> Text)
-> (LazyByteString -> ByteString) -> LazyByteString -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. LazyByteString -> ByteString
toStrict (LazyByteString -> Text) -> LazyByteString -> Text
forall a b. (a -> b) -> a -> b
$ LazyByteString
x
else Text
"<REDACTED LAZY BYTE STRING>"
ServantRequest.RequestBodyBS ByteString
x →
if Bool
showReqBody
then ByteString -> Text
decodeUtf8 ByteString
x
else Text
"<REDACTED BYTE STRING>"
ServantRequest.RequestBodySource SourceIO LazyByteString
_ → Text
"<REDACTED STREAM>"
fPath :: Builder -> LazyByteString
fPath = Builder -> LazyByteString
toLazyByteString
in do
Request
clientRequest ← IO Request -> m Request
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Request -> m Request) -> IO Request -> m Request
forall a b. (a -> b) -> a -> b
$ BaseUrl -> Request -> IO Request
defaultMakeClientRequest BaseUrl
baseUrl' Request
req
Text -> m ()
forall (m :: * -> *). (MonadLogger m, HasCallStack) => Text -> m ()
logDebug (Text -> m ()) -> Text -> m ()
forall a b. (a -> b) -> a -> b
$ [Text] -> Text
forall a. Monoid a => [a] -> a
mconcat
[ Text
"Making a client request: "
, String -> Text
pack (String -> Text) -> (Request -> String) -> Request -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Request -> String
forall a. Show a => a -> String
show (Request -> Text) -> Request -> Text
forall a b. (a -> b) -> a -> b
$ Request
clientRequest
, String -> Text
pack (String -> Text) -> (Request -> String) -> Request -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RequestF Text LazyByteString -> String
forall a. Show a => a -> String
show (RequestF Text LazyByteString -> String)
-> (Request -> RequestF Text LazyByteString) -> Request -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (RequestBody -> Text)
-> (Builder -> LazyByteString)
-> Request
-> RequestF Text LazyByteString
forall a b c d.
(a -> b) -> (c -> d) -> RequestF a c -> RequestF b d
forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
bimap RequestBody -> Text
fReqBody Builder -> LazyByteString
fPath (Request -> Text) -> Request -> Text
forall a b. (a -> b) -> a -> b
$ Request
req
]
Free.Free (ServantFree.Throw ClientError
x) →
String -> m ()
forall a. String -> m a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Unexpected client request mock failure: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> ClientError -> String
forall a. Show a => a -> String
show ClientError
x
Free.Pure a
x →
String -> m ()
forall a. String -> m a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Unexpected client request mock Pure: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> a -> String
forall a. Show a => a -> String
show a
x
IO (Either ClientError a) -> m (Either ClientError a)
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Either ClientError a) -> m (Either ClientError a))
-> (Proxy api -> IO (Either ClientError a))
-> Proxy api
-> m (Either ClientError a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ClientM a -> ClientEnv -> IO (Either ClientError a))
-> ClientEnv -> ClientM a -> IO (Either ClientError a)
forall a b c. (a -> b -> c) -> b -> a -> c
flip ClientM a -> ClientEnv -> IO (Either ClientError a)
forall a. ClientM a -> ClientEnv -> IO (Either ClientError a)
runClientM ClientEnv
clientEnv (ClientM a -> IO (Either ClientError a))
-> (Proxy api -> ClientM a)
-> Proxy api
-> IO (Either ClientError a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Client ClientM api -> ClientM a
forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a
genericClientF (Client ClientM api -> ClientM a)
-> (Proxy api -> Client ClientM api) -> Proxy api -> ClientM a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Proxy api -> Client ClientM api
forall api.
HasClient ClientM api =>
Proxy api -> Client ClientM api
client (Proxy api -> m (Either ClientError a))
-> Proxy api -> m (Either ClientError a)
forall a b. (a -> b) -> a -> b
$ Proxy api
p
MatrixApiClient -> m MatrixApiClient
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (MatrixApiClient -> m MatrixApiClient)
-> MatrixApiClient -> m MatrixApiClient
forall a b. (a -> b) -> a -> b
$ (forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, Show a, MonadLogger m,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a))
-> (forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, Show a, MonadLogger m,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a))
-> (forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, Show a, MonadThrow m, MonadLogger m,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m a)
-> (forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, Show a, MonadThrow m, MonadLogger m,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m a)
-> MatrixApiClient
MatrixApiClient
(Bool
-> Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a)
forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, MonadLogger m, Show a,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Bool
-> Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a)
f Bool
True)
(Bool
-> Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a)
forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, MonadLogger m, Show a,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Bool
-> Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a)
f Bool
False)
(\Proxy api
p forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a
clientF → Bool
-> Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a)
forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, MonadLogger m, Show a,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Bool
-> Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a)
f Bool
True Proxy api
p Client clientM api -> clientM a
forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a
clientF m (Either ClientError a) -> (Either ClientError a -> m a) -> m a
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (ClientError -> m a) -> (a -> m a) -> Either ClientError a -> m a
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either ClientError -> m a
forall (m :: * -> *) e a.
(HasCallStack, MonadThrow m, Exception e) =>
e -> m a
throwM a -> m a
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure)
(\Proxy api
p forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a
clientF → Bool
-> Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a)
forall api (m :: * -> *) a.
(MonadIO m, MonadFail m, MonadLogger m, Show a,
HasClient (Free ClientF) api, HasClient ClientM api) =>
Bool
-> Proxy api
-> (forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a)
-> m (Either ClientError a)
f Bool
False Proxy api
p Client clientM api -> clientM a
forall (clientM :: * -> *).
HasClient clientM api =>
Client clientM api -> clientM a
clientF m (Either ClientError a) -> (Either ClientError a -> m a) -> m a
forall a b. m a -> (a -> m b) -> m b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (ClientError -> m a) -> (a -> m a) -> Either ClientError a -> m a
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either ClientError -> m a
forall (m :: * -> *) e a.
(HasCallStack, MonadThrow m, Exception e) =>
e -> m a
throwM a -> m a
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure)
where
responseTimeout :: ResponseTimeout
responseTimeout =
ResponseTimeout
-> (Seconds -> ResponseTimeout) -> Maybe Seconds -> ResponseTimeout
forall b a. b -> (a -> b) -> Maybe a -> b
maybe
ResponseTimeout
HTTP.responseTimeoutDefault
(Int -> ResponseTimeout
HTTP.responseTimeoutMicro (Int -> ResponseTimeout)
-> (Seconds -> Int) -> Seconds -> ResponseTimeout
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Integer -> Int
forall a. Num a => Integer -> a
fromInteger (Integer -> Int) -> (Seconds -> Integer) -> Seconds -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Microseconds -> Integer
T.unMicroseconds (Microseconds -> Integer)
-> (Seconds -> Microseconds) -> Seconds -> Integer
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Seconds -> Microseconds
T.secondsToMicroseconds)
(RequestOptions -> Maybe Seconds
requestOptionsTimeout RequestOptions
reqOpts)
type instance AuthClientData (AuthProtect "access-token") = T.AccessToken