【发布时间】:2021-04-26 07:06:49
【问题描述】:
我正在尝试将 Servant 身份验证(servant-auth-server 包)与 RIO 结合为我的处理程序 monad,以避免出现异常反模式。但是,我无法正确排列类型以处理被拒绝的身份验证。
我的(简化的)API 端点是
type UserEndpoint = "user" :> (
Get '[JSON] User
:<|> ReqBody '[JSON] UpdatedUser :> Put '[JSON] User
)
以及对应的服务器
protectedServer
:: HasLogFunc m
=> AuthResult AuthUserId
-> ServerT UserEndpoint (RIO m)
protectedServer (Authenticated authUser) =
getUser authUser :<|> updateUser authUser
-- Otherwise, we return a 401.
protectedServer _ = throwIO err401
拒绝身份验证的分支中出现类型错误:
Could not deduce (MonadIO ((:<|>) (RIO m User)))
arising from a use of ‘throwIO’
[..]
我不理解这种类型的错误。据我了解(并给出protectedServer 的签名),返回类型应该是ServerT UserEndpoint (RIO m),它应该有一个MonadIO 的实例,因此根据exceptions tutorial 的异常处理应该使用throwIO 而不是throwAll 来自 Servant.Auth.Server。看来我还没有完全理解Servant的类机,我的错在哪里?
两个处理函数定义为
updateUser :: HasLogFunc m => AuthUserId -> UpdatedUser -> RIO m User
updateUser authUser updateUser = ...
getUser :: HasLogFunc m => AuthUserId -> RIO m User
getUser authUser = ...
【问题讨论】:
-
如果在
throwIO err401的位置放置一个类型化的孔_,那么推断的孔类型是什么?另外,如果你尝试在 ghci 中运行:kind! ServerT UserEndpoint (RIO ()),返回的扩展类型是什么? -
@danidiaz,推断的类型是
_ :: RIO m User :<|> (UpdatedUser -> RIO m User)其中:“m”是受protectedServer :: forall m. HasLogFunc m => AuthResult AuthUserId -> ServerT UserEndpoint (RIO m)的类型签名绑定的刚性类型变量。:<|>不应该将处理程序类型组合成输出类型,这里是ServerT UserEndpoint (RIO m)吗? -
单个
throwIO err401就像您要返回的那样没有_ :<|> _类型。看来您必须使用:<|>组合端点实现,就像您在已验证的分支中所做的那样,只是这次返回 401 代码。 -
在为服务器提供实现时,如果您的 API 中有多个端点,您必须为每个端点提供一个处理函数,并将它们与
:<|>结合起来。当您编写单个throwIO err401时,即不是 实现的组合,它是单个处理程序!因此,您需要像在经过身份验证的分支中所做的那样:为每个端点提供一个处理程序,并将它们与 (:<|>) 结合起来。 -
@danidiaz 感谢您的提示。对于未经身份验证的情况,我使用以下定义修复了类型错误:
protectedServer _ = throwIO err401 :<|> (\_ -> throwIO err401)它会进行类型检查,但看起来相当麻烦,尤其是对于更复杂的 API :-(
标签: authentication haskell servant rio