-- | Helpers for dealing with HTTP requests.
module Strive.Internal.HTTP
  ( delete
  , get
  , post
  , put
  , buildRequest
  , performRequest
  , handleResponse
  , decodeValue
  ) where

import Data.Aeson (FromJSON, eitherDecode)
import Data.ByteString.Char8 (unpack)
import Data.ByteString.Lazy (ByteString)
import Network.HTTP.Client (Request, Response, method, parseRequest,
                            responseBody)
import Network.HTTP.Types (Method, Query, QueryLike, methodDelete, methodGet,
                           methodPost, methodPut, renderQuery, toQuery)
import Strive.Aliases (Result)
import Strive.Client (Client (client_accessToken, client_requester))

-- | Perform an HTTP DELETE request.
delete :: (QueryLike q, FromJSON j) => Client -> String -> q -> IO (Result j)
delete :: Client -> String -> q -> IO (Result j)
delete = Method -> Client -> String -> q -> IO (Result j)
forall q j.
(QueryLike q, FromJSON j) =>
Method -> Client -> String -> q -> IO (Result j)
http Method
methodDelete

-- | Perform an HTTP GET request.
get :: (QueryLike q, FromJSON j) => Client -> String -> q -> IO (Result j)
get :: Client -> String -> q -> IO (Result j)
get = Method -> Client -> String -> q -> IO (Result j)
forall q j.
(QueryLike q, FromJSON j) =>
Method -> Client -> String -> q -> IO (Result j)
http Method
methodGet

-- | Perform an HTTP POST request.
post :: (QueryLike q, FromJSON j) => Client -> String -> q -> IO (Result j)
post :: Client -> String -> q -> IO (Result j)
post = Method -> Client -> String -> q -> IO (Result j)
forall q j.
(QueryLike q, FromJSON j) =>
Method -> Client -> String -> q -> IO (Result j)
http Method
methodPost

-- | Perform an HTTP PUT request.
put :: (QueryLike q, FromJSON j) => Client -> String -> q -> IO (Result j)
put :: Client -> String -> q -> IO (Result j)
put = Method -> Client -> String -> q -> IO (Result j)
forall q j.
(QueryLike q, FromJSON j) =>
Method -> Client -> String -> q -> IO (Result j)
http Method
methodPut

-- | Perform an HTTP request.
http :: (QueryLike q, FromJSON j) => Method -> Client -> String -> q -> IO (Result j)
http :: Method -> Client -> String -> q -> IO (Result j)
http Method
httpMethod Client
client String
resource q
query = do
  Request
request <- Method -> Client -> String -> q -> IO Request
forall q.
QueryLike q =>
Method -> Client -> String -> q -> IO Request
buildRequest Method
httpMethod Client
client String
resource q
query
  Response ByteString
response <- Client -> Request -> IO (Response ByteString)
performRequest Client
client Request
request
  Result j -> IO (Result j)
forall (m :: * -> *) a. Monad m => a -> m a
return (Response ByteString -> Result j
forall j. FromJSON j => Response ByteString -> Result j
handleResponse Response ByteString
response)

-- | Build a request.
buildRequest :: QueryLike q => Method -> Client -> String -> q -> IO Request
buildRequest :: Method -> Client -> String -> q -> IO Request
buildRequest Method
httpMethod Client
client String
resource q
query = do
  Request
request <- String -> IO Request
forall (m :: * -> *). MonadThrow m => String -> m Request
parseRequest (Client -> String -> q -> String
forall q. QueryLike q => Client -> String -> q -> String
buildUrl Client
client String
resource q
query)
  Request -> IO Request
forall (m :: * -> *) a. Monad m => a -> m a
return Request
request
    { method :: Method
method = Method
httpMethod
    }

-- | Build a URL.
buildUrl :: QueryLike q => Client -> String -> q -> String
buildUrl :: Client -> String -> q -> String
buildUrl Client
client String
resource q
query = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
  [ String
"https://www.strava.com/"
  , String
resource
  , Method -> String
unpack (Bool -> Query -> Method
renderQuery Bool
True (Client -> Query
buildQuery Client
client Query -> Query -> Query
forall a. [a] -> [a] -> [a]
++ q -> Query
forall a. QueryLike a => a -> Query
toQuery q
query))
  ]

-- | Build a query.
buildQuery :: Client -> Query
buildQuery :: Client -> Query
buildQuery Client
client = [(String, String)] -> Query
forall a. QueryLike a => a -> Query
toQuery
  [ (String
"access_token", Client -> String
client_accessToken Client
client)
  ]

-- | Actually perform an HTTP request.
performRequest :: Client -> Request -> IO (Response ByteString)
performRequest :: Client -> Request -> IO (Response ByteString)
performRequest Client
client Request
request = (Client -> Request -> IO (Response ByteString)
client_requester Client
client) Request
request

-- | Handle decoding a potentially failed response.
handleResponse :: FromJSON j => Response ByteString -> Result j
handleResponse :: Response ByteString -> Result j
handleResponse Response ByteString
response = case Response ByteString -> Either String j
forall j. FromJSON j => Response ByteString -> Either String j
decodeValue Response ByteString
response of
  Left String
message -> (Response ByteString, String) -> Result j
forall a b. a -> Either a b
Left (Response ByteString
response, String
message)
  Right j
value -> j -> Result j
forall a b. b -> Either a b
Right j
value

-- | Decode a response body as JSON.
decodeValue :: FromJSON j => Response ByteString -> Either String j
decodeValue :: Response ByteString -> Either String j
decodeValue Response ByteString
response = ByteString -> Either String j
forall a. FromJSON a => ByteString -> Either String a
eitherDecode (Response ByteString -> ByteString
forall body. Response body -> body
responseBody Response ByteString
response)