module Asana.Api.Request
( AsanaAccessKey(..)
, HasAsanaAccessKey(..)
, Single(..)
, Page(..)
, NextPage(..)
, ApiData(..)
, getAll
, getAllParams
, getSingle
, put
, post
, maxRequests
) where
import Asana.Api.Prelude
import Data.Aeson
import Data.Aeson.Casing (aesonPrefix, snakeCase)
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Encoding.Error as T
import Network.HTTP.Simple
( JSONException(JSONConversionException, JSONParseException)
, Request
, Response
, addRequestHeader
, getResponseBody
, getResponseHeader
, getResponseStatusCode
, httpJSON
, parseRequest_
, setRequestBodyJSON
, setRequestMethod
)
import UnliftIO.Concurrent (threadDelay)
newtype AsanaAccessKey = AsanaAccessKey
{ AsanaAccessKey -> Text
unAsanaAccessKey :: Text
}
class HasAsanaAccessKey env where
asanaAccessKeyL :: Lens' env AsanaAccessKey
instance HasAsanaAccessKey AsanaAccessKey where
asanaAccessKeyL :: Lens' AsanaAccessKey AsanaAccessKey
asanaAccessKeyL = forall a. a -> a
id
maxRequests :: Int
maxRequests :: Int
maxRequests = Int
50
newtype Single a = Single
{ forall a. Single a -> a
sData :: a
}
deriving newtype (Single a -> Single a -> Bool
forall a. Eq a => Single a -> Single a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Single a -> Single a -> Bool
$c/= :: forall a. Eq a => Single a -> Single a -> Bool
== :: Single a -> Single a -> Bool
$c== :: forall a. Eq a => Single a -> Single a -> Bool
Eq, Int -> Single a -> ShowS
[Single a] -> ShowS
Single a -> String
forall a. Show a => Int -> Single a -> ShowS
forall a. Show a => [Single a] -> ShowS
forall a. Show a => Single a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Single a] -> ShowS
$cshowList :: forall a. Show a => [Single a] -> ShowS
show :: Single a -> String
$cshow :: forall a. Show a => Single a -> String
showsPrec :: Int -> Single a -> ShowS
$cshowsPrec :: forall a. Show a => Int -> Single a -> ShowS
Show)
deriving stock forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall a x. Rep (Single a) x -> Single a
forall a x. Single a -> Rep (Single a) x
$cto :: forall a x. Rep (Single a) x -> Single a
$cfrom :: forall a x. Single a -> Rep (Single a) x
Generic
instance FromJSON a => FromJSON (Single a) where
parseJSON :: Value -> Parser (Single a)
parseJSON = forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
genericParseJSON forall a b. (a -> b) -> a -> b
$ ShowS -> Options
aesonPrefix ShowS
snakeCase
data Page a = Page
{ forall a. Page a -> [a]
pData :: [a]
, forall a. Page a -> Maybe NextPage
pNextPage :: Maybe NextPage
}
deriving stock (Page a -> Page a -> Bool
forall a. Eq a => Page a -> Page a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Page a -> Page a -> Bool
$c/= :: forall a. Eq a => Page a -> Page a -> Bool
== :: Page a -> Page a -> Bool
$c== :: forall a. Eq a => Page a -> Page a -> Bool
Eq, forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall a x. Rep (Page a) x -> Page a
forall a x. Page a -> Rep (Page a) x
$cto :: forall a x. Rep (Page a) x -> Page a
$cfrom :: forall a x. Page a -> Rep (Page a) x
Generic, Int -> Page a -> ShowS
forall a. Show a => Int -> Page a -> ShowS
forall a. Show a => [Page a] -> ShowS
forall a. Show a => Page a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Page a] -> ShowS
$cshowList :: forall a. Show a => [Page a] -> ShowS
show :: Page a -> String
$cshow :: forall a. Show a => Page a -> String
showsPrec :: Int -> Page a -> ShowS
$cshowsPrec :: forall a. Show a => Int -> Page a -> ShowS
Show)
instance FromJSON a => FromJSON (Page a) where
parseJSON :: Value -> Parser (Page a)
parseJSON = forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
genericParseJSON forall a b. (a -> b) -> a -> b
$ ShowS -> Options
aesonPrefix ShowS
snakeCase
data NextPage = NextPage
{ NextPage -> Text
npOffset :: Text
, NextPage -> Text
npPath :: Text
, NextPage -> Text
npUri :: Text
}
deriving stock (NextPage -> NextPage -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: NextPage -> NextPage -> Bool
$c/= :: NextPage -> NextPage -> Bool
== :: NextPage -> NextPage -> Bool
$c== :: NextPage -> NextPage -> Bool
Eq, forall x. Rep NextPage x -> NextPage
forall x. NextPage -> Rep NextPage x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep NextPage x -> NextPage
$cfrom :: forall x. NextPage -> Rep NextPage x
Generic, Int -> NextPage -> ShowS
[NextPage] -> ShowS
NextPage -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [NextPage] -> ShowS
$cshowList :: [NextPage] -> ShowS
show :: NextPage -> String
$cshow :: NextPage -> String
showsPrec :: Int -> NextPage -> ShowS
$cshowsPrec :: Int -> NextPage -> ShowS
Show)
instance FromJSON NextPage where
parseJSON :: Value -> Parser NextPage
parseJSON = forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
genericParseJSON forall a b. (a -> b) -> a -> b
$ ShowS -> Options
aesonPrefix ShowS
snakeCase
newtype ApiData a = ApiData
{ forall a. ApiData a -> a
adData :: a
}
deriving newtype (Int -> ApiData a -> ShowS
[ApiData a] -> ShowS
ApiData a -> String
forall a. Show a => Int -> ApiData a -> ShowS
forall a. Show a => [ApiData a] -> ShowS
forall a. Show a => ApiData a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ApiData a] -> ShowS
$cshowList :: forall a. Show a => [ApiData a] -> ShowS
show :: ApiData a -> String
$cshow :: forall a. Show a => ApiData a -> String
showsPrec :: Int -> ApiData a -> ShowS
$cshowsPrec :: forall a. Show a => Int -> ApiData a -> ShowS
Show, ApiData a -> ApiData a -> Bool
forall a. Eq a => ApiData a -> ApiData a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ApiData a -> ApiData a -> Bool
$c/= :: forall a. Eq a => ApiData a -> ApiData a -> Bool
== :: ApiData a -> ApiData a -> Bool
$c== :: forall a. Eq a => ApiData a -> ApiData a -> Bool
Eq)
deriving stock forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall a x. Rep (ApiData a) x -> ApiData a
forall a x. ApiData a -> Rep (ApiData a) x
$cto :: forall a x. Rep (ApiData a) x -> ApiData a
$cfrom :: forall a x. ApiData a -> Rep (ApiData a) x
Generic
instance FromJSON a => FromJSON (ApiData a) where
parseJSON :: Value -> Parser (ApiData a)
parseJSON = forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
genericParseJSON forall a b. (a -> b) -> a -> b
$ ShowS -> Options
aesonPrefix ShowS
snakeCase
instance ToJSON a => ToJSON (ApiData a) where
toJSON :: ApiData a -> Value
toJSON = forall a.
(Generic a, GToJSON' Value Zero (Rep a)) =>
Options -> a -> Value
genericToJSON forall a b. (a -> b) -> a -> b
$ ShowS -> Options
aesonPrefix ShowS
snakeCase
toEncoding :: ApiData a -> Encoding
toEncoding = forall a.
(Generic a, GToJSON' Encoding Zero (Rep a)) =>
Options -> a -> Encoding
genericToEncoding forall a b. (a -> b) -> a -> b
$ ShowS -> Options
aesonPrefix ShowS
snakeCase
getAll
:: ( MonadUnliftIO m
, MonadLogger m
, MonadReader env m
, HasAsanaAccessKey env
, FromJSON a
)
=> String
-> m [a]
getAll :: forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
HasAsanaAccessKey env, FromJSON a) =>
String -> m [a]
getAll String
path = forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
HasAsanaAccessKey env, FromJSON a) =>
String -> [(String, String)] -> m [a]
getAllParams String
path []
getAllParams
:: ( MonadUnliftIO m
, MonadLogger m
, MonadReader env m
, HasAsanaAccessKey env
, FromJSON a
)
=> String
-> [(String, String)]
-> m [a]
getAllParams :: forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
HasAsanaAccessKey env, FromJSON a) =>
String -> [(String, String)] -> m [a]
getAllParams String
path [(String, String)]
params = Maybe String -> m [a]
go forall a. Maybe a
Nothing
where
go :: Maybe String -> m [a]
go Maybe String
mOffset = do
Page [a]
d Maybe NextPage
mNextPage <- forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
HasAsanaAccessKey env, FromJSON a) =>
String -> [(String, String)] -> Int -> Maybe String -> m a
get String
path [(String, String)]
params Int
50 Maybe String
mOffset
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall (f :: * -> *) a. Applicative f => a -> f a
pure [a]
d) (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ([a]
d forall a. [a] -> [a] -> [a]
++) forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe String -> m [a]
go forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. a -> Maybe a
Just forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack forall b c a. (b -> c) -> (a -> b) -> a -> c
. NextPage -> Text
npOffset) Maybe NextPage
mNextPage
getSingle
:: ( MonadUnliftIO m
, MonadLogger m
, MonadReader env m
, HasAsanaAccessKey env
, FromJSON a
)
=> String
-> m a
getSingle :: forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
HasAsanaAccessKey env, FromJSON a) =>
String -> m a
getSingle String
path = forall a. Single a -> a
sData forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
HasAsanaAccessKey env, FromJSON a) =>
String -> [(String, String)] -> Int -> Maybe String -> m a
get String
path [] Int
1 forall a. Maybe a
Nothing
get
:: ( MonadUnliftIO m
, MonadLogger m
, MonadReader env m
, HasAsanaAccessKey env
, FromJSON a
)
=> String
-> [(String, String)]
-> Int
-> Maybe String
-> m a
get :: forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
HasAsanaAccessKey env, FromJSON a) =>
String -> [(String, String)] -> Int -> Maybe String -> m a
get String
path [(String, String)]
params Int
limit Maybe String
mOffset = do
AsanaAccessKey Text
key <- forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view forall env. HasAsanaAccessKey env => Lens' env AsanaAccessKey
asanaAccessKeyL
let
request :: Request
request =
String -> Request
parseRequest_
forall a b. (a -> b) -> a -> b
$ String
"https://app.asana.com/api/1.0"
forall a. Semigroup a => a -> a -> a
<> String
path
forall a. Semigroup a => a -> a -> a
<> String
"?limit="
forall a. Semigroup a => a -> a -> a
<> forall a. Show a => a -> String
show Int
limit
forall a. Semigroup a => a -> a -> a
<> forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"" (String
"&offset=" forall a. Semigroup a => a -> a -> a
<>) Maybe String
mOffset
forall a. Semigroup a => a -> a -> a
<> forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\(String
k, String
v) -> String
"&" forall a. Semigroup a => a -> a -> a
<> String
k forall a. Semigroup a => a -> a -> a
<> String
"=" forall a. Semigroup a => a -> a -> a
<> String
v) [(String, String)]
params
Response a
response <- forall a (m :: * -> *).
(MonadUnliftIO m, MonadLogger m) =>
Int -> m (Response a) -> m (Response a)
retry Int
50 forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
Request -> m (Response a)
httpJSON (Text -> Request -> Request
addAuthorization Text
key Request
request)
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Int
300 forall a. Ord a => a -> a -> Bool
<= forall a. Response a -> Int
getResponseStatusCode Response a
response)
forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *). MonadLogger m => Text -> Text -> m ()
logWarnNS Text
"Asana"
forall a b. (a -> b) -> a -> b
$ Text
"GET failed, status: "
forall a. Semigroup a => a -> a -> a
<> String -> Text
pack (forall a. Show a => a -> String
show forall a b. (a -> b) -> a -> b
$ forall a. Response a -> Int
getResponseStatusCode Response a
response)
forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall a. Response a -> a
getResponseBody Response a
response
put
:: ( MonadUnliftIO m
, MonadLogger m
, MonadReader env m
, HasAsanaAccessKey env
, ToJSON a
)
=> String
-> a
-> m Value
put :: forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
HasAsanaAccessKey env, ToJSON a) =>
String -> a -> m Value
put = forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
HasAsanaAccessKey env, ToJSON a) =>
ByteString -> String -> a -> m Value
httpAction ByteString
"PUT"
post
:: ( MonadUnliftIO m
, MonadLogger m
, MonadReader env m
, HasAsanaAccessKey env
, ToJSON a
)
=> String
-> a
-> m Value
post :: forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
HasAsanaAccessKey env, ToJSON a) =>
String -> a -> m Value
post = forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
HasAsanaAccessKey env, ToJSON a) =>
ByteString -> String -> a -> m Value
httpAction ByteString
"POST"
httpAction
:: ( MonadUnliftIO m
, MonadLogger m
, MonadReader env m
, HasAsanaAccessKey env
, ToJSON a
)
=> ByteString
-> String
-> a
-> m Value
httpAction :: forall (m :: * -> *) env a.
(MonadUnliftIO m, MonadLogger m, MonadReader env m,
HasAsanaAccessKey env, ToJSON a) =>
ByteString -> String -> a -> m Value
httpAction ByteString
verb String
path a
payload = do
AsanaAccessKey Text
key <- forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view forall env. HasAsanaAccessKey env => Lens' env AsanaAccessKey
asanaAccessKeyL
let request :: Request
request = String -> Request
parseRequest_ forall a b. (a -> b) -> a -> b
$ String
"https://app.asana.com/api/1.0" forall a. Semigroup a => a -> a -> a
<> String
path
Response Value
response <- forall a (m :: * -> *).
(MonadUnliftIO m, MonadLogger m) =>
Int -> m (Response a) -> m (Response a)
retry Int
10 forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a.
(MonadIO m, FromJSON a) =>
Request -> m (Response a)
httpJSON
(ByteString -> Request -> Request
setRequestMethod ByteString
verb forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. ToJSON a => a -> Request -> Request
setRequestBodyJSON a
payload forall a b. (a -> b) -> a -> b
$ Text -> Request -> Request
addAuthorization
Text
key
Request
request
)
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Int
300 forall a. Ord a => a -> a -> Bool
<= forall a. Response a -> Int
getResponseStatusCode Response Value
response) forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *). MonadLogger m => Text -> Text -> m ()
logWarnNS Text
"Asana" forall a b. (a -> b) -> a -> b
$ forall a. Monoid a => [a] -> a
mconcat
[ Text
"Request failed"
, Text
"\n method: " forall a. Semigroup a => a -> a -> a
<> ByteString -> Text
T.decodeUtf8 ByteString
verb
, Text
"\n status: " forall a. Semigroup a => a -> a -> a
<> String -> Text
pack (forall a. Show a => a -> String
show forall a b. (a -> b) -> a -> b
$ forall a. Response a -> Int
getResponseStatusCode Response Value
response)
, Text
"\n body : " forall a. Semigroup a => a -> a -> a
<> ByteString -> Text
T.decodeUtf8
(ByteString -> ByteString
BSL.toStrict forall a b. (a -> b) -> a -> b
$ forall a. ToJSON a => a -> ByteString
encode forall a b. (a -> b) -> a -> b
$ forall a. ToJSON a => a -> Value
toJSON forall a b. (a -> b) -> a -> b
$ forall a. Response a -> a
getResponseBody @Value Response Value
response)
]
forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall a. Response a -> a
getResponseBody Response Value
response
addAuthorization :: Text -> Request -> Request
addAuthorization :: Text -> Request -> Request
addAuthorization Text
key =
HeaderName -> ByteString -> Request -> Request
addRequestHeader HeaderName
"Authorization" forall a b. (a -> b) -> a -> b
$ ByteString
"Bearer " forall a. Semigroup a => a -> a -> a
<> Text -> ByteString
T.encodeUtf8 Text
key
retry
:: forall a m
. (MonadUnliftIO m, MonadLogger m)
=> Int
-> m (Response a)
-> m (Response a)
retry :: forall a (m :: * -> *).
(MonadUnliftIO m, MonadLogger m) =>
Int -> m (Response a) -> m (Response a)
retry Int
attempt m (Response a)
go
| Int
attempt forall a. Ord a => a -> a -> Bool
<= Int
0 = m (Response a)
go
| Bool
otherwise = Response a -> m (Response a)
handler forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< m (Response a)
go forall (m :: * -> *) e a.
(MonadUnliftIO m, Exception e) =>
m a -> (e -> m a) -> m a
`catch` JSONException -> m (Response a)
handleParseError
where
handleParseError :: JSONException -> m (Response a)
handleParseError :: JSONException -> m (Response a)
handleParseError JSONException
e = case JSONException
e of
JSONParseException Request
_ Response ()
rsp ParseError
_ -> forall e b. Exception e => e -> Response b -> m (Response a)
orThrow JSONException
e Response ()
rsp
JSONConversionException Request
_ Response Value
rsp String
_ -> forall e b. Exception e => e -> Response b -> m (Response a)
orThrow JSONException
e Response Value
rsp
orThrow :: Exception e => e -> Response b -> m (Response a)
orThrow :: forall e b. Exception e => e -> Response b -> m (Response a)
orThrow e
e Response b
response
| forall a. Response a -> Int
getResponseStatusCode Response b
response forall a. Eq a => a -> a -> Bool
== Int
429 = do
let seconds :: Int
seconds = forall a. Response a -> Int
getResponseDelay Response b
response
forall (m :: * -> *). MonadLogger m => Text -> Text -> m ()
logWarnNS Text
"Asana" forall a b. (a -> b) -> a -> b
$ Text
"Retrying after " forall a. Semigroup a => a -> a -> a
<> String -> Text
pack (forall a. Show a => a -> String
show Int
seconds) forall a. Semigroup a => a -> a -> a
<> Text
" seconds"
forall (m :: * -> *). MonadIO m => Int -> m ()
threadDelay forall a b. (a -> b) -> a -> b
$ Int
seconds forall a. Num a => a -> a -> a
* Int
1000000
forall a (m :: * -> *).
(MonadUnliftIO m, MonadLogger m) =>
Int -> m (Response a) -> m (Response a)
retry (forall a. Enum a => a -> a
pred Int
attempt) m (Response a)
go
| Bool
otherwise = forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO e
e
handler :: Response a -> m (Response a)
handler :: Response a -> m (Response a)
handler Response a
response
| forall a. Response a -> Int
getResponseStatusCode Response a
response forall a. Eq a => a -> a -> Bool
== Int
429 = do
let seconds :: Int
seconds = forall a. Response a -> Int
getResponseDelay Response a
response
forall (m :: * -> *). MonadLogger m => Text -> Text -> m ()
logWarnNS Text
"Asana" forall a b. (a -> b) -> a -> b
$ Text
"Retrying after " forall a. Semigroup a => a -> a -> a
<> String -> Text
pack (forall a. Show a => a -> String
show Int
seconds) forall a. Semigroup a => a -> a -> a
<> Text
" seconds"
forall (m :: * -> *). MonadIO m => Int -> m ()
threadDelay forall a b. (a -> b) -> a -> b
$ Int
seconds forall a. Num a => a -> a -> a
* Int
100000
forall a (m :: * -> *).
(MonadUnliftIO m, MonadLogger m) =>
Int -> m (Response a) -> m (Response a)
retry (forall a. Enum a => a -> a
pred Int
attempt) m (Response a)
go
| Bool
otherwise = forall (f :: * -> *) a. Applicative f => a -> f a
pure Response a
response
getResponseDelay :: Response a -> Int
getResponseDelay :: forall a. Response a -> Int
getResponseDelay =
forall a. a -> Maybe a -> a
fromMaybe Int
0
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Read a => String -> Maybe a
readMaybe
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack
forall b c a. (b -> c) -> (a -> b) -> a -> c
. OnDecodeError -> ByteString -> Text
T.decodeUtf8With OnDecodeError
T.lenientDecode
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Monoid a => [a] -> a
mconcat
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. HeaderName -> Response a -> [ByteString]
getResponseHeader HeaderName
"Retry-After"