4

I want to handle a database exception inside a servant handler monad.

I've tried to use the try function from the Control.Exception package to be able to case match with Left exception -> throwError err422 { errBody = ... }.

I'm using postgresql-typed to interface with a PostgreSQL database. I want to capture a PGError exception.

The relevant code with the following modification:

accountHandler :: CreateAccountPayload -> Handler Account
accountHandler payload =
  let errors = validateCreateAccountPayload payload in
  if hasErrors errors then
    throwError err422 { errBody = JSON.encode errors }
  else
    do
      result <- try (liftIO (insertAccount payload))
      case result of
        Right account -> return account
        Left exception -> throwError err422 { errBody = JSON.encode [ValidationError (Text.pack "email") (Text.pack "is already taken")] }

I am expecting to be able to capture a result from the database call and be able to case match it. The cases should be for an exception or a value. I am currently getting the following compile error:

src/Main.hs:64:17: error:
    • Couldn't match type ‘IO’ with ‘Handler’
      Expected type: Handler (Either e0 Account)
        Actual type: IO (Either e0 Account)
    • In a stmt of a 'do' block:
        result <- try (liftIO (insertAccount payload))
      In the expression:
        do result <- try (liftIO (insertAccount payload))
           case result of
             Right account -> return account
             Left exception -> throwError err422 {errBody = encode ...}
      In the expression:
        if hasErrors errors then
            throwError err422 {errBody = encode errors}
        else
            do result <- try (liftIO (insertAccount payload))
               case result of
                 Right account -> return account
                 Left exception -> throwError ...
   |
64 |       result <- try (liftIO (insertAccount payload))
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  • 2
    I'm not sure, but I'd think that `liftIO` lifts your `IO Account` result into the `Handler` monad. If so, perhaps `try` should go first and `liftIO` second: `result <- liftIO (try (insertAccount payload))`. – Mark Seemann Jun 21 '19 at 21:47
  • @MarkSeemann That and updating the pattern match in the case to `Left (PGError e) ->` did the trick. Thanks! I'll accept that answer if you post it. – fernandomrtnz Jun 21 '19 at 22:17

1 Answers1

2

I'd think that liftIO lifts your IO Account result into the Handler monad. If so, perhaps try should go first and liftIO second:

result <- liftIO (try (insertAccount payload))
case result of
    Right account -> return account
    Left (PGError e) -> throwError err422
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736