Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell / Wreq - Advice on complicated type signatures for http requests

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?

like image 840
Nick Acosta Avatar asked Aug 25 '17 05:08

Nick Acosta


1 Answers

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.

like image 171
Mark Seemann Avatar answered Nov 15 '22 07:11

Mark Seemann