{-# LANGUAGE CPP                        #-}
{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE DeriveFunctor              #-}
{-# LANGUAGE ExistentialQuantification  #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE RankNTypes                 #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE UndecidableInstances       #-}
{-# LANGUAGE UndecidableSuperClasses    #-}
module Network.HTTP.ApiMaker.Class
  ( Request(..)
  , Config (..)
  , SessionState (..)
  , Session(..)
  , emptySession
  , runSafeReqM
  , askConfig
  , askApiConfig
  , SafeReqSt
  , SafeReq
  , SafeReqM (..)
  , SafeException (..)
  , throwUserException
  ) where

import           Control.Exception
import           Control.Monad.Base
import           Control.Monad.Except
import           Control.Monad.Trans.Reader
import           Control.Monad.Trans.State
import           Data.Dynamic
import           Data.Kind                          (Type)
import           Data.Proxy
import qualified Network.HTTP.Client                as C
import           Network.HTTP.Req

import           Network.HTTP.ApiMaker.SessionState


-- | Class definition for a 'Request'. Every request should implement this, the rest is then handled by the library. See 'Network.HTTP.ApiMaker.Ops.mkReq' to create a request, the functions
--  'Network.HTTP.ApiMaker.Ops.mkReqM' and 'Network.HTTP.ApiMaker.Ops.runRequests' to build a 'SafeReqM' monad that shares the same state, session and configuration, and finally
--  'Network.HTTP.ApiMaker.Ops.runReqM', 'Network.HTTP.ApiMaker.Ops.runSessReqM', 'Network.HTTP.ApiMaker.Ops.runReqWithParamsM' and 'Network.HTTP.ApiMaker.Ops.runSessReqWithParamsM' to run the monad.
class (HttpMethod (Method r), HttpBody (Body r), HttpResponse (Response r), HttpBodyAllowed (AllowsBody (Method r)) (ProvidesBody (Body r))) =>
      Request cfg r
  where
  type Method r :: Type
  type Body r :: Type
  type Response r :: Type
  type Output r :: Type
  -- type Protocol r :: Scheme
  method   :: cfg -> r -> Method r
  url      :: cfg -> r -> Url 'Https -- (Protocol r)
  body     :: cfg -> r -> Body r
  response :: cfg -> r -> Proxy (Response r)
  option   :: cfg -> r -> IO (Option 'Https) -- (Protocol r)
  requestModifier :: cfg -> r -> C.Request -> IO C.Request
  requestModifier cfg
_ r
_ = Request -> IO Request
forall (m :: Type -> Type) a. Monad m => a -> m a
return
  process  :: (MonadHttp m, MonadError SafeException m, SessionState st) => cfg -> r -> Response r -> StateT st m (Output r)


-- Type safe request

-- | Configuration that is passed from request to request to hold the session and default https header options. It also holds a user defined configuration.
data Config cfg = Config
  { Config cfg -> HttpConfig
httpConfig           :: HttpConfig
  , Config cfg -> [Option 'Https]
apiDefaultParameters :: [Option 'Https]
  , Config cfg -> cfg
apiConfig            :: cfg
  }

-- | Safe request monad with customized session state `sessionState`, Config `cfg` and result `a`.
type SafeReqSt sessionState cfg a = StateT sessionState (SafeReqM cfg) a

-- | Safe request monad with predetermined @Session@, config `cfg` and result `a`.
type SafeReq cfg a = SafeReqSt Session cfg a


data SafeException
  = ReqException HttpException
  | forall e. (Typeable e, Exception e) =>
              SafeUserException e

instance Exception SafeException
instance Show SafeException where
  show :: SafeException -> String
show (ReqException HttpException
e)      = HttpException -> String
forall a. Show a => a -> String
show HttpException
e
  show (SafeUserException e
e) = e -> String
forall a. Show a => a -> String
show e
e


-- | Safe request, e.g. all errors are caught and tured into exceptions.
newtype SafeReqM cfg a =
  SafeReqM (ExceptT SafeException (ReaderT (Config cfg) IO) a)
  deriving (a -> SafeReqM cfg b -> SafeReqM cfg a
(a -> b) -> SafeReqM cfg a -> SafeReqM cfg b
(forall a b. (a -> b) -> SafeReqM cfg a -> SafeReqM cfg b)
-> (forall a b. a -> SafeReqM cfg b -> SafeReqM cfg a)
-> Functor (SafeReqM cfg)
forall a b. a -> SafeReqM cfg b -> SafeReqM cfg a
forall a b. (a -> b) -> SafeReqM cfg a -> SafeReqM cfg b
forall cfg a b. a -> SafeReqM cfg b -> SafeReqM cfg a
forall cfg a b. (a -> b) -> SafeReqM cfg a -> SafeReqM cfg b
forall (f :: Type -> Type).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
<$ :: a -> SafeReqM cfg b -> SafeReqM cfg a
$c<$ :: forall cfg a b. a -> SafeReqM cfg b -> SafeReqM cfg a
fmap :: (a -> b) -> SafeReqM cfg a -> SafeReqM cfg b
$cfmap :: forall cfg a b. (a -> b) -> SafeReqM cfg a -> SafeReqM cfg b
Functor, Functor (SafeReqM cfg)
a -> SafeReqM cfg a
Functor (SafeReqM cfg)
-> (forall a. a -> SafeReqM cfg a)
-> (forall a b.
    SafeReqM cfg (a -> b) -> SafeReqM cfg a -> SafeReqM cfg b)
-> (forall a b c.
    (a -> b -> c)
    -> SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg c)
-> (forall a b. SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg b)
-> (forall a b. SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg a)
-> Applicative (SafeReqM cfg)
SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg b
SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg a
SafeReqM cfg (a -> b) -> SafeReqM cfg a -> SafeReqM cfg b
(a -> b -> c) -> SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg c
forall cfg. Functor (SafeReqM cfg)
forall a. a -> SafeReqM cfg a
forall cfg a. a -> SafeReqM cfg a
forall a b. SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg a
forall a b. SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg b
forall a b.
SafeReqM cfg (a -> b) -> SafeReqM cfg a -> SafeReqM cfg b
forall cfg a b. SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg a
forall cfg a b. SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg b
forall cfg a b.
SafeReqM cfg (a -> b) -> SafeReqM cfg a -> SafeReqM cfg b
forall a b c.
(a -> b -> c) -> SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg c
forall cfg a b c.
(a -> b -> c) -> SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg c
forall (f :: Type -> Type).
Functor f
-> (forall a. a -> f a)
-> (forall a b. f (a -> b) -> f a -> f b)
-> (forall a b c. (a -> b -> c) -> f a -> f b -> f c)
-> (forall a b. f a -> f b -> f b)
-> (forall a b. f a -> f b -> f a)
-> Applicative f
<* :: SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg a
$c<* :: forall cfg a b. SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg a
*> :: SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg b
$c*> :: forall cfg a b. SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg b
liftA2 :: (a -> b -> c) -> SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg c
$cliftA2 :: forall cfg a b c.
(a -> b -> c) -> SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg c
<*> :: SafeReqM cfg (a -> b) -> SafeReqM cfg a -> SafeReqM cfg b
$c<*> :: forall cfg a b.
SafeReqM cfg (a -> b) -> SafeReqM cfg a -> SafeReqM cfg b
pure :: a -> SafeReqM cfg a
$cpure :: forall cfg a. a -> SafeReqM cfg a
$cp1Applicative :: forall cfg. Functor (SafeReqM cfg)
Applicative, Applicative (SafeReqM cfg)
a -> SafeReqM cfg a
Applicative (SafeReqM cfg)
-> (forall a b.
    SafeReqM cfg a -> (a -> SafeReqM cfg b) -> SafeReqM cfg b)
-> (forall a b. SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg b)
-> (forall a. a -> SafeReqM cfg a)
-> Monad (SafeReqM cfg)
SafeReqM cfg a -> (a -> SafeReqM cfg b) -> SafeReqM cfg b
SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg b
forall cfg. Applicative (SafeReqM cfg)
forall a. a -> SafeReqM cfg a
forall cfg a. a -> SafeReqM cfg a
forall a b. SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg b
forall a b.
SafeReqM cfg a -> (a -> SafeReqM cfg b) -> SafeReqM cfg b
forall cfg a b. SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg b
forall cfg a b.
SafeReqM cfg a -> (a -> SafeReqM cfg b) -> SafeReqM cfg b
forall (m :: Type -> Type).
Applicative m
-> (forall a b. m a -> (a -> m b) -> m b)
-> (forall a b. m a -> m b -> m b)
-> (forall a. a -> m a)
-> Monad m
return :: a -> SafeReqM cfg a
$creturn :: forall cfg a. a -> SafeReqM cfg a
>> :: SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg b
$c>> :: forall cfg a b. SafeReqM cfg a -> SafeReqM cfg b -> SafeReqM cfg b
>>= :: SafeReqM cfg a -> (a -> SafeReqM cfg b) -> SafeReqM cfg b
$c>>= :: forall cfg a b.
SafeReqM cfg a -> (a -> SafeReqM cfg b) -> SafeReqM cfg b
$cp1Monad :: forall cfg. Applicative (SafeReqM cfg)
Monad, Monad (SafeReqM cfg)
Monad (SafeReqM cfg)
-> (forall a. IO a -> SafeReqM cfg a) -> MonadIO (SafeReqM cfg)
IO a -> SafeReqM cfg a
forall cfg. Monad (SafeReqM cfg)
forall a. IO a -> SafeReqM cfg a
forall cfg a. IO a -> SafeReqM cfg a
forall (m :: Type -> Type).
Monad m -> (forall a. IO a -> m a) -> MonadIO m
liftIO :: IO a -> SafeReqM cfg a
$cliftIO :: forall cfg a. IO a -> SafeReqM cfg a
$cp1MonadIO :: forall cfg. Monad (SafeReqM cfg)
MonadIO)

askConfig :: SafeReqM cfg (Config cfg)
askConfig :: SafeReqM cfg (Config cfg)
askConfig = ExceptT SafeException (ReaderT (Config cfg) IO) (Config cfg)
-> SafeReqM cfg (Config cfg)
forall cfg a.
ExceptT SafeException (ReaderT (Config cfg) IO) a -> SafeReqM cfg a
SafeReqM (ReaderT (Config cfg) IO (Config cfg)
-> ExceptT SafeException (ReaderT (Config cfg) IO) (Config cfg)
forall (t :: (Type -> Type) -> Type -> Type) (m :: Type -> Type) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift ReaderT (Config cfg) IO (Config cfg)
forall (m :: Type -> Type) r. Monad m => ReaderT r m r
ask)

askApiConfig :: SafeReqM cfg cfg
askApiConfig :: SafeReqM cfg cfg
askApiConfig = Config cfg -> cfg
forall cfg. Config cfg -> cfg
apiConfig (Config cfg -> cfg)
-> SafeReqM cfg (Config cfg) -> SafeReqM cfg cfg
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
<$> ExceptT SafeException (ReaderT (Config cfg) IO) (Config cfg)
-> SafeReqM cfg (Config cfg)
forall cfg a.
ExceptT SafeException (ReaderT (Config cfg) IO) a -> SafeReqM cfg a
SafeReqM (ReaderT (Config cfg) IO (Config cfg)
-> ExceptT SafeException (ReaderT (Config cfg) IO) (Config cfg)
forall (t :: (Type -> Type) -> Type -> Type) (m :: Type -> Type) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift ReaderT (Config cfg) IO (Config cfg)
forall (m :: Type -> Type) r. Monad m => ReaderT r m r
ask)

instance MonadBase IO (SafeReqM cfg) where
  liftBase :: IO α -> SafeReqM cfg α
liftBase = IO α -> SafeReqM cfg α
forall (m :: Type -> Type) a. MonadIO m => IO a -> m a
liftIO

instance MonadHttp (SafeReqM cfg) where
  handleHttpException :: HttpException -> SafeReqM cfg a
handleHttpException = ExceptT SafeException (ReaderT (Config cfg) IO) a -> SafeReqM cfg a
forall cfg a.
ExceptT SafeException (ReaderT (Config cfg) IO) a -> SafeReqM cfg a
SafeReqM (ExceptT SafeException (ReaderT (Config cfg) IO) a
 -> SafeReqM cfg a)
-> (HttpException
    -> ExceptT SafeException (ReaderT (Config cfg) IO) a)
-> HttpException
-> SafeReqM cfg a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SafeException -> ExceptT SafeException (ReaderT (Config cfg) IO) a
forall e (m :: Type -> Type) a. MonadError e m => e -> m a
throwError (SafeException
 -> ExceptT SafeException (ReaderT (Config cfg) IO) a)
-> (HttpException -> SafeException)
-> HttpException
-> ExceptT SafeException (ReaderT (Config cfg) IO) a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HttpException -> SafeException
ReqException
  getHttpConfig :: SafeReqM cfg HttpConfig
getHttpConfig = Config cfg -> HttpConfig
forall cfg. Config cfg -> HttpConfig
httpConfig (Config cfg -> HttpConfig)
-> SafeReqM cfg (Config cfg) -> SafeReqM cfg HttpConfig
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
<$> ExceptT SafeException (ReaderT (Config cfg) IO) (Config cfg)
-> SafeReqM cfg (Config cfg)
forall cfg a.
ExceptT SafeException (ReaderT (Config cfg) IO) a -> SafeReqM cfg a
SafeReqM (ReaderT (Config cfg) IO (Config cfg)
-> ExceptT SafeException (ReaderT (Config cfg) IO) (Config cfg)
forall (t :: (Type -> Type) -> Type -> Type) (m :: Type -> Type) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift ReaderT (Config cfg) IO (Config cfg)
forall (m :: Type -> Type) r. Monad m => ReaderT r m r
ask)

instance MonadError SafeException (SafeReqM cfg) where
  throwError :: SafeException -> SafeReqM cfg a
throwError = ExceptT SafeException (ReaderT (Config cfg) IO) a -> SafeReqM cfg a
forall cfg a.
ExceptT SafeException (ReaderT (Config cfg) IO) a -> SafeReqM cfg a
SafeReqM (ExceptT SafeException (ReaderT (Config cfg) IO) a
 -> SafeReqM cfg a)
-> (SafeException
    -> ExceptT SafeException (ReaderT (Config cfg) IO) a)
-> SafeException
-> SafeReqM cfg a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SafeException -> ExceptT SafeException (ReaderT (Config cfg) IO) a
forall e (m :: Type -> Type) a. MonadError e m => e -> m a
throwError (SafeException
 -> ExceptT SafeException (ReaderT (Config cfg) IO) a)
-> (SafeException -> SafeException)
-> SafeException
-> ExceptT SafeException (ReaderT (Config cfg) IO) a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SafeException -> SafeException
forall e. (Typeable e, Exception e) => e -> SafeException
SafeUserException
  catchError :: SafeReqM cfg a
-> (SafeException -> SafeReqM cfg a) -> SafeReqM cfg a
catchError SafeReqM cfg a
c SafeException -> SafeReqM cfg a
h = do
    Config cfg
cfg <- SafeReqM cfg (Config cfg)
forall cfg. SafeReqM cfg (Config cfg)
askConfig
    Either SafeException a
res <- Config cfg
-> SafeReqM cfg a -> SafeReqM cfg (Either SafeException a)
forall (m :: Type -> Type) cfg a.
MonadIO m =>
Config cfg -> SafeReqM cfg a -> m (Either SafeException a)
runSafeReqM Config cfg
cfg SafeReqM cfg a
c
    case Either SafeException a
res of
      Left SafeException
ex -> SafeException -> SafeReqM cfg a
h SafeException
ex
      Right{} -> ExceptT SafeException (ReaderT (Config cfg) IO) a -> SafeReqM cfg a
forall cfg a.
ExceptT SafeException (ReaderT (Config cfg) IO) a -> SafeReqM cfg a
SafeReqM (ExceptT SafeException (ReaderT (Config cfg) IO) a
 -> SafeReqM cfg a)
-> ExceptT SafeException (ReaderT (Config cfg) IO) a
-> SafeReqM cfg a
forall a b. (a -> b) -> a -> b
$ ReaderT (Config cfg) IO (Either SafeException a)
-> ExceptT SafeException (ReaderT (Config cfg) IO) a
forall e (m :: Type -> Type) a. m (Either e a) -> ExceptT e m a
ExceptT (ReaderT (Config cfg) IO (Either SafeException a)
 -> ExceptT SafeException (ReaderT (Config cfg) IO) a)
-> ReaderT (Config cfg) IO (Either SafeException a)
-> ExceptT SafeException (ReaderT (Config cfg) IO) a
forall a b. (a -> b) -> a -> b
$ Either SafeException a
-> ReaderT (Config cfg) IO (Either SafeException a)
forall (m :: Type -> Type) a. Monad m => a -> m a
return Either SafeException a
res


-- | Throw an Exception to the `SafeReqM` Monad.
throwUserException :: (MonadError SafeException m, Exception e) => e -> m a
throwUserException :: e -> m a
throwUserException = SafeException -> m a
forall e (m :: Type -> Type) a. MonadError e m => e -> m a
throwError (SafeException -> m a) -> (e -> SafeException) -> e -> m a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. e -> SafeException
forall e. (Typeable e, Exception e) => e -> SafeException
SafeUserException


-- | Safely run the request monad.
runSafeReqM ::
     MonadIO m
  => Config cfg                 -- ^ Config including 'HttpConfig' to use
  -> SafeReqM cfg a             -- ^ Computation to run
  -> m (Either SafeException a)
runSafeReqM :: Config cfg -> SafeReqM cfg a -> m (Either SafeException a)
runSafeReqM Config cfg
config (SafeReqM ExceptT SafeException (ReaderT (Config cfg) IO) a
m) = IO (Either SafeException a) -> m (Either SafeException a)
forall (m :: Type -> Type) a. MonadIO m => IO a -> m a
liftIO (ReaderT (Config cfg) IO (Either SafeException a)
-> Config cfg -> IO (Either SafeException a)
forall r (m :: Type -> Type) a. ReaderT r m a -> r -> m a
runReaderT (ExceptT SafeException (ReaderT (Config cfg) IO) a
-> ReaderT (Config cfg) IO (Either SafeException a)
forall e (m :: Type -> Type) a. ExceptT e m a -> m (Either e a)
runExceptT ExceptT SafeException (ReaderT (Config cfg) IO) a
m) Config cfg
config)