Page MenuHomePhorge

No OneTemporary

Size
8 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/tesla/middleware/form_urlencoded.ex b/lib/tesla/middleware/form_urlencoded.ex
index 5bbf346..65b6ac9 100644
--- a/lib/tesla/middleware/form_urlencoded.ex
+++ b/lib/tesla/middleware/form_urlencoded.ex
@@ -1,114 +1,150 @@
defmodule Tesla.Middleware.FormUrlencoded do
@moduledoc """
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/1`
- `:encode` - encoding function, defaults to `URI.encode_query/1`
## 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"}})
```
"""
@behaviour Tesla.Middleware
@content_type "application/x-www-form-urlencoded"
@impl Tesla.Middleware
def call(env, next, opts) do
env
|> encode(opts)
|> Tesla.run(next)
|> case do
{:ok, env} -> {:ok, decode(env, opts)}
error -> error
end
end
- defp encode(env, opts) do
+ @doc """
+ Encode response body as querystring.
+
+ It is used by `Tesla.Middleware.EncodeFormUrlencoded`.
+ """
+ def encode(env, opts) do
if encodable?(env) do
env
|> 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, _opts) when is_binary(body), do: body
defp encode_body(body, opts), do: do_encode(body, opts)
- defp decode(env, opts) do
+ @doc """
+ Decode response body as querystring.
+
+ It is used by `Tesla.Middleware.DecodeFormUrlencoded`.
+ """
+ def decode(env, opts) do
if decodable?(env) do
env
|> 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, opts), do: do_decode(body, opts)
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
+
+defmodule Tesla.Middleware.DecodeFormUrlencoded do
+ @behaviour Tesla.Middleware
+
+ @impl true
+ def call(env, next, opts) do
+ opts = opts || []
+
+ with {:ok, env} <- Tesla.run(env, next) do
+ {:ok, Tesla.Middleware.FormUrlencoded.decode(env, opts)}
+ end
+ end
+end
+
+defmodule Tesla.Middleware.EncodeFormUrlencoded do
+ @behaviour Tesla.Middleware
+
+ @impl true
+ def call(env, next, opts) do
+ opts = opts || []
+
+ with env <- Tesla.Middleware.FormUrlencoded.encode(env, opts) do
+ Tesla.run(env, next)
+ end
+ end
+end
diff --git a/test/tesla/middleware/form_urlencoded_test.exs b/test/tesla/middleware/form_urlencoded_test.exs
index 25f392e..0a6234e 100644
--- a/test/tesla/middleware/form_urlencoded_test.exs
+++ b/test/tesla/middleware/form_urlencoded_test.exs
@@ -1,126 +1,151 @@
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
+
+ describe "Encode / Decode" do
+ defmodule EncodeDecodeFormUrlencodedClient do
+ use Tesla
+
+ plug Tesla.Middleware.DecodeFormUrlencoded
+ plug Tesla.Middleware.EncodeFormUrlencoded
+
+ adapter fn env ->
+ {status, headers, body} =
+ case env.url do
+ "/foo2baz" ->
+ {200, [{"content-type", "application/x-www-form-urlencoded"}],
+ env.body |> String.replace("foo", "baz")}
+ end
+
+ {:ok, %{env | status: status, headers: headers, body: body}}
+ end
+ end
+
+ test "work without options" do
+ assert {:ok, env} = EncodeDecodeFormUrlencodedClient.post("/foo2baz", %{"foo" => "bar"})
+ assert env.body == %{"baz" => "bar"}
+ end
+ end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 6:42 PM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38524
Default Alt Text
(8 KB)

Event Timeline