diff --git a/lib/tesla/middleware/form_urlencoded.ex b/lib/tesla/middleware/form_urlencoded.ex index 806d1a79..d32a9dde 100644 --- a/lib/tesla/middleware/form_urlencoded.ex +++ b/lib/tesla/middleware/form_urlencoded.ex @@ -183,6 +183,12 @@ defmodule Tesla.Middleware.FormUrlencoded do field on Stripe-style update endpoints. - Map keys are not ordered; keyword lists and lists of 2-tuples preserve the order you give them. + - A body that is already a binary is left untouched and no + `content-type` header is added, matching `Tesla.Middleware.JSON`. This + is what lets both middlewares share one stack: `JSON` encodes the body + and claims `application/json`, and this middleware then sees a binary + and stays out of the way rather than appending a second `content-type`. + Encode the body yourself and you own its `content-type` too. - Decoding stays flat. `Plug.Conn.Query.decode/1` will parse the bracket keys into nested maps, but indexed lists come back as maps keyed by string indices (`"0"`, `"1"`, …), not as Elixir lists, so @@ -221,10 +227,10 @@ defmodule Tesla.Middleware.FormUrlencoded do defp encodable?(%{body: {:form_urlencoded, _}}), do: true defp encodable?(%{body: nil}), do: false + defp encodable?(%{body: body}) when is_binary(body), do: false defp encodable?(%{body: %Tesla.Multipart{}}), do: false defp encodable?(_), do: true - defp encode_body(body, _opts) when is_binary(body), do: body defp encode_body({:form_urlencoded, data}, opts), do: do_encode(data, opts) defp encode_body(body, opts), do: do_encode(body, opts) diff --git a/test/tesla/middleware/form_urlencoded_test.exs b/test/tesla/middleware/form_urlencoded_test.exs index f42bb051..cacc6483 100644 --- a/test/tesla/middleware/form_urlencoded_test.exs +++ b/test/tesla/middleware/form_urlencoded_test.exs @@ -39,11 +39,45 @@ defmodule Tesla.Middleware.FormUrlencodedTest do assert env.body == "application/x-www-form-urlencoded" end + test "no content-type is set if body is binary" do + assert {:ok, env} = Client.post("/check_incoming_content_type", "data") + assert env.body == nil + end + test "decode response" do assert {:ok, env} = Client.get("/decode_response") assert env.body == %{"x" => "1", "y" => "2"} end + defmodule JsonStackClient do + use Tesla + + plug Tesla.Middleware.JSON + plug Tesla.Middleware.FormUrlencoded + + adapter fn env -> + {:ok, + %{ + env + | status: 200, + headers: [{"content-type", "text/html"}], + body: Tesla.get_headers(env, "content-type") + }} + end + end + + describe "sharing a stack with Tesla.Middleware.JSON" do + test "a json body is left with a single application/json content-type" do + assert {:ok, env} = JsonStackClient.post("/post", %{"foo" => "bar"}) + assert env.body == ["application/json"] + end + + test "a tagged form body is left with a single form content-type" do + assert {:ok, env} = JsonStackClient.post("/post", {:form_urlencoded, %{"foo" => "bar"}}) + assert env.body == ["application/x-www-form-urlencoded"] + end + end + defmodule MultipartClient do use Tesla