elixir-plug / plug

Compose web applications with functions
https://hex.pm/packages/plug
Other
2.84k stars 582 forks source link

Request paths with suffixes match dynamic segments without suffixes #1158

Closed thymusvulgaris closed 1 year ago

thymusvulgaris commented 1 year ago

Plug will match a request path with an extension, for example:

"/hello/world.json"

to a route with no extension, for example:

"/hello/:id"

To elaborate, given the following Plug:

defmodule AppRouter do
  use Plug.Router

  plug :match
  plug :dispatch

  get "/hello/:id" do
    send_resp(conn, 200, "world")
  end

  get "/hello/:id.json" do
    send_resp(conn, 200, "world")
  end  
end

The following expression matches (Elixir version 1.14.3):

iex(1)> import Plug.Test
Plug.Test
iex(2)> %Plug.Conn{private: %{plug_route: {"/hello/:id", _function}}} = AppRouter.call(conn(:get, "/hello/world.json"), [])

This is also confirmed with the following Router change and added test (to v1.14.2), which passes:

--- a/test/plug/router_test.exs
+++ b/test/plug/router_test.exs
@@ -103,8 +103,12 @@ defmodule Plug.RouterTest do
     get "/2/:bar" do
       resp(conn, 200, inspect(bar))
     end

+    get "/2/:bar.json" do
+      resp(conn, 200, inspect(bar))
+    end
+
     get "/3/bar-:bar" do
       resp(conn, 200, inspect(bar))
     end

@@ -247,8 +251,13 @@ defmodule Plug.RouterTest do
     conn = call(Sample, conn(:get, "/3/bar-value"))
     assert conn.resp_body == ~s("value")
   end

+  test "request path with suffix matches dynamic segment without suffix" do
+    conn = call(Sample, conn(:get, "/2/value.json"))
+    assert {"/2/:bar", _function} = conn.private.plug_route
+  end
+
   test "dispatch dynamic segment with suffix" do
     conn = call(Sample, conn(:get, "/9/value.json"))
     assert conn.resp_body == ~s("value")
     assert conn.params == %{"bar" => "value"}

Is there an option that allows compilation of the route "/2/:bar" such that it does not match request paths with extensions? The option should enable the following test to pass, given the above change:

      test "request path with suffix does not match dynamic segment without suffix" do
        conn = call(Sample, conn(:get, "/2/value.json"))
        assert {"/2/:bar.json", _function} = conn.private.plug_route    
      end
josevalim commented 1 year ago

You can have a plug that executes before the matching that strips the extension if you want. It won't be done out of the box because we simply compile to Elixir's pattern matching and Elixir's pattern matching can only match prefixes.