Verified routes ~p sigil replaces / with %2F for route with or without params

Hello everyone,

I’m using ~p in one of my liveviews to push_patch based on an event and the resulting path from the snippet below appears to be messed up where the /s are replaced with %2Fs regardless of whether I include query params or not. I’m not sure where I’m going wrong here, any help please?

def handle_info({:update, opts}, socket) do
    params = PaginationForm.merge_and_sanitize_params(socket, opts)
    current_path = socket.assigns.current_path
    |> String.trim_leading("/")
    path = ~p"/#{current_path}"
    {:noreply, push_patch(socket, to: path, replace: true)}

affixing the query params with path = ~p"/#{current_path}?#{params}" has no effect. The resulting path/error is like so:

** (ArgumentError) cannot push_patch/2 to %URI{scheme: "http", 
  userinfo: nil, 
  host: "localhost", 
  port: 4000, 
  path: "/en%2Fitems%2F1%2Fstatement", 
  query:  query: "page=1&page_size=10", 
  fragment: nil} 
because the given path does not point to the current root view

The relevant router path is defined in a locale scope:

  scope "/:locale", MyAppWeb do
    pipe_through [:browser, :localization, :require_authenticated_user]

    live "/items/:id/statement", ItemLive.Show, :statement

The app uses Phoenix 1.7.11 (recent upgrade)

Thanks in advance!

You can only use interpolation with constructs that implement the Phoenix.Param protocol. You are passing it a string which happens to contain /s but it’s just interpreting it has a single path fragment. There isn’t much point in wrapping a current_path in ~p as it should already be verified up the call stack that it’s a valid path.


Verified routes allow interpolation only for individual path segments, because that’s the only way they can deal with dynamic parts of a path without loosing all capability of verifying anything about the path. Hence all / are escaped, because the expectation is that you just pass a single path segment.

For ~p"/#{current_path}" you can just as well not use sigil p to begin with. It couldn’t validate anything about current_path anyways, given nothing about it is known at compile time.


@sodapopcan @LostKobrakai both of what you said makes sense, thanks for explaining the basics, I would have tagged both answers as acceptable solution but that’s not possible :slight_smile:
I was hoping that I could interpolate the whole thing to get the query params piece automatically taken care of, converting the map to a query string. Guess I’ll URI.merge the two parts then :sweat_smile:

1 Like