Given that you have a list of user-defined routes stored in the database, you can manually check if the path of an incoming request matches one of them. Regex can help you solve this problem gracefully (although it won't be as efficient as compiled routes):
defmodule RouteMatcher do
def find(routes, path) do
Enum.find_value(routes, nil, &match(&1, path))
end
defp match(route, path) do
pattern = String.replace(route, ~r/:(\w+)/, ~S"(?<\g{1}>[\w-]+)")
regex = ~r/^#{pattern}$/
case Regex.named_captures(regex, path) do
nil -> nil
map -> {route, map}
end
end
end
Now suppose this is the list all defined routes in the database:
routes = [
"/v1/users",
"/v1/users/:user_id",
"/v1/users/:user_id/posts",
"/v1/users/:user_id/posts/:post_id",
"/v1/users/:user_id/posts/:post_id/:comment_id",
]
Then the RouteMatcher.find/2
function will return the first route that matches a given path along with the matched params (if no route matches it will simply return nil
):
RouteMatcher.find(routes, "/v1/users")
#=> {"/v1/users", %{}}
RouteMatcher.find(routes, "/v1/users/psy")
#=> {"/v1/users/:user_id", %{"user_id" => "psy"}}
RouteMatcher.find(routes, "/v1/users/psy/posts")
#=> {"/v1/users/:user_id/posts", %{"user_id" => "psy"}}
RouteMatcher.find(routes, "/v1/users/psy/posts/hello-world")
#=> {"/v1/users/:user_id/posts/:post_id", %{"post_id" => "hello-world", "user_id" => "psy"}}
RouteMatcher.find(routes, "/v1/users/psy/posts/hello-world/45")
#=> {"/v1/users/:user_id/posts/:post_id/:comment_id", %{"comment_id" => "45", "post_id" => "hello-world", "user_id" => "psy"}}
RouteMatcher.find(routes, "/unknown/route")
#=> nil