问题描述
背景
我有一个简单的Plug路由器,带有一个接收JSON正文的PUT端点。要解析它,我使用的是Plug.Parsers。
问题
Plug.Parsers
插件工作正常,并将json放入conn.body_params
中。但是,如果我收到的JSON格式错误,则我的应用程序会因错误而爆炸。为了防止这种情况,我使用了Plug.ErrorHandler,但是由于它会在以后重新引发错误,因此该应用仍然会爆炸。
代码
这是我的路由器。
defmodule Api do
use Plug.{Router,ErrorHandler}
alias Api.Controllers.{Products,NotFound}
plug Plug.Logger
plug :match
plug Plug.Parsers,parsers: [:urlencoded,:json],pass: ["text/*"],json_decoder: Jason
plug :dispatch
put "/products",do: Products.process(conn)
match _,do: NotFound.process(conn)
def handle_errors(conn,%{kind: _kind,reason: _reason,stack: _stack}) do
send_resp(conn,conn.status,"Something went wrong")
end
end
应注意,实际上Products.process
未被(或不应被调用)是因为Plug.Parsers
之前已经加注。
这是我的考验:
test "returns 400 when the request is not a valid JSON" do
# Arrange
body_params = "[{\"id\": 1}" # this is not valid JSON
conn =
:put
|> conn("/products",body_params)
|> put_req_header("accept","application/json")
|> put_req_header("content-type","application/json")
# Act
conn = Api.call(conn,Api.init([]))
# Assert
assert conn.state == :sent
assert conn.status == 400
assert conn.resp_body == "Invalid JSON in body request"
end
错误
您可能会猜到,我期望该请求返回400和一个不错的错误消息。相反,我得到这个:
当请求的JSON正文无效时,test PUT / cars返回400 (ApiTest)test / api_test.exs:157 **(Plug.Parsers.ParseError)格式错误的请求,引发了Jason.DecodeError异常,并显示消息“位置上的输入意外结束 10英寸代码:conn = Api.call(conn,@opts)stacktrace :(插头1.10.4) lib / plug / parsers / json.ex:88:Plug.Parsers.JSON.decode / 2(plug 1.10.4) lib / plug / parsers.ex:313:Plug.Parsers.reduce / 8(api 0.1.0) lib / api.ex:1:Api.plug_builder_call / 2(api 0.1.0) lib / plug / error_handler.ex:65:Api.call/2 test / api_test.exs:168:(测试)
我很傻。
修复失败
为避免这种情况,我尝试将handle_errors
函数修改为以下内容,但仍然失败:
def handle_errors(conn,"Something went wrong")
{:error,:something_went_wrong}
end
我似乎对错误没有任何控制。
问题
解决方法
我认为您不应该阻止再次提出该错误。我认为问题在于您的测试没有预期的错误。
您可以catch the error:
assert %Plug.Parsers.ParseError{} =
catch_error(Api.call(conn,Api.init([])))
或者您可以通过使用HTTP客户端来测试您的端点来避免该问题,而不是直接执行插件。例如,使用特斯拉:
assert %{status: 400,body: "Invalid JSON in body request"} =
Tesla.put!(@base_url <> "/products","")