diff --git a/lib/phoenix/router.ex b/lib/phoenix/router.ex index 599acea1b2..4cfb53f26b 100644 --- a/lib/phoenix/router.ex +++ b/lib/phoenix/router.ex @@ -845,4 +845,39 @@ defmodule Phoenix.Router do unquote(add_route(:forward, :*, path, plug, plug_opts, router_opts)) end end + + @doc """ + Returns the compile-time route info and runtime path params for a request. + + A map of metadata is returned with the following keys: + + * `:log` - the configured log level. For example `:debug` + * `:path_params` - the map of runtime path params + * `:pipe_through` - the list of pipelines for the route's scope, for example `[:browser]` + * `:plug` - the plug to dipatch the route to, for example `AppWeb.PostController` + * `:plug_opts` - the options to pass when calling the plug, for example: `:index` + * `:route` - the string route pattern, such as `"/posts/:id"` + + ## Examples + + iex> Phoenix.Router.route_info(AppWeb.Router, "GET", "/posts/123", "myhost") + %{ + log: :debug, + path_params: %{"id" => "123"}, + pipe_through: [:browser], + plug: AppWeb.PostController, + plug_opts: :show, + route: "/posts/:id", + } + + iex> Phoenix.Router.route_info(MyRouter, "GET", "/not-exists", "myhost") + :error + """ + def route_info(router, method, path, host) do + split_path = for segment <- String.split(path, "/"), segment != "", do: segment + case router.__match_route__(method, split_path, host) do + {%{} = metadata, _prepare, _pipeline, {_plug, _opts}} -> Map.delete(metadata, :conn) + :error -> :error + end + end end diff --git a/lib/phoenix/test/conn_test.ex b/lib/phoenix/test/conn_test.ex index b3ba49e8d9..6cf1d64de3 100644 --- a/lib/phoenix/test/conn_test.ex +++ b/lib/phoenix/test/conn_test.ex @@ -570,18 +570,14 @@ defmodule Phoenix.ConnTest do def redirected_params(%Plug.Conn{} = conn) do router = Phoenix.Controller.router_module(conn) %URI{path: path, host: host} = conn |> redirected_to() |> URI.parse() - path_info = split_path(path) - case router.__match_route__("GET", path_info, host || conn.host) do + case Phoenix.Router.route_info(router, "GET", path, host || conn.host) do :error -> raise Phoenix.Router.NoRouteError, conn: conn, router: router - {%{path_params: path_params}, _prepare, _pipes, _dispatch} -> + %{path_params: path_params} -> Enum.into(path_params, %{}, fn {key, val} -> {String.to_atom(key), val} end) end end - defp split_path(path) do - for segment <- String.split(path, "/"), segment != "", do: segment - end @doc """ Asserts an error was wrapped and sent with the given status. diff --git a/test/phoenix/router/routing_test.exs b/test/phoenix/router/routing_test.exs index 2bc08aa9a0..22240eb7af 100644 --- a/test/phoenix/router/routing_test.exs +++ b/test/phoenix/router/routing_test.exs @@ -36,7 +36,7 @@ defmodule Phoenix.Router.RoutingTest do get "/backups/*path", UserController, :image get "/static/images/icons/*image", UserController, :image - trace "/trace", UserController, :trace + trace("/trace", UserController, :trace) options "/options", UserController, :options connect "/connect", UserController, :connect match :move, "/move", UserController, :move @@ -189,26 +189,26 @@ defmodule Phoenix.Router.RoutingTest do test "logs controller and action with (path) parameters" do assert capture_log(fn -> call(Router, :get, "/users/1", foo: "bar") end) =~ """ - [debug] Processing with Phoenix.Router.RoutingTest.UserController.show/2 - Parameters: %{"foo" => "bar", "id" => "1"} - Pipelines: [] - """ + [debug] Processing with Phoenix.Router.RoutingTest.UserController.show/2 + Parameters: %{"foo" => "bar", "id" => "1"} + Pipelines: [] + """ end test "logs controller and action with filtered parameters" do assert capture_log(fn -> call(Router, :get, "/users/1", password: "bar") end) =~ """ - [debug] Processing with Phoenix.Router.RoutingTest.UserController.show/2 - Parameters: %{"id" => "1", "password" => "[FILTERED]"} - Pipelines: [] - """ + [debug] Processing with Phoenix.Router.RoutingTest.UserController.show/2 + Parameters: %{"id" => "1", "password" => "[FILTERED]"} + Pipelines: [] + """ end test "logs plug with pipeline and custom level" do assert capture_log(fn -> call(Router, :get, "/plug") end) =~ """ - [info] Processing with Phoenix.Router.RoutingTest.SomePlug - Parameters: %{} - Pipelines: [:noop] - """ + [info] Processing with Phoenix.Router.RoutingTest.SomePlug + Parameters: %{} + Pipelines: [:noop] + """ end test "does not log when log is set to false" do @@ -216,4 +216,35 @@ defmodule Phoenix.Router.RoutingTest do "Processing with Phoenix.Router.RoutingTest.SomePlug" end end + + test "route_info returns route string and path params" do + assert Phoenix.Router.route_info(Router, "GET", "foo/bar/baz", nil) == %{ + log: :debug, + path_params: %{"path" => ["foo", "bar", "baz"]}, + pipe_through: [], + plug: Phoenix.Router.RoutingTest.UserController, + plug_opts: :not_found, + route: "/*path" + } + + assert Phoenix.Router.route_info(Router, "GET", "users/1", nil) == %{ + log: :debug, + path_params: %{"id" => "1"}, + pipe_through: [], + plug: Phoenix.Router.RoutingTest.UserController, + plug_opts: :show, + route: "/users/:id", + } + + assert Phoenix.Router.route_info(Router, "GET", "/", "host") == %{ + log: :debug, + path_params: %{}, + pipe_through: [], + plug: Phoenix.Router.RoutingTest.UserController, + plug_opts: :index, + route: "/", + } + + assert Phoenix.Router.route_info(Router, "POST", "/not-exists", "host") == :error + end end diff --git a/test/phoenix/test/conn_test.exs b/test/phoenix/test/conn_test.exs index 89a810cbce..861562d617 100644 --- a/test/phoenix/test/conn_test.exs +++ b/test/phoenix/test/conn_test.exs @@ -40,6 +40,8 @@ defmodule Phoenix.Test.ConnTest do use Phoenix.ConnTest alias Phoenix.Test.ConnTest.{Router, RedirRouter} + @moduletag :capture_log + defmodule ConnError do defexception [message: nil, plug_status: 500] end @@ -188,7 +190,7 @@ defmodule Phoenix.Test.ConnTest do port: 111317, ssl_cert: <<1, 2, 3, 4>> } - conn = + conn = build_conn() |> Plug.Test.put_peer_data(peer_data)