How to catch Plug.Parsers.ParseError

I know this thread is pretty old, but it’s high up in the search function. I managed to find a semi-elegant solution for this.

I made a custom plug to call out to Plug.Parsers and handle any exceptions it throws.

defmodule DataApiWeb.Plugs.QuietJSON do
  @behaviour Plug
  import Plug.Conn

  @impl true
  def init(opts) do
    # Create the optoins that `Plug.Parsers` expects.
    Plug.Parsers.init(opts)
  end

  @impl true
  def call(conn, opts) do
    Plug.Parsers.call(conn, opts)
  rescue
    Plug.Parsers.ParseError ->
      conn
      |> put_resp_content_type("application/json")
      |> send_resp(400, ~s({"error":{"detail":"Malformed JSON"}}))
      |> halt()
  end
end

Then, in my endpoint.ex I’ve made this change:

  # plug Plug.Parsers,
  plug DataApiWeb.Plugs.QuietJSON,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Phoenix.json_library()

This approach simply takes in the options from the original plug and passes them on to the wrapped plug.

On malformed data, the plug returns the error message {"error": {"detail": "Malformed JSON"}} and you shouldnt see any error logs anymore.

A sample test I added to verify the behavior:

    test "invalid JSON body returns a clean 400", %{conn: conn} do
      conn =
        conn
        |> put_req_header("content-type", "application/json")
        |> post(~p"/api/health", "{not-json")

      assert %{"error" => _} = json_response(conn, 400)
    end

The reason I needed this was because some bots were spamming and it polluted my log files.