Ecognium
Ecognium

Reputation: 2076

`wreq` Get / Post with exception handling

I am making some http calls using wreq and would like to catch any exception and return an Either type. I tried something like this but could not figure out how to manipulate the calls so it will type check.

 -- exhaustive pattern match omitted here
 safeGetUrl :: URL -> Maybe Login -> Maybe Password -> IO (Either String (Response LBS.ByteString))
 safeGetUrl url (Just login) (Just pass) = do
     let def  = defaults
         opts = def & auth ?~ basicAuth (BS.pack login) (BS.pack pass)
     r <- getWith opts url  `E.catch` handler
     return $ Right r

   where
     handler :: HttpException -> Either String (Response LBS.ByteString)
     handler (StatusCodeException s _ _) = do
          return $ Left $ LBS.unpack (s ^. statusMessage)

I am pasting the type error below but I know the above code will not compile. The issue is r <- getWith opts url E.catch handler. The first part returns IO (Res... but the exception handler returns Either... I tried adding lifting the getWith.. into Either but that did not type check either.

Couldn't match type ‘Either String (Response LBS.ByteString)’
                 with ‘IO (Response LBS.ByteString)’
  Expected type: HttpException -> IO (Response LBS.ByteString)
    Actual type: HttpException
                 -> Either String (Response LBS.ByteString)
  In the second argument of ‘catch’, namely ‘handler’
  In a stmt of a 'do' block: r <- getWith opts url `catch` handler

Is there a way to catch this exception and return an IO Either type?

Upvotes: 3

Views: 1028

Answers (2)

vjousse
vjousse

Reputation: 61

Since @jozefg answer, the API has changed a little bit and the answer doesn't compile anymore.

Here is an updated version that compiles:

import qualified Control.Exception     as E
import           Control.Lens
import qualified Data.ByteString.Char8 as BSC
import qualified Data.ByteString.Lazy  as LBS
import           Network.HTTP.Client
import           Network.Wreq          as NW

type URL = String

type Login = String

type Password = String

safeGetUrl ::
     URL
  -> Maybe Login
  -> Maybe Password
  -> IO (Either String (Response LBS.ByteString))
safeGetUrl url (Just login) (Just pass) = do
  let def = defaults
      opts = def & auth ?~ basicAuth (BSC.pack login) (BSC.pack pass)
  (Right <$> getWith opts url) `E.catch` handler
  where
    handler :: HttpException -> IO (Either String (Response LBS.ByteString))
    handler (HttpExceptionRequest _ (StatusCodeException r _)) =
      return $ Left $ BSC.unpack (r ^. NW.responseStatus . statusMessage)

Upvotes: 6

daniel gratzer
daniel gratzer

Reputation: 53901

Your issue is that one side of the handle returns an unwrapped response (no Either) and the other side returns an Either-wrapped exception. You then attempt to wrap the response in an Either, which you do need to do, but it's just at the wrong place. You can fix this merely by switching where you do the wrapping

safeGetUrl :: URL -> Maybe Login -> Maybe Password -> IO (Either String (Response LBS.ByteString))
 safeGetUrl url (Just login) (Just pass) = do
     let def  = defaults
         opts = def & auth ?~ basicAuth (BS.pack login) (BS.pack pass)
     (Right <$> getWith opts url) `E.catch` handler

   where
     handler :: HttpException -> IO (Either String (Response LBS.ByteString))
     handler (StatusCodeException s _ _) = do
          return $ Left $ LBS.unpack (s ^. statusMessage)

However there are some other problems with your functions, remember that unpack gives back Word8s not Char. You may want to import Data.ByteString.Char as the version of unpack defined in there should work better than LBS.unpack. Without your imports though I cannot confirm this definitively. The final (working) code for me is

import Control.Lens
import Network.Wreq
import Network.HTTP.Client
import qualified Control.Exception as E
import qualified Data.ByteString.Char8 as BSC
import qualified Data.ByteString.Lazy as LBS

type URL = String
type Login = String
type Password = String

safeGetUrl :: URL
           -> Maybe Login
           -> Maybe Password
           -> IO (Either String (Response LBS.ByteString))
safeGetUrl url (Just login) (Just pass) = do
  let def  = defaults
      opts = def & auth ?~ basicAuth (BSC.pack login) (BSC.pack pass)
  (Right <$> getWith opts url)  `E.catch` handler
  where
    handler :: HttpException -> IO (Either String (Response LBS.ByteString))
    handler (StatusCodeException s _ _) = do
      return $ Left $ BSC.unpack (s ^. statusMessage)

Upvotes: 4

Related Questions