伺服器伺服器的自订JSON错误 [英] Custom JSON errors for Servant-server
问题描述
在使用 servant 时,我想将所有错误返回为JSON .当前,如果请求解析失败,我会看到类似这样的错误消息,以纯文本格式返回
When using servant, I'd like to return all errors as JSON. Currently, if a request fails to parse, I see an error message like this, returned as plain text
Failed reading: not a valid json value
相反,我想将其返回为application/json
Instead I would like to return this as application/json
{"error":"Failed reading: not a valid json value"}
我该怎么做?文档说ServantErr
是默认错误类型,我当然可以在处理程序中使用自定义错误进行响应,但是如果解析失败,我将看不到如何返回自定义错误.
How can I do this? The docs say ServantErr
is the default error type, and I can certainly respond with custom errors inside my handlers, but if parsing fails I don't see how I can return a custom error.
推荐答案
首先,一些语言扩展
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
现在
不幸的是,这比应该做的困难.尽管Servant经过精心设计并且由小的逻辑部分组成,但对于HTTP服务的操作方式却持坚定的态度.您可能正在使用的ReqBody
的默认实现是硬编码的,以吐出文本字符串.
Now then
Unfortunately this is more difficult than it should be. Servant, while well-designed and the composition of small logical parts, is very opinionated about how HTTP services should operate. The default implementation of ReqBody
, which you are probably using, is hard-coded to spit out a text string.
但是,我们可以将ReqBody
切换为我们自己的数据类型:
However, we can switch out ReqBody
for our own data type:
module Body where
import Control.Monad.Trans (liftIO)
import Data.Proxy (Proxy(..))
import Network.Wai (lazyRequestBody)
import Data.Aeson
import Servant.API
import Servant.Server
import Servant.Server.Internal
data Body a
instance (FromJSON a, HasServer api context) => HasServer (Body a :> api) context where
type ServerT (Body a :> api) m = a -> ServerT api m
route Proxy context subserver =
route (Proxy :: Proxy api) context (addBodyCheck subserver (withRequest bodyCheck))
where
bodyCheck request = do
body <- liftIO (lazyRequestBody request)
case eitherDecode body of
Left (BodyError -> e) ->
delayedFailFatal err400 { errBody = encode e }
Right v ->
return v
在这段非常简短的代码中,发生了很多事情:
In this very brief amount of code a lot is happening:
-
我们正在向
servant-server
包讲解如何在新数据类型出现在serve (Proxy :: Proxy (Body foo :> bar)) server
的类型解析中时处理它.
We are teaching the
servant-server
package on how to handle our new datatype when it appears in the type resolution forserve (Proxy :: Proxy (Body foo :> bar)) server
.
We have ripped most of the code from the v0.8.1 release of ReqBody
.
我们正在向处理请求正文的管道中添加一个函数.
We are adding a function to the pipeline that processes request bodies.
在其中,我们尝试将其解码为Body
的a
参数.失败时,我们吐出JSON Blob和HTTP 400.
In it, we attempt to decode to the a
parameter of Body
. On failure, we spit out a JSON blob and an HTTP 400.
为简洁起见,我们在这里完全忽略了内容类型标题.
We are entirely ignoring content-type headers here, for brevity.
这是JSON blob的类型:
Here is the type of the JSON blob:
newtype BodyError = BodyError String
instance ToJSON BodyError where
toJSON (BodyError b) = object ["error" .= b]
大多数这种机器是servant-server
内部的,文档不足且相当脆弱.例如,我已经看到代码在master
分支上发生分歧,并且我的addBodyCheck
的属性已更改.
Most of this machinery is internal to servant-server
and underdocumented and rather fragile. For example, already I see that the code diverges on master
branch and the arity of my addBodyCheck
has changed.
尽管Servant项目还很年轻,而且雄心勃勃,但我不得不说,这种解决方案的美观性和鲁棒性绝对令人难以置信.
Though the Servant project is still quite young and remarkably ambitious, I have to say that the aesthetics and robustness of this solution are definitely underwhelming.
我们将需要一个主模块:
We will need a Main module:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
module Main where
import Data.Proxy (Proxy(..))
import Network.Wai.Handler.Warp (run)
import Servant.API
import Servant.Server
import Body
type API = Body [Int] :> Post '[JSON] [Int]
server :: Server API
server = pure
main :: IO ()
main = do
putStrLn "running on port 8000"
run 8000 (serve (Proxy :: Proxy API) server)
还有一个外壳:
~ ❯❯❯ curl -i -XPOST 'http://localhost:8000/'
HTTP/1.1 400 Bad Request
Transfer-Encoding: chunked
Date: Fri, 20 Jan 2017 01:18:57 GMT
Server: Warp/3.2.9
{"error":"Error in $: not enough input"}%
~ ❯❯❯ curl -id 'hey' -XPOST 'http://localhost:8000/'
HTTP/1.1 400 Bad Request
Transfer-Encoding: chunked
Date: Fri, 20 Jan 2017 01:19:02 GMT
Server: Warp/3.2.9
{"error":"Error in $: Failed reading: not a valid json value"}%
~ ❯❯❯ curl -id '[1,2,3]' -XPOST 'http://localhost:8000/'
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Fri, 20 Jan 2017 01:19:07 GMT
Server: Warp/3.2.9
Content-Type: application/json
[1,2,3]%
Ta-da!
您应该能够在LTS-7.16上运行所有这些代码.
You should be able to run all this code on LTS-7.16.
(1)Servant和Haskell很有趣.
(1) Servant and Haskell are fun.
(2)当涉及到您在API中指定的类型时,Servant的类型类机制允许即插即用.我们可以取出ReqBody
并用我们自己的替换;在我工作的一个项目中,我们甚至用自己的仆人动词(GET
,POST
,...)替换了.我们编写了新的内容类型,甚至像您在此处看到的那样,对ReqBody
进行了类似的操作.
(2) The typeclass machinery of Servant allows for a kind of plug-and-play when it comes to the types you specify in your API. We can take out ReqBody
and replace it with our own; on a project I did at work we even replaced the Servant verbs (GET
, POST
, ...) with our own. We wrote new content types and we even did something similar with ReqBody
like you saw here.
(3)GHC编译器的显着功能是,我们可以在编译时解构类型,从而以安全且合乎逻辑的方式影响运行时行为.我们可以在类型级别上表达一棵API路由树,然后使用类型类实例遍历它们,并使用类型族累积服务器类型,这是构建良好类型的Web服务的一种绝妙的优雅方法.
(3) It is the remarkable ability of the GHC compiler that we can destructure types during compile-time to influence runtime behavior in a safe and logically sound way. That we can express a tree of API routes at the type-level and then walk over them using typeclass instances, accumulating a server type using type families, is a wonderfully elegant way to build a well-typed web service.
这篇关于伺服器伺服器的自订JSON错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!