Handling HTTP errors in req

The req package provides a MonadHttp class that has a handleHttpException method. The library documentation has this to say in regards to HTTP error handling:

some people prefer throwing exceptions, while others are concerned with purity. Just define handleHttpException accordingly when making your monad instance of MonadHttp and it will play together seamlessly.

Define our own monad

We write our Monad by creating a type that wrapps the various Monad Transformers types (including ReaderT of Three Layer Haskell Cake), which gets us the appropriate monad instances for free.

newtype GitHubT a = GitHubT
  { unGitHubT ::
      ReaderT GitHubTokenResponse (ExceptT GitHubApiError Req) a
  }
  deriving
    ( Applicative,
      Functor,
      Monad,
      MonadIO,
      MonadReader GitHubTokenResponse,
      MonadError GitHubApiError
    )

instance MonadHttp GitHubT where
  handleHttpException e =
    print e >> throwAppError e
    where
      throwAppError = \case
        VanillaHttpException err ->
          throwError $ GitHubApiError_HttpException $ show err
        JsonHttpException err ->
          throwError $ GitHubApiError_BadResponse err

runGitHub :: MonadIO m => GitHubTokenResponse -> GitHubT a -> m (Either GitHubApiError a)
runGitHub tok =
  liftIO
    . runReq defaultHttpConfig
    . runExceptT
    . usingReaderT tok
    . unGitHubT

We can then define domain-specific operations on this monad, such as to get the OAuth token as req’s Option type:

authOption :: GitHubT (Option 'Https)
authOption =
  oAuth2Token . encodeUtf8 . _githubTokenResponse_accessToken <$> ask

Exercise for the reader