diff --git a/lib/phoenix_live_view.ex b/lib/phoenix_live_view.ex index 142d5aaa65..c0edbe8249 100644 --- a/lib/phoenix_live_view.ex +++ b/lib/phoenix_live_view.ex @@ -917,30 +917,51 @@ defmodule Phoenix.LiveView do ## Options * `:to` - the path to redirect to. It must always be a local path + * `:status` - the HTTP status code to use for the redirect. Defaults to 302. * `:external` - an external path to redirect to. Either a string or `{scheme, url}` to redirect to a custom scheme ## Examples {:noreply, redirect(socket, to: "/")} + {:noreply, redirect(socket, to: "/", status: 301)} {:noreply, redirect(socket, external: "https://example.com")} """ - def redirect(socket, opts \\ []) + def redirect(socket, opts \\ []) do + cond do + Keyword.has_key?(opts, :to) -> + do_internal_redirect(socket, Keyword.fetch!(opts, :to), Keyword.get(opts, :status)) - def redirect(%Socket{} = socket, to: url) do + Keyword.has_key?(opts, :external) -> + do_external_redirect(socket, Keyword.fetch!(opts, :external), Keyword.get(opts, :status)) + + true -> + raise ArgumentError, "expected :to or :external option in redirect/2" + end + end + + defp do_internal_redirect(%Socket{} = socket, url, redirect_status) do validate_local_url!(url, "redirect/2") - put_redirect(socket, {:redirect, %{to: url}}) + + put_redirect(socket, {:redirect, add_redirect_status(%{to: url}, redirect_status)}) end - def redirect(%Socket{} = socket, external: url) do + defp do_external_redirect(%Socket{} = socket, url, redirect_status) do case url do {scheme, rest} -> - put_redirect(socket, {:redirect, %{external: "#{scheme}:#{rest}"}}) + put_redirect( + socket, + {:redirect, add_redirect_status(%{external: "#{scheme}:#{rest}"}, redirect_status)} + ) url when is_binary(url) -> external_url = Phoenix.LiveView.Utils.valid_string_destination!(url, "redirect/2") - put_redirect(socket, {:redirect, %{external: external_url}}) + + put_redirect( + socket, + {:redirect, add_redirect_status(%{external: external_url}, redirect_status)} + ) other -> raise ArgumentError, @@ -948,9 +969,8 @@ defmodule Phoenix.LiveView do end end - def redirect(%Socket{}, _) do - raise ArgumentError, "expected :to or :external option in redirect/2" - end + defp add_redirect_status(opts, nil), do: opts + defp add_redirect_status(opts, status), do: Map.put(opts, :status, status) @doc """ Annotates the socket for navigation within the current LiveView. diff --git a/lib/phoenix_live_view/controller.ex b/lib/phoenix_live_view/controller.ex index 2f43aee527..221030a2fe 100644 --- a/lib/phoenix_live_view/controller.ex +++ b/lib/phoenix_live_view/controller.ex @@ -48,9 +48,12 @@ defmodule Phoenix.LiveView.Controller do ) {:stop, %Socket{redirected: {:redirect, opts}} = socket} -> + redirect_opts = opts |> Map.drop([:status]) |> Map.to_list() + conn + |> put_status(opts) |> put_flash(LiveView.Utils.get_flash(socket)) - |> Phoenix.Controller.redirect(Map.to_list(opts)) + |> Phoenix.Controller.redirect(redirect_opts) {:stop, %Socket{redirected: {:live, _, %{to: to}}} = socket} -> conn @@ -60,6 +63,9 @@ defmodule Phoenix.LiveView.Controller do end end + defp put_status(conn, %{status: status}), do: Plug.Conn.put_status(conn, status) + defp put_status(conn, %{}), do: conn + defp ensure_format(conn) do if Phoenix.Controller.get_format(conn) do conn diff --git a/test/phoenix_live_view/integrations/params_test.exs b/test/phoenix_live_view/integrations/params_test.exs index 1577d789a9..f58f5bd170 100644 --- a/test/phoenix_live_view/integrations/params_test.exs +++ b/test/phoenix_live_view/integrations/params_test.exs @@ -95,6 +95,16 @@ defmodule Phoenix.LiveView.ParamsTest do |> redirected_to() == "/" end + test "hard redirects with a custom status", %{conn: conn} do + assert conn + |> put_serialized_session( + :on_handle_params, + &{:noreply, LiveView.redirect(&1, to: "/", status: 301)} + ) + |> get("/counter/123?from=handle_params") + |> redirected_to(301) == "/" + end + test "hard redirect with flash message", %{conn: conn} do conn = put_serialized_session(conn, :on_handle_params, fn socket -> diff --git a/test/phoenix_live_view_test.exs b/test/phoenix_live_view_test.exs index 3719589a24..ac120affd0 100644 --- a/test/phoenix_live_view_test.exs +++ b/test/phoenix_live_view_test.exs @@ -242,6 +242,14 @@ defmodule Phoenix.LiveViewUnitTest do assert redirect(@socket, to: "/foo").redirected == {:redirect, %{to: "/foo"}} end + test "accepts a custom redirect status for local / external paths" do + assert redirect(@socket, to: "/foo", status: 301).redirected == + {:redirect, %{to: "/foo", status: 301}} + + assert redirect(@socket, external: "http://foo.com/bar", status: 301).redirected == + {:redirect, %{external: "http://foo.com/bar", status: 301}} + end + test "allows external paths" do assert redirect(@socket, external: "http://foo.com/bar").redirected == {:redirect, %{external: "http://foo.com/bar"}}