packages feed

libstackexchange-0.1.0.0: src/Network/StackExchange/Response.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE UnicodeSyntax #-}
{-# LANGUAGE ViewPatterns #-}
-- | Get response from Request and retrieve data from it
module Network.StackExchange.Response
  ( -- * Schedule request
    SEException(..), askSE, render
    -- * Generalized combinator
  , aeson
    -- * Convenience functions
  , field, fields
  ) where

import Control.Applicative ((<$>))
import Control.Exception (Exception, throwIO)
import Control.Category ((>>>))
import Control.Monad ((<=<))
import Data.Monoid (Monoid(..))
import Data.Typeable (Typeable)

import           Data.ByteString.Lazy (ByteString, toStrict)
import           Control.Lens
import           Data.Aeson (FromJSON, Value, (.:), parseJSON)
import qualified Data.Aeson.Types as A
import           Data.Default (Default(..))
import qualified Data.Map as M
import           Data.Text (Text)
import qualified Data.Text.Lazy as T
import           Data.Text.Lazy.Encoding (encodeUtf8)
import qualified Network.HTTP.Conduit as C

import Network.StackExchange.Request


-- | StackExchange invalid response exception
data SEException = SEException
  { _data ∷ ByteString -- ^ Recieved data
  , _error ∷ String -- ^ Parser/libstackexchange errors
  } deriving (Show, Typeable)


instance Exception SEException


-- | Send Request and parse response
askSE ∷ Request Ready n r → IO r
askSE (mappend def → q@Request {_method, _parse}) = do
  r ← C.withManager $ \m → C.parseUrl (render q) >>= \url →
    C.responseBody <$> C.httpLbs (url {C.method = toStrict $ encodeUtf8 _method}) m
  case _parse of
    Just f → return $ f r
    Nothing → throwIO $
      SEException r "libstackexchange.askSE: no parsing function registered"


-- | Render Request as string for networking
render ∷ Request a n r → String
render Request {_host, _path, _query} = T.unpack $ mconcat [_host, "/", _path, "?", argie _query]
 where
  argie = T.intercalate "&" . M.foldrWithKey (\k v m → T.concat [k, "=", v] : m) mempty


-- | Generalized combinator, useful if full power of Aeson is needed
aeson ∷ Monad m ⇒ (a → A.Parser b) → Action m a b
aeson p = act $ A.parse p >>> \case
  A.Success v → return v
  A.Error g → fail g
{-# INLINE aeson #-}


-- | Select specific field in JSON
field ∷ (Monad m, FromJSON a) ⇒ Text → Action m (SE x) a
field xs = aeson ((.: xs) <=< parseJSON . unSE)
{-# INLINE field #-}


-- | Select specific fields of an array in JSON
fields ∷ (Monad m, FromJSON a) ⇒ Text → Action m Value [a]
fields xs = aeson $ mapM (.: xs) <=< parseJSON
{-# INLINE fields #-}