rfc-http-client-0.0.0.1: src/RFC/HTTP/Client.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE UndecidableInstances #-}
module RFC.HTTP.Client
( withAPISession
, HasAPIClient(..)
, HasHttpManager(..)
, BadStatusException
, apiGet
, module Network.Wreq.Session
, module Network.HTTP.Types.Status
, geocodeGet
, GoogleGeocodeResult(..)
) where
import Control.Lens
import Data.Aeson as JSON
import Data.Aeson.Types as JSON
import qualified Data.Scientific as Sci
import Data.Vector ( (!?) )
import Network.HTTP.Client ( Manager, ManagerSettings, newManager )
import Network.HTTP.Client.TLS ( tlsManagerSettings )
import Network.HTTP.Types.Status hiding ( statusCode, statusMessage )
import Network.URI as URI
import Network.Wreq.Lens
import Network.Wreq.Session hiding ( withAPISession )
import RFC.Prelude
import Servant.Server
data GoogleGeocodeResult = GoogleGeocodeResult
{ ggrLat :: Float
, ggrLng :: Float
, ggrAddr :: StrictText
} deriving (Eq,Ord,Show,Generic,Typeable)
instance FromJSON GoogleGeocodeResult where
parseJSON = JSON.withObject "GoogleGeocodeResult" $ \v -> do
resultsAry <- v .: "results"
result <- JSON.withArray "results[0]" extractHead resultsAry
withObject "results[0] content" parseResult result
where
extractHead :: JSON.Array -> JSON.Parser JSON.Value
extractHead vec =
maybe
(fail "Empty results array returned")
return
(vec !? 0)
parseResult :: JSON.Object -> JSON.Parser GoogleGeocodeResult
parseResult obj = do
addr <- obj .: "formatted_address" >>= withText "results[0].formatted_address" return
geometry <- obj .: "geometry" >>= withObject "results[0].geometry" return
location <- geometry .: "location" >>= withObject "results[0].geometry.location" return
lat <- location .: "lat" >>= withScientific "results[0].geometry.location.lat" (return . Sci.toRealFloat)
lng <- location .: "lng" >>= withScientific "results[0].geometry.location.lng" (return . Sci.toRealFloat)
return GoogleGeocodeResult { ggrLat = lat, ggrLng = lng, ggrAddr = addr }
rfcManagerSettings :: ManagerSettings
rfcManagerSettings = tlsManagerSettings
createRfcManager :: (MonadIO m) => m Manager
createRfcManager = liftIO $ newManager rfcManagerSettings
withAPISession :: (MonadIO m) => (Session -> m a) -> m a
withAPISession = (>>=) $ liftIO (newSessionControl Nothing rfcManagerSettings)
newtype BadStatusException = BadStatusException (Status,URI)
deriving (Show,Eq,Ord,Generic,Typeable)
instance Exception BadStatusException
apiExecute :: (HasAPIClient m, MonadUnliftIO m) =>
URI -> (Session -> String -> IO (Response LazyByteString)) -> (LazyByteString -> m a) -> m a
apiExecute rawUrl action converter = webExecute rawUrl action >>= converter
webExecute :: (HasAPIClient m, MonadUnliftIO m) =>
URI -> (Session -> String -> IO (Response LazyByteString)) -> m LazyByteString
webExecute rawUrl action = do
session <- getAPIClient
response <- liftIO $ action session url
let status = response ^. responseStatus
case status ^. statusCode of
200 -> return $ response ^. responseBody
_ -> throwIO $ badResponseStatus status
where
url = show rawUrl
badResponseStatus status = BadStatusException (status, rawUrl)
apiGet :: (HasAPIClient m, FromJSON a, MonadUnliftIO m, Exception e) => URI -> (e -> m a) -> m a
apiGet url onError =
handle onError $ apiExecute url get decodeOrDie
geocodeGet ::
(HasAPIClient m, MonadUnliftIO m, Exception e) =>
StrictText -> (e -> m GoogleGeocodeResult) -> m GoogleGeocodeResult
geocodeGet addr onError = handle onError $
case URI.parseURI "https://maps.googleapis.com/maps/api/geocode/json" of
Nothing -> throwIO err500 { errBody = "Cannot parse the geocoding URI" }
Just uri -> do
let address = URI.escapeURIString URI.isUnescapedInURIComponent (fromText addr)
let apiKey = "AIzaSyDL1yQ2fFJExMM6CcmsgpklR2Q4c-O4kks"
let uriWithQuery = uri { uriQuery = "address=" <> address <> "&" <> "key=" <> apiKey <> "&" <> uriQuery uri }
apiGet uriWithQuery onError
class HasAPIClient m where
getAPIClient :: m Session
class HasHttpManager m where
getHttpManager :: m Manager
instance (MonadIO m) => HasHttpManager m where
getHttpManager = liftIO createRfcManager