Typed error wrapper for Servant.
Typed error wrapper using UVerb 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.