将 Servant.Generic 路由与 ReaderT(池连接)IO 结合使用
Using Servant.Generic routes with ReaderT (Pool Connection) IO
我正在使用 servant-generic-0.1.0.3
和 servant-server-0.13.0.1
来执行以下操作:
data Site route = Site
{ page :: route :-
"page" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] [Int]
, home :: route :-
Raw
} deriving (Generic)
type API = ToServant (Site AsApi)
siteServer :: Pool Connection -> Site AsServer
siteServer pool = Site
{ page = \x y ->
liftIO $ withResource pool $ \conn -> someDbFunction conn x y
, home = serveDirectoryWebApp "static"
}
api :: Proxy API
api = Proxy
app :: Pool Connection -> Application
app pool = serve api (toServant $ siteServer pool)
效果很好,然后我尝试使用 ReaderT
来避免将 Pool Connection
传递给 siteServer
,所以我添加了 AppM
并替换了 siteServer
这个:
type AppM = ReaderT (Pool Connection) IO
siteServer :: ServerT API AppM
siteServer = Site
{ page = do
pool <- ask
\x y ->
liftIO $ withResource pool $ \conn -> someDbFunction conn x y
, home = serveDirectoryWebApp "static"
}
但是当我尝试编译它时出现了一堆错误。
我遵循了 servant cookbook 中显示的相同步骤,但我无法使用通用路由进行此操作,尽管它在使用常规路由时有效。
我是否遗漏了一些可以使这项工作成功的东西?
至少对于 servant-* >= 0.14(见 here)支持的记录式路由,如果你想使用 Handler
之外的另一个 monad,你会想看看在 AsServerT
和 genericServerT
.
应用于您的示例,这意味着 siteServer
应定义如下(未进行类型检查,但应该非常接近正确)。
siteServer :: Site (AsServerT AppM)
siteServer = Site
{ page = ... something in AppM ...
, home = ... something in AppM ...
}
-- turning that into a usual chain of :<|>-separated handlers
oldStyleServer :: ServerT API AppM
oldStyleServer = genericServerT siteServer
-- bringing it all back in Handler
oldStyleServerInHandler :: Pool Connection -> Server API -- same as ServerT API Handler
oldStyleServerInHandler conns = hoistServer (Proxy @API) appToHandler oldStyleServer
where appToHandler = liftIO . flip runReaderT conns
-- or something along those lines
-- serving it
app :: Pool Connection -> Application
app conns = serve (Proxy @API) (oldStyleServerInHandler conns)
编辑: 由于您将 servant-* < 0.14 与 servant-generic 一起使用,因此应将 genericServerT
替换为 toServant
。
我正在使用 servant-generic-0.1.0.3
和 servant-server-0.13.0.1
来执行以下操作:
data Site route = Site
{ page :: route :-
"page" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] [Int]
, home :: route :-
Raw
} deriving (Generic)
type API = ToServant (Site AsApi)
siteServer :: Pool Connection -> Site AsServer
siteServer pool = Site
{ page = \x y ->
liftIO $ withResource pool $ \conn -> someDbFunction conn x y
, home = serveDirectoryWebApp "static"
}
api :: Proxy API
api = Proxy
app :: Pool Connection -> Application
app pool = serve api (toServant $ siteServer pool)
效果很好,然后我尝试使用 ReaderT
来避免将 Pool Connection
传递给 siteServer
,所以我添加了 AppM
并替换了 siteServer
这个:
type AppM = ReaderT (Pool Connection) IO
siteServer :: ServerT API AppM
siteServer = Site
{ page = do
pool <- ask
\x y ->
liftIO $ withResource pool $ \conn -> someDbFunction conn x y
, home = serveDirectoryWebApp "static"
}
但是当我尝试编译它时出现了一堆错误。
我遵循了 servant cookbook 中显示的相同步骤,但我无法使用通用路由进行此操作,尽管它在使用常规路由时有效。
我是否遗漏了一些可以使这项工作成功的东西?
至少对于 servant-* >= 0.14(见 here)支持的记录式路由,如果你想使用 Handler
之外的另一个 monad,你会想看看在 AsServerT
和 genericServerT
.
应用于您的示例,这意味着 siteServer
应定义如下(未进行类型检查,但应该非常接近正确)。
siteServer :: Site (AsServerT AppM)
siteServer = Site
{ page = ... something in AppM ...
, home = ... something in AppM ...
}
-- turning that into a usual chain of :<|>-separated handlers
oldStyleServer :: ServerT API AppM
oldStyleServer = genericServerT siteServer
-- bringing it all back in Handler
oldStyleServerInHandler :: Pool Connection -> Server API -- same as ServerT API Handler
oldStyleServerInHandler conns = hoistServer (Proxy @API) appToHandler oldStyleServer
where appToHandler = liftIO . flip runReaderT conns
-- or something along those lines
-- serving it
app :: Pool Connection -> Application
app conns = serve (Proxy @API) (oldStyleServerInHandler conns)
编辑: 由于您将 servant-* < 0.14 与 servant-generic 一起使用,因此应将 genericServerT
替换为 toServant
。