Page MenuHomePhorge

No OneTemporary

Size
7 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/tesla/middleware/form_urlencoded.ex b/lib/tesla/middleware/form_urlencoded.ex
index e2680b0..2123582 100644
--- a/lib/tesla/middleware/form_urlencoded.ex
+++ b/lib/tesla/middleware/form_urlencoded.ex
@@ -1,77 +1,108 @@
defmodule Tesla.Middleware.FormUrlencoded do
@behaviour Tesla.Middleware
@moduledoc """
- Send request body as application/x-www-form-urlencoded
-
- Longer description, including e.g. additional dependencies.
+ Send request body as `application/x-www-form-urlencoded`.
+ Performs encoding of `body` from a `Map` such as `%{"foo" => "bar"}` into
+ url encoded data.
+ Performs decoding of the response into a map when urlencoded and content-type
+ is `application/x-www-form-urlencoded`, so `"foo=bar"` becomes
+ `%{"foo" => "bar"}`.
### Example usage
```
defmodule Myclient do
use Tesla
plug Tesla.Middleware.FormUrlencoded
end
Myclient.post("/url", %{key: :value})
```
+
+ ### Options
+ - `:decode` - decoding function, defaults to `URI.decode_query`
+ - `:encode` - encoding function, defaults to `URI.encode_query`
+
+ ### Nested Maps
+ Natively, nested maps are not supported in the body, so
+ `%{"foo" => %{"bar" => "baz"}}` won't be encoded and raise an error.
+ Support for this specific case is obtained by configuring the middleware to
+ encode (and decode) with `Plug.Conn.Query`
+
+ ```
+ defmodule Myclient do
+ use Tesla
+
+ plug Tesla.Middleware.FormUrlencoded,
+ encode: &Plug.Conn.Query.encode/1,
+ decode: &Plug.Conn.Query.decode/1
+ end
+
+ Myclient.post("/url", %{key: %{nested: "value"}})
"""
@content_type "application/x-www-form-urlencoded"
- def call(env, next, _opts) do
+ def call(env, next, opts) do
env
- |> encode()
+ |> encode(opts)
|> Tesla.run(next)
|> case do
- {:ok, env} -> {:ok, decode(env)}
+ {:ok, env} -> {:ok, decode(env, opts)}
error -> error
end
end
- defp encode(env) do
+ defp encode(env, opts) do
if encodable?(env) do
env
- |> Map.update!(:body, &encode_body(&1))
+ |> Map.update!(:body, &encode_body(&1, opts))
|> Tesla.put_headers([{"content-type", @content_type}])
else
env
end
end
defp encodable?(%{body: nil}), do: false
defp encodable?(%{body: %Tesla.Multipart{}}), do: false
defp encodable?(_), do: true
- defp encode_body(body) when is_binary(body), do: body
- defp encode_body(body), do: do_encode(body)
+ defp encode_body(body, _opts) when is_binary(body), do: body
+ defp encode_body(body, opts), do: do_encode(body, opts)
- defp decode(env) do
+ defp decode(env, opts) do
if decodable?(env) do
env
- |> Map.update!(:body, &decode_body(&1))
+ |> Map.update!(:body, &decode_body(&1, opts))
else
env
end
end
defp decodable?(env), do: decodable_body?(env) && decodable_content_type?(env)
defp decodable_body?(env) do
(is_binary(env.body) && env.body != "") || (is_list(env.body) && env.body != [])
end
defp decodable_content_type?(env) do
case Tesla.get_header(env, "content-type") do
nil -> false
content_type -> String.starts_with?(content_type, @content_type)
end
end
- defp decode_body(body), do: do_decode(body)
+ defp decode_body(body, opts), do: do_decode(body, opts)
- defp do_encode(data), do: URI.encode_query(data)
- defp do_decode(data), do: URI.decode_query(data)
+ defp do_encode(data, opts) do
+ encoder = Keyword.get(opts, :encode, &URI.encode_query/1)
+ encoder.(data)
+ end
+
+ defp do_decode(data, opts) do
+ decoder = Keyword.get(opts, :decode, &URI.decode_query/1)
+ decoder.(data)
+ end
end
diff --git a/test/tesla/middleware/form_urlencoded_test.exs b/test/tesla/middleware/form_urlencoded_test.exs
index e54d634..25f392e 100644
--- a/test/tesla/middleware/form_urlencoded_test.exs
+++ b/test/tesla/middleware/form_urlencoded_test.exs
@@ -1,73 +1,126 @@
defmodule Tesla.Middleware.FormUrlencodedTest do
use ExUnit.Case
defmodule Client do
use Tesla
plug Tesla.Middleware.FormUrlencoded
adapter fn env ->
{status, headers, body} =
case env.url do
"/post" ->
{201, [{"content-type", "text/html"}], env.body}
"/check_incoming_content_type" ->
{201, [{"content-type", "text/html"}], Tesla.get_header(env, "content-type")}
"/decode_response" ->
{200, [{"content-type", "application/x-www-form-urlencoded; charset=utf-8"}],
"x=1&y=2"}
end
{:ok, %{env | status: status, headers: headers, body: body}}
end
end
test "encode body as application/x-www-form-urlencoded" do
assert {:ok, env} = Client.post("/post", %{"foo" => "%bar "})
assert URI.decode_query(env.body) == %{"foo" => "%bar "}
end
test "leave body alone if binary" do
assert {:ok, env} = Client.post("/post", "data")
assert env.body == "data"
end
test "check header is set as application/x-www-form-urlencoded" do
assert {:ok, env} = Client.post("/check_incoming_content_type", %{"foo" => "%bar "})
assert env.body == "application/x-www-form-urlencoded"
end
test "decode response" do
assert {:ok, env} = Client.get("/decode_response")
assert env.body == %{"x" => "1", "y" => "2"}
end
defmodule MultipartClient do
use Tesla
plug Tesla.Middleware.FormUrlencoded
adapter fn %{url: url, body: %Tesla.Multipart{}} = env ->
{status, headers, body} =
case url do
"/upload" ->
{200, [{"content-type", "text/html"}], "ok"}
end
{:ok, %{env | status: status, headers: headers, body: body}}
end
end
test "skips encoding multipart bodies" do
alias Tesla.Multipart
mp =
Multipart.new()
|> Multipart.add_field("param", "foo")
assert {:ok, env} = MultipartClient.post("/upload", mp)
assert env.body == "ok"
end
+
+ defmodule NewEncoderClient do
+ use Tesla
+
+ def encoder(_data) do
+ "iamencoded"
+ end
+
+ plug Tesla.Middleware.FormUrlencoded, encode: &encoder/1
+
+ adapter fn env ->
+ {status, headers, body} =
+ case env.url do
+ "/post" ->
+ {201, [{"content-type", "text/html"}], env.body}
+ end
+
+ {:ok, %{env | status: status, headers: headers, body: body}}
+ end
+ end
+
+ test "uses encoder configured in options" do
+ {:ok, env} = NewEncoderClient.post("/post", %{"foo" => "bar"})
+
+ assert env.body == "iamencoded"
+ end
+
+ defmodule NewDecoderClient do
+ use Tesla
+
+ def decoder(_data) do
+ "decodedbody"
+ end
+
+ plug Tesla.Middleware.FormUrlencoded, decode: &decoder/1
+
+ adapter fn env ->
+ {status, headers, body} =
+ case env.url do
+ "/post" ->
+ {200, [{"content-type", "application/x-www-form-urlencoded; charset=utf-8"}],
+ "x=1&y=2"}
+ end
+
+ {:ok, %{env | status: status, headers: headers, body: body}}
+ end
+ end
+
+ test "uses decoder configured in options" do
+ {:ok, env} = NewDecoderClient.post("/post", %{"foo" => "bar"})
+
+ assert env.body == "decodedbody"
+ end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 19, 3:22 PM (21 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1769591
Default Alt Text
(7 KB)

Event Timeline