graphql-client-1.0.0: src/Data/GraphQL/Monad.hs
{-|
Module : Data.GraphQL.Monad
Maintainer : Brandon Chinn <brandon@leapyear.io>
Stability : experimental
Portability : portable
Defines the 'GraphQLQueryT' monad transformer, which implements
'MonadGraphQLQuery' to allow querying GraphQL APIs.
-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RecordWildCards #-}
module Data.GraphQL.Monad
( module Data.GraphQL.Monad.Class
, GraphQLQueryT
, runGraphQLQueryT
, GraphQLSettings(..)
, defaultGraphQLSettings
) where
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.IO.Unlift (MonadUnliftIO(..))
import Control.Monad.Reader (MonadReader, ReaderT, ask, runReaderT)
import Control.Monad.Trans.Class (MonadTrans)
import Data.Aeson ((.=))
import qualified Data.Aeson as Aeson
import Network.HTTP.Client
( Manager
, ManagerSettings
, Request(..)
, RequestBody(..)
, httpLbs
, newManager
, parseUrlThrow
, responseBody
)
import Network.HTTP.Client.TLS (tlsManagerSettings)
import Network.HTTP.Types (hContentType)
import Data.GraphQL.Monad.Class
import Data.GraphQL.Query (GraphQLQuery(..))
-- | The state for running GraphQLQueryT.
data QueryState = QueryState
{ manager :: Manager
, baseReq :: Request
}
-- | The monad transformer type that should be used to run GraphQL queries.
--
-- @
-- newtype MyMonad a = MyMonad { unMyMonad :: GraphQLQueryT IO a }
--
-- runMyMonad :: MyMonad a -> IO a
-- runMyMonad = runGraphQLQueryT graphQLSettings . unMyMonad
-- where
-- graphQLSettings = defaultGraphQLSettings
-- { url = "https://api.github.com/graphql"
-- , modifyReq = \\req -> req
-- { requestHeaders =
-- (hAuthorization, "bearer my_github_token") : requestHeaders req
-- }
-- }
-- @
newtype GraphQLQueryT m a = GraphQLQueryT { unGraphQLQueryT :: ReaderT QueryState m a }
deriving
( Functor
, Applicative
, Monad
, MonadIO
, MonadReader QueryState
, MonadTrans
)
instance MonadUnliftIO m => MonadUnliftIO (GraphQLQueryT m) where
withRunInIO inner = GraphQLQueryT $ withRunInIO $ \run -> inner (run . unGraphQLQueryT)
instance MonadIO m => MonadGraphQLQuery (GraphQLQueryT m) where
runQuerySafe query = do
QueryState{..} <- ask
let request = baseReq
{ requestBody = RequestBodyLBS $ Aeson.encode $ Aeson.object
[ "query" .= getQueryText query
, "variables" .= getArgs query
]
}
liftIO $ either fail return . Aeson.eitherDecode . responseBody =<< httpLbs request manager
-- | Run a GraphQLQueryT stack.
runGraphQLQueryT :: MonadIO m => GraphQLSettings -> GraphQLQueryT m a -> m a
runGraphQLQueryT GraphQLSettings{..} m = do
state <- liftIO $ do
manager <- newManager managerSettings
baseReq <- modifyReq . modifyReq' <$> parseUrlThrow url
return QueryState{..}
(`runReaderT` state)
. unGraphQLQueryT
$ m
where
modifyReq' req = req
{ method = "POST"
, requestHeaders = (hContentType, "application/json") : requestHeaders req
}
-- | The settings for running GraphQLQueryT.
data GraphQLSettings = GraphQLSettings
{ managerSettings :: ManagerSettings
-- ^ Uses TLS by default
, url :: String
, modifyReq :: Request -> Request
}
-- | Default query settings.
defaultGraphQLSettings :: GraphQLSettings
defaultGraphQLSettings = GraphQLSettings
{ managerSettings = tlsManagerSettings
, url = error "No URL is provided"
, modifyReq = id
}