Prevent Plug.ErrorHandler from re-raising error after callback

Background

I have a simple Plug router with a PUT endpoint that receives a JSON body. To parse it I am using Plug.Parsers.

Problem

The Plug.Parsers plug works fine and puts the json inside conn.body_params. However, if the JSON I am receiving is malformated, my application explodes with errors. To prevent this I am using Plug.ErrorHandler but since it re-raises the error after, the app still explodes.

Code

This is my router.

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

It should be noted that in reality Products.process is not (or should not be) called because Plug.Parsers raises before.

And this is my test:

    test "returns 400 when the request is not a valid JSON" do
      # Arrange
      body_params = "" # 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

Error

As you can probably guess, I am expecting the request to return 400 and a nice error message. Instead I get this:

test PUT /products returns 400 when the request is not a valid JSON (ApiTest)
test/api_test.exs:143
** (Plug.Conn.WrapperError) ** (FunctionClauseError) no function clause matching in Api.Controllers.Products.handle_flow_response/2
code: conn = Api.call(conn, @opts)
stacktrace:
(api 0.1.0) lib/api/controllers/products.ex:39: Api.Controllers.Products.handle_flow_response(:ok, %Plug.Conn{adapter: {Plug.Adapters.Test.Conn, :…}, assigns: %{}, before_send: [function<1.129014997/1 in Plug.Logger.call/2>], body_params: %{}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: “www.example.com”, method: “PUT”, owner: pid<0.406.0>, params: %{}, path_info: [“cars”], path_params: %{}, port: 80, private: %{plug_route: {“/products”, function<1.102964628/2 in Api.do_match/4>}}, query_params: %{}, query_string: “”, remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [{“accept”, “application/json”}, {“content-type”, “application/json”}], request_path: “/products”, resp_body: nil, resp_cookies: %{}, resp_headers: [{“cache-control”, “max-age=0, private, must-revalidate”}], scheme: :http, script_name: , secret_key_base: nil, state: :unset, status: nil})
(api 0.1.0) lib/plug/router.ex:284: Api.dispatch/2
(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:154: (test)

I am rather dumbfounded.

Failed Fix

To avoid this I tried modifying the handle_errors function to the following, but it still failed:

def handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
    send_resp(conn, conn.status, "Something went wrong")
    {:error, :something_went_wrong}
end

No matter what I do, the code always flows with “:ok”. Nothing I do seems to have control over the error.

Question

How can I prevent this error from re-raising and simply return the nice error message I have in my test?