@hackage servant-typed-error0.1.2.0

Typed error wrapper for Servant

servant-typed-error

This is a small wrapper for the exception approach detailed here: https://docs.servant.dev/en/stable/cookbook/uverb/UVerb.html?highlight=exceptions

This library allows sending and receiving Typed errors via servant. For example, we can define the following API:

data APIError = Whoops | Bad Int
  deriving (Generic, ToJSON, FromJSON)

type API =
  "foo" :> Capture "number" Int :> GetTypedError '[JSON] Bool APIError
    :<|> "whoops" :> GetTypedError '[JSON] Int APIError

Here we use GetTypedError instead of the usual Get to indicate the API can return an ApiError, which must have a ToJSON/FromJSON instance.

There are two ways of writing a server for this API. We can either use the TypedHandler monad, which has throwTypedError and throwServantError functions allowing us to either throw our custom typed error or a generic ServantError. Another approach using a generic mtl style definition of a monad with a MonadError e m constraint; we can then use liftTypedError to lift it into servant's Handler monad:

alwaysWhoops :: MonadError APIError m => m Int
alwaysWhoops = throwError Whoops

server :: Server API
server =
  ( \i ->
      runTypedHandler $ case i of
        42 -> throwTypedError $ Bad i
        -1 -> throwServantError err500
        x -> pure $ x `mod` 2 == 0
  )
    :<|> liftTypedError alwaysWhoops

Finally, we can recover the errors on the client side via a special TypedClientM e a monad. We use the typedClient to convert the servant-client API to use our TypedClientM:

foo :: Int -> TypedClientM APIError Bool
whoops :: TypedClientM APIError Int
foo :<|> whoops = typedClient $ client $ Proxy @API

Then, using runTypedClientM we can obtain an Either (Either ClientError APIError) a and pattern match on both the generic servant-client error or the user defined APIError.

Differences with other servant typed error/exception libs

  • servant-checked-exceptions This library wraps the response in a custom type, so it would be a bit awkward to use with the frontend. The approach here sends the same 200 response as vanilla servant would

  • servant-exceptions Not investigated much since it's missing the client implementation