I'm a Haskell beginner and am currently using wreq to make a simple wrapper around an api. I want to send an if-modified-since
header if provided with a time. I am doing so in the following manner.
getResponse :: (FormatTime t, Exception e) => File -> Maybe t -> IO (Either e (Response L.ByteString))
getResponse file (Just t) =
let formattedTime = (B.pack . formatTime defaultTimeLocale rfc822DateFormat) t
opts = defaults & header "if-modified-since" .~ [formattedTime]
in try $ getWith opts $ buildUrl file
getResponse file Nothing = try $ (get $ buildUrl file)
I noticed that 304 (not modified)
responses are coming back as exceptions so that was my justification for using the Either
type. I wanted to provide visibility to errors for people that might use this api wrapper.
Assuming the request is successful, I want to parse the response body into a corresponding type defined in my library. There is a chance deserialization might not work correctly if something changes on the server I'm making requests to so I chose to use the Maybe
type to account for this.
getPayload :: FromJSON b => (Either e (Response L.ByteString)) -> Either e (Maybe b)
getPayload (Left _) = return Nothing
getPayload (Right a) = return $ fmap Just (^. responseBody) =<< asJSON a
The signatures of these functions are starting to seem like an eyesore to me, something in my gut is telling me there is a better way but I am unsure. One thing I did was make another function to put them together with the hopes it would be easier to use. This is the function I plan on using to create other functions to make more specific requests to individual resources.
getResource :: (Exception e, FormatTime t, FromJSON b) => File -> Maybe t -> IO (Either e (Maybe b))
getResource f t = getPayload <$> (getResponse f t)
I now have to deal with 3 layers of structure when dealing with an http request. IO
, Either
, and Maybe
. Am I making this too complicated? What can I do to make this less of a pain to work with from a usage and maintainability perspective? How can I improve this?
This may not be quite what you want, but asJSON
has the return type m (Response a)
, where m
is a MonadThrow
. While Maybe
is a MonadThrow
instance, then so is Either e
. This means that you don't have to use Maybe
in order to handle if anything goes wrong with asJSON
. You can 'stay' in the Either
monad instead:
getPayload :: FromJSON b => Either SomeException (Response L.ByteString)
-> Either SomeException b
getPayload = ((fmap (^. responseBody) . asJSON) =<<)
Clearly, this puts an additional constraint on the type of error on the left side, so I'm not sure that this is acceptable. If not, please leave a comment.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With