module Web.ATND
(
ATND,
ATNDT,
runATND,
runATNDT,
query,
query',
ATNDConfig(..),
defaultATNDConfig,
ATNDError
) where
import Web.ATND.Util
import Data.Text(Text, unpack, pack)
import qualified Data.Text as D (concat)
import Data.Typeable
import Data.Aeson (decode, Value(..), FromJSON(..), (.:))
import Control.Monad (mzero)
import Control.Monad.IO.Class (liftIO, MonadIO)
import qualified Data.ByteString.Char8 as B8
import qualified Data.ByteString.Lazy as BL
import Network.HTTP.Conduit (HttpException(..), setQueryString, tlsManagerSettings, parseUrl, newManager, httpLbs, Response(..), Request(..), Manager)
import Network.HTTP.Types.Header (ResponseHeaders)
import Control.Monad.Reader (runReaderT, ReaderT)
import Control.Monad.Logger
import Control.Monad.Trans.Control (MonadBaseControl)
import Control.Exception.Lifted as CEL
data ATNDConfig = ATNDConfig { atndManager :: Manager }
defaultATNDConfig :: MonadIO m => m ATNDConfig
defaultATNDConfig = do
man <- liftIO $ newManager tlsManagerSettings
return ATNDConfig { atndManager = man }
type ATNDT m a = (MonadIO m, MonadLogger m, MonadBaseControl IO m) => ReaderT ATNDConfig m a
runATNDT :: (MonadIO m, MonadLogger m, MonadBaseControl IO m) => ATNDConfig -> ATNDT m a -> m a
runATNDT config action =
runReaderT action config
type ATND a = ATNDT(LoggingT IO) a
runATND :: (MonadIO m) =>
(ATNDConfig -> ATNDT(LoggingT IO) a -> m a)
runATND config action =
liftIO $ runStderrLoggingT $ runATNDT config action
endpointUrl :: Section -> Text
endpointUrl a = D.concat ["https://api.atnd.org/", fromSection a]
query :: (FromJSON x) => Section
-> [(B8.ByteString, Maybe B8.ByteString)]
-> ATNDT m x
query section queryList = do
query' section $ filterQuery queryList
query' :: (FromJSON x) => Section -> [(B8.ByteString, Maybe B8.ByteString)] -> ATNDT m x
query' section queryList = do
config <- defaultATNDConfig
initReq <- liftIO $ parseUrl $ unpack $ endpointUrl section
let req = setQueryString queryList $ initReq
$(logDebug) $ pack . show $ queryString req
response <- CEL.catch (httpLbs req $ atndManager config) catchHttpException
$(logDebug) $ pack . show $ responseBody response
case decode $ responseBody response of
Just eventResults -> return eventResults
Nothing -> throwIO $ OtherATNDError (1) "Parse Error: Could not parse result JSON from ATND"
where
catchHttpException :: HttpException -> ATNDT m a
catchHttpException e@(StatusCodeException _ headers _) = do
$(logDebug) $ pack . show $ lookup "X-Response-Boby-Start" headers
maybe (throwIO e) throwIO (decodeError headers)
catchHttpException e = throwIO e
decodeError :: ResponseHeaders -> Maybe ATNDError
decodeError headers = BL.fromStrict `fmap` lookup "X-Response-Body-Start" headers >>= decode
data ATNDError = NotFoundError Int Text
| OtherATNDError Int Text
deriving (Typeable, Show, Eq)
instance Exception ATNDError
instance FromJSON ATNDError where
parseJSON (Object v) = do
status <- v .: "status"
message <- v .: "error"
return $ (errConstructor status) ((read $ unpack status)::Int) message
where
errConstructor status = case (status :: Text) of
"404" -> NotFoundError
_ -> OtherATNDError
parseJSON _ = mzero