I'm trying to combine Servant authentication (servant-auth-server package) with RIO as my handler monad to avoid the ExceptT anti-pattern. However, I can't line up the types properly for handling denied authentications.
My (simplified) API endpoint is
type UserEndpoint = "user" :> (
Get '[JSON] User
:<|> ReqBody '[JSON] UpdatedUser :> Put '[JSON] User
)
and the corresponding server
protectedServer
:: HasLogFunc m
=> AuthResult AuthUserId
-> ServerT UserEndpoint (RIO m)
protectedServer (Authenticated authUser) =
getUser authUser :<|> updateUser authUser
-- Otherwise, we return a 401.
protectedServer _ = throwIO err401
A type error arises in the branch for denied authentication:
Could not deduce (MonadIO ((:<|>) (RIO m User)))
arising from a use of ‘throwIO’
[..]
I don't grok this type error. To my understanding (and given the signature of protectedServer
), the return type should be ServerT UserEndpoint (RIO m)
, which should have an instance of MonadIO
, so that exception handling according to the exceptions tutorial should use throwIO
instead of throwAll
from Servant.Auth.Server
. It seems that I haven't fully understood Servant's type machinery yet, where is my mistake?
The two handler functions are defined as
updateUser :: HasLogFunc m => AuthUserId -> UpdatedUser -> RIO m User
updateUser authUser updateUser = ...
getUser :: HasLogFunc m => AuthUserId -> RIO m User
getUser authUser = ...
The problem was that
throwIO err401
is a singleRIO
action. But when a servant server has more than one endpoint, each different handler must be composed with the:<|>
combinator.If your API has has many endpoints, it will quickly become annoying to write 401-returning handlers for each and every one. Fortunately, it seems that servant-auth-server provides a
throwAll
helper function which automatically builds error-returning handlers for an entire API.Edit: as Ulrich has noted, the problem with
throwAll
is that it only works withMonadError
monads, andRIO
is not an instance ofMonadError
. But it should be possible to modify the typeclass so that it supportsRIO
.First, some imports and helper datatypes:
And this is the main
RIOThrowAll
typeclass: