伺服器伺服器的自订JSON错误 [英] Custom JSON errors for Servant-server

查看:148
本文介绍了伺服器伺服器的自订JSON错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在使用 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 for serve (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.

在其中,我们尝试将其解码为Bodya参数.失败时,我们吐出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并用我们自己的替换;在我工作的一个项目中,我们甚至用自己的仆人动词(GETPOST,...)替换了.我们编写了新的内容类型,甚至像您在此处看到的那样,对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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆