Skip to content

Commit

Permalink
put_plug: Don't crash if plug is not installed and :plug is not used
Browse files Browse the repository at this point in the history
  • Loading branch information
wojtekmach committed Dec 11, 2023
1 parent 515bb89 commit 99512ad
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 83 deletions.
176 changes: 96 additions & 80 deletions lib/req/steps.ex
Original file line number Diff line number Diff line change
Expand Up @@ -996,114 +996,130 @@ defmodule Req.Steps do
end
end

defp run_plug(request) do
plug = request.options[:plug]
if Code.ensure_loaded?(Plug.Test) do
defp run_plug(request) do
plug = request.options[:plug]

req_body =
case request.body do
iodata when is_binary(iodata) or is_list(iodata) ->
IO.iodata_to_binary(iodata)
req_body =
case request.body do
iodata when is_binary(iodata) or is_list(iodata) ->
IO.iodata_to_binary(iodata)

nil ->
""
nil ->
""

enumerable ->
enumerable |> Enum.to_list() |> IO.iodata_to_binary()
end
enumerable ->
enumerable |> Enum.to_list() |> IO.iodata_to_binary()
end

req_headers =
if unquote(Req.MixProject.legacy_headers_as_lists?()) do
request.headers
else
for {name, values} <- request.headers,
value <- values do
{name, value}
req_headers =
if unquote(Req.MixProject.legacy_headers_as_lists?()) do
request.headers
else
for {name, values} <- request.headers,
value <- values do
{name, value}
end
end
end

conn = Plug.Test.conn(request.method, request.url, req_body)
conn = Plug.Test.conn(request.method, request.url, req_body)

conn = put_in(conn.req_headers, req_headers)
conn = put_in(conn.req_headers, req_headers)

case request.into do
nil ->
conn = call_plug(conn, plug)
case request.into do
nil ->
conn = call_plug(conn, plug)

response =
Req.Response.new(
status: conn.status,
headers: conn.resp_headers,
body: conn.resp_body
)
response =
Req.Response.new(
status: conn.status,
headers: conn.resp_headers,
body: conn.resp_body
)

{request, response}
{request, response}

fun when is_function(fun, 2) ->
conn = call_plug(conn, plug)
fun when is_function(fun, 2) ->
conn = call_plug(conn, plug)

response =
Req.Response.new(
status: conn.status,
headers: conn.resp_headers
)
response =
Req.Response.new(
status: conn.status,
headers: conn.resp_headers
)

Enum.reduce_while(plug_body_chunks(conn), {request, response}, fn chunk, acc ->
fun.({:data, chunk}, acc)
end)
Enum.reduce_while(plug_body_chunks(conn), {request, response}, fn chunk, acc ->
fun.({:data, chunk}, acc)
end)

collectable ->
{acc, collector} = Collectable.into(collectable)
conn = call_plug(conn, plug)
collectable ->
{acc, collector} = Collectable.into(collectable)
conn = call_plug(conn, plug)

response =
Req.Response.new(
status: conn.status,
headers: conn.resp_headers
)
response =
Req.Response.new(
status: conn.status,
headers: conn.resp_headers
)

acc =
Enum.reduce(plug_body_chunks(conn), acc, fn chunk, acc ->
collector.(acc, {:cont, chunk})
end)
acc =
Enum.reduce(plug_body_chunks(conn), acc, fn chunk, acc ->
collector.(acc, {:cont, chunk})
end)

acc = collector.(acc, :done)
{request, %{response | body: acc}}
acc = collector.(acc, :done)
{request, %{response | body: acc}}
end
end
end

defp plug_body_chunks(conn) do
%Plug.Conn{adapter: {Plug.Adapters.Test.Conn, %{ref: ref}}} = conn
defp plug_body_chunks(conn) do
%Plug.Conn{adapter: {Plug.Adapters.Test.Conn, %{ref: ref}}} = conn

# If plug sent response (send_resp/send_file), use that, otherwise get sent chunks.
receive do
{^ref, response} ->
{_status, _headers, body} = response
[body]
after
0 ->
plug_sent_chunks(conn)
# If plug sent response (send_resp/send_file) then use that, otherwise get sent chunks.
receive do
{^ref, response} ->
{_status, _headers, body} = response
[body]
after
0 ->
plug_sent_chunks(conn)
end
end
end

defp plug_sent_chunks(conn) do
# TODO: remove when we depend on Plug 1.16
if Code.ensure_loaded?(Plug.Test) and function_exported?(Plug.Test, :sent_chunks, 1) do
Plug.Test.sent_chunks(conn)
if function_exported?(Plug.Test, :sent_chunks, 1) do
defp plug_sent_chunks(conn) do
Plug.Test.sent_chunks(conn)
end
else
raise "using :plug and :into with chunked response requires Plug 1.16"
defp plug_sent_chunks(_conn) do
raise "using :plug and :into with chunked response requires Plug 1.16"
end
end
end

defp call_plug(conn, plug) when is_atom(plug) do
plug.call(conn, [])
end
defp call_plug(conn, plug) when is_atom(plug) do
plug.call(conn, [])
end

defp call_plug(conn, {plug, options}) when is_atom(plug) do
plug.call(conn, plug.init(options))
end
defp call_plug(conn, {plug, options}) when is_atom(plug) do
plug.call(conn, plug.init(options))
end

defp call_plug(conn, plug) when is_function(plug, 1) do
plug.(conn)
defp call_plug(conn, plug) when is_function(plug, 1) do
plug.(conn)
end
else
defp run_plug(_request) do
Logger.error("""
Could not find plug dependency.
Please add :plug to your dependencies:
{:plug, "~> 1.0"}
""")

raise "missing plug dependency"
end
end

defmodule CollectableWithChecksum do
Expand Down
4 changes: 1 addition & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ defmodule Req.MixProject do
:brotli,
:ezstd,
# TODO: Wait for async_request/3
Finch,
# TODO: Wait for Plug 1.15
Plug.Conn
Finch
]
]
]
Expand Down

0 comments on commit 99512ad

Please sign in to comment.