How to include exception in Servant's response

Hello Everyone,

I have the following code that throws an exception in the event the json parsing fails:

tryObsFromResponse :: (MonadLogger m, MonadCatch m) => LB.ByteString -> m ObsETL
  tryObsFromResponse res = do

      -- Json ByteString -> Maybe ObsEtlInput
      obsInput :: ObsEtlInput <- case decode res of
        Just obs -> pure obs
        Nothing  -> do
           App.logErrorN "Failed to decode the json"
              $ App.ValueException
                   (Just "\nThe obsetl data decoding failed")

      tryBuildObs <- fromInputObsEtl obsInput -- :: m (Either ObsException ObsETL)
      case tryBuildObs of -- :: Either ObsException ObsETL
          Left e -> App.throw e
          Right obsEtl -> pure obsEtl

It is called by way of the following:

api :: ProjectId -> GQLRequest -> AppObs GQLResponse
api pid req = do
    _ <- setDbWithS3 pid
    interpreter gqlRoot req

Finally, AppObs is a custom monad used in servant which includes Control.Exception.Safe where throw is defined.

How should I think about getting the exception into the response generated by servant?

The Handler monad is a newtype around ExceptT ServerError IO a. It’s also an instance of MonadError ServerError and MonadCatch.

Perhaps, you could catch the ObsException in your handler, convert it into a ServerError, and re-throw it with throwError.

ServerError has fields like errHTTPCode, errBody … that let you build whatever response you wish.

If you instead let the ObsException bubble up, clients will get uninformative 5XX-style error messages.

@danidiaz You have the behavior as it is now exactly right. I get a 500 error with whatever message was used to construct the ObsException. Your suggestion on how to catch then throw a newly instantiated ServerError, to then build a custom response is the guidance I was looking for. All that lingers now: I’m a bit surprised I can’t build the response using the ObsException. I’ll be sure to give your outline a go. Thank you!