How to use Plug.Parsers?

Background

I have a Plug Router that has some simple endpoints. One of these endpoints is a put method that receives a JSON body. I want to use Plug.Parsers to decode the body before processing it but I am having errors and I don’t know why.

Code

This is my router. Requests come here to be dispatched:

defmodule Api do
  use Plug.Router

  plug :match
  plug Plug.Parsers, parsers: [:urlencoded, :json],
    json_decoder: Jason
  plug :dispatch

  put "/cars"    do
    IO.inspect conn
    Conn.send_resp(conn, Status.code(:ok), "OK")
  end

end

To exercise this code, I have the following test:

    test "returns 200 OK when the list of cars is loaded correctly" do
      # Arrange
      body_params = "[
        {
          \"id\": 1,
          \"number\": 4
        },
        {
          \"id\": 2,
          \"number\": 6
        }
      ]"

      conn =
        :put
        |> conn("/cars")
        |> put_req_header("accept", "application/json")
        |> put_req_header("content-type", "application/json")
        |> put_body_params(body_params)

      # Act
      conn = Api.call(conn, @opts)

      # Assert
      assert conn.state == :sent
      assert conn.status == 200
      assert conn.resp_body == "OK"
    end

According to my knowledge, this should work, but I get the following error:

test PUT /cars returns 200 OK when the list of cars is loaded correctly (ApiTest)
test/api_test.exs:69
** (BadMapError) expected a map, got: “[\n {\n "id": 1,\n "number": 4\n },\n {\n "id": 2,\n "number": 6\n }\n ]”
code: conn = Api.call(conn, @opts)
stacktrace:
(stdlib 3.10) :maps.merge(%{}, “[\n {\n "id": 1,\n "seats": 4\n },\n {\n "id": 2,\n "seats": 6\n }\n ]”)
(plug 1.10.4) lib/plug/parsers.ex:354: Plug.Parsers.merge_params/4
(plug 1.10.4) lib/plug/parsers.ex:299: Plug.Parsers.call/2
(api 0.1.0) lib/api.ex:1: Api.plug_builder_call/2
test/api_test.exs:91: (test)

Questions

  1. Is my configuration of the router incorrect?
  2. Will the parser be applied to all incoming requests?
  3. What happens if I get a request that is not JSON? (will the parser ignore it?)
  4. What am I doing wrong?

Might need a content-length header in the request that is the byte_size of the encoded JSON string

EDIT: can’t find this ‘put_body_params’ function documented, you sure that’s the correct way to test such a thing with Plug? Are you testing Jason or?

Using put_body_params entirely short-circuits the parsing in Plug.Parser - that only happens when body_params is a %Plug.Unfetched{}.

You want to set the body to the unparsed JSON when calling Plug.Test.conn/3:

conn(:put, "/cars", body_params_json_string)
|> put_request_header("content-type", "application/json")
|> put_request_header("accept", "application/json")
1 Like