使用 Servant 根据“Maybe”的内容用 200 或 404 回答请求

Answer a request with 200 or 404 based on the content of `Maybe` using Servant

我目前正在尝试使用 servant 实现一个简单的 Web 服务器。目前,我有一个 IO (Maybe String) 我想通过 GET 端点公开(这可能是一个数据库查找,可能会或可能不会 return 结果,因此 IOMaybe).如果 Maybe 包含一个值,则响应应包含此值以及 200 OK 响应状态。如果 MaybeNothing404 Not Found 应该是 returned.

到目前为止我一直在关注 the tutorial,它也描述了使用 throwError 处理错误。但是,我还没有设法让它编译。我有以下代码:

type MaybeAPI = "maybe" :> Get '[ JSON] String

server :: Server MaybeAPI
server = stringHandler

maybeAPI :: Proxy MaybeAPI
maybeAPI = Proxy

app :: Application
app = serve maybeAPI server

stringHandler :: Handler String
stringHandler = liftIO $ fmap (\s -> (fromMaybe (throwError err404) s)) ioMaybeString 

ioMaybeString :: IO (Maybe String)
ioMaybeString = return $ Just "foo"

runServer :: IO ()
runServer = run 8081 app

我知道这可能比需要的更冗长,但我想一旦它开始工作就可以简化它。问题是 stringHandler,编译失败:

No instance for (MonadError ServerError []) arising from a use of ‘throwError’

所以我的问题是:这是在 Servant 中实现这样一个端点的方式吗?如果是这样,我该如何解决实施问题?我的 Haskell 知识相当有限,而且我以前从未使用过 throwError,所以我很可能在这里遗漏了一些东西。感谢您的帮助!

正如我在评论中提到的,问题在于违规行:

stringHandler :: Handler String
stringHandler = liftIO $ fmap (\s -> (fromMaybe (throwError err404) s)) ioMaybeString 

s 是一个 Maybe String,因此要将它用作 fromMaybe 的第二个参数,第一个参数必须是 String - 和 throwError 永远不会产生字符串。

虽然您谈到您的代码可能过于冗长并且您稍后会考虑简化它,但我认为这里的部分问题是在这个特定的处理程序中您试图成为简洁。让我们试着用一种更基本的、伪命令式的风格来写它。由于 Handler 是一个 monad,我们可以将其写在 do 块中,该块检查 s 的值并采取适当的操作:

stringHandler :: Handler String
stringHandler = do
    s <- liftIO ioMaybeString
    case s of
        Just str -> return str
        Nothing -> throwError err404

请注意,throwError 可以为它需要的任何类型生成类型 Handler a 的值,在本例中为 String。而且我们必须在 ioMaybeString 上使用 liftIO 将其提升到 Handler monad 中,否则这不会进行类型检查。

我能理解为什么您可能认为 fromMaybe 很适合这里,但从根本上讲它不是 - 原因是它是一个 "pure" 函数,不涉及 IO根本上,当你谈论抛出服务器错误时,你绝对不可避免地在做 IO。这些东西基本上不能混合在一个函数中。 (这也使得 fmap 不合适 - 它当然可以用于 "lift" 一个纯计算来处理 IO 操作,但在这里,正如我所说的,你需要的计算根本不是纯粹的.)

如果你想让上面的 stringHandler 函数更简洁,虽然我不认为这是一个真正的改进,你仍然可以明确地使用 >>= 而不是 do 块,而不会使代码太难读:

stringHandler = liftIO ioMaybeString >>= f
    where f (Just str) = return str
          f Nothing = throwError err404