当我们发送请求时,我们在conn结构中具有当前路径信息。假设是补丁请求。
["v1", "users", "2"] or "v1/users/2"
我正在根据数据库中的路径信息编写用于用户授权的插件。路径在数据库中的存储方式如下:
"v1/users/:id"
这是我们从运行mix phx.routes
获得的路径。是否有可能我得到"v1/users/:id"
而不是"v1/users/2"
的当前路径?这样我就可以将它与数据库中存储的路径进行匹配。
有什么解决方法吗?
答案 0 :(得分:1)
一种非常hackey的方法是手动替换关联密钥路径中的值。
在conn
结构中,我们有path_info: ["v1", "users", "2"]
和path_params: %{"id" => "2"}
,所以我们可以这样做:
Enum.reduce(conn.path_params, conn.path_info, fn {key, value}, acc ->
index = Enum.find_index(acc, fn x -> x == value end)
List.replace_at(acc, index, ":#{key}")
end)
|> Enum.join("/")
输出将为v1/users/:id
。
请注意,如果您的参数值与路由的一部分相同(这似乎不太可能发生),则此操作将失败。另外,如果您有多个可以取相同值的参数,那么我们将依赖于path_params
中参数的顺序。
答案 1 :(得分:0)
鉴于您已在数据库中存储了一系列用户定义的路由,因此可以手动检查传入请求的路径是否与其中之一匹配。 正则表达式可以帮助您优雅地解决此问题(尽管它不如已编译的路由有效):
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
现在假设这是数据库中所有已定义路由的列表:
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",
]
然后RouteMatcher.find/2
函数将返回与给定路径匹配的第一条路线以及匹配的参数(如果没有路线匹配,它将仅返回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