{-# LANGUAGE CPP #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
module GitHub.REST.Monad
( MonadGitHubREST(..)
, GitHubT
, GitHubState(..)
, runGitHubT
) where
#if !MIN_VERSION_base(4,13,0)
import Control.Monad.Fail (MonadFail)
#endif
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.IO.Unlift (MonadUnliftIO(..))
import Control.Monad.Reader (ReaderT, ask, runReaderT)
import Control.Monad.Trans (MonadTrans)
import Data.Aeson (eitherDecode, encode)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as ByteStringL
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid ((<>))
#endif
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import Network.HTTP.Client
( Manager
, Request(..)
, RequestBody(..)
, Response(..)
, httpLbs
, newManager
, parseRequest_
, throwErrorStatusCodes
)
import Network.HTTP.Client.TLS (tlsManagerSettings)
import Network.HTTP.Types (hAccept, hAuthorization, hUserAgent)
import GitHub.REST.Auth (Token, fromToken)
import GitHub.REST.Endpoint (GHEndpoint(..), endpointPath, renderMethod)
import GitHub.REST.KeyValue (kvToValue)
import GitHub.REST.Monad.Class
import GitHub.REST.PageLinks (parsePageLinks)
data GitHubState = GitHubState
{ token :: Maybe Token
, userAgent :: ByteString
, apiVersion :: ByteString
}
newtype GitHubT m a = GitHubT
{ unGitHubT :: ReaderT (Manager, GitHubState) m a
}
deriving
( Functor
, Applicative
, Monad
, MonadFail
, MonadIO
, MonadTrans
)
instance MonadUnliftIO m => MonadUnliftIO (GitHubT m) where
withRunInIO inner = GitHubT $
withRunInIO $ \run ->
inner (run . unGitHubT)
instance MonadIO m => MonadGitHubREST (GitHubT m) where
queryGitHubPage' ghEndpoint = do
(manager, GitHubState{..}) <- GitHubT ask
let request = (parseRequest_ $ Text.unpack $ ghUrl <> endpointPath ghEndpoint)
{ method = renderMethod ghEndpoint
, requestHeaders =
[ (hAccept, "application/vnd.github." <> apiVersion <> "+json")
, (hUserAgent, userAgent)
] ++ maybe [] ((:[]) . (hAuthorization,) . fromToken) token
, requestBody = RequestBodyLBS $ encode $ kvToValue $ ghData ghEndpoint
, checkResponse = throwErrorStatusCodes
}
response <- liftIO $ httpLbs request manager
let body = responseBody response
nonEmptyBody = if ByteStringL.null body then encode () else body
pageLinks = maybe mempty parsePageLinks . lookupHeader "Link" $ response
return $ case eitherDecode nonEmptyBody of
Right payload -> Right (payload, pageLinks)
Left e -> Left (Text.pack e, Text.decodeUtf8 $ ByteStringL.toStrict body)
where
ghUrl = "https://api.github.com"
lookupHeader headerName = fmap Text.decodeUtf8 . lookup headerName . responseHeaders
runGitHubT :: MonadIO m => GitHubState -> GitHubT m a -> m a
runGitHubT state action = do
manager <- liftIO $ newManager tlsManagerSettings
(`runReaderT` (manager, state)) . unGitHubT $ action