将failWith与Servant和自定义monad堆栈一起使用

时间:2016-05-29 21:40:35

标签: haskell monad-transformers servant

我正在使用 Servant 和自定义monad堆栈:

newtype AppHandler a = AppHandler { runHandler :: ReaderT Config (ExceptT ServantErr IO) a }
  deriving (Functor, Applicative, Monad, MonadReader Config, MonadError ServantErr, MonadIO)

data Config = Config
    { getPool :: ConnectionPool }

现在,在许多处理程序中,我只需要从db中获取一些数据(Persistent)并对其进行操作,所以我得到了:

runDb :: (MonadReader Config m, MonadIO m) => SqlPersistT IO b -> m b
runDb query = do
  pool <- asks getPool
  liftIO $ runSqlPool query pool

事实证明,当从db获取时,你必然会使用Maybe,而且当Maybe是Nothing时,你只想抛出错误,以便Servant服务器将将其转换为正确的HTTP响应。这使我发现了Control.Error.Util(!?) :: Applicative m => m (Maybe a) -> e -> ExceptT e m a助手。所以我试着跟随:

someHandler :: AppHandler NoContent
someHandler = do
  entity <- (runDb $ getCompanyByName companyName) !? err400
  -- some more logic
  return NoContent

但这不能编译,!?的结果是ExceptT ServantErr m0 (Entity SomeEntity),但我不再使用这种处理程序类型,它需要AppHandler (Entity SomeEntity)。我如何将这样的值转换回我的处理程序类型?

2 个答案:

答案 0 :(得分:3)

你想要一个(!?)的变体,它是多态的,它会返回monad。例如:

(!??) :: MonadError e m => m (Maybe a) -> e -> m a
act !?? err = act >>= maybe (throwError err) return

然后,提供err400 :: ServantError - 您声明AppHandler的错误类型 - 您将能够写

runDb (getCompanyByName companyName) !?? err400

答案 1 :(得分:1)

通常,要将m a类型的值转换为ReaderT r m a,只需使用lift

所以这可能适合你:

entity <- lift $ (runDb $ getCompanyByName companyName) !? err400

如果整个(runDb ...) !? err400ExceptT ServantError ...值。

此外,这个仆人问题讨论:

https://github.com/haskell-servant/servant/issues/286

可能会有所帮助。