Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Uri parameters in vars expression #142

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
97 changes: 64 additions & 33 deletions lib/resty/radixtree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ local str_find = string.find
local str_lower = string.lower
local remove_tab = table.remove
local str_byte = string.byte
local sub_str = string.sub
local ASTERISK = str_byte("*")


Expand Down Expand Up @@ -150,6 +151,16 @@ local function has_suffix(s, suffix)
return rc == 0
end

local function has_prefix(s, prefix)
if type(s) ~= "string" or type(prefix) ~= "string" then
error("unexpected type: s:" .. type(s) .. ", prefix:" .. type(prefix))
end
if #s < #prefix then
return false
end
local rc = C.memcmp(s, prefix, #prefix)
return rc == 0
end

-- only work under lua51 or luajit
local function setmt__gc(t, mt)
Expand Down Expand Up @@ -479,9 +490,7 @@ local function common_route_data(path, route, route_opts, global_opts)
else
pos = str_find(path, '*', 1, true)
if pos then
if pos ~= #path then
route_opts.param = true
end
route_opts.param = true
path = path:sub(1, pos - 1)
route_opts.path_op = "<="
else
Expand Down Expand Up @@ -665,7 +674,7 @@ local function fetch_pat(path)
end
table.insert(names, name)
-- '.' matches any character except newline
res[i] = [=[((.|\n)*)]=]
res[i] = [=[((?:.|\n)*)]=]
end
end

Expand All @@ -680,7 +689,7 @@ local function fetch_pat(path)
end


local function compare_param(req_path, route, opts)
local function match_route_params(req_path, route, opts)
if not opts.matched and not route.param then
return true
end
Expand All @@ -700,21 +709,11 @@ local function compare_param(req_path, route, opts)
return false
end

if not opts.matched then
return true
end

for i, v in ipairs(m) do
local name = names[i]
if name and v then
opts.matched[name] = v
end
end
return true
return true, m, names
end


local function match_route_opts(route, opts, args)
local function match_route_opts(route, path, opts, args)
local method = opts.method
local opts_matched_exists = (opts.matched ~= nil)
if route.method ~= nil and route.method ~= 0 then
Expand Down Expand Up @@ -774,8 +773,35 @@ local function match_route_opts(route, opts, args)
end
end

local opts_vars = opts.vars or ngx_var
local param_matches, param_names
if route.param then
local matched
matched, param_matches, param_names = match_route_params(path, route, opts)
if not matched then
return false
end

-- Allow vars expr or filter_fun to use `uri_param_<name>`
if (route.vars or route.filter_fun) and param_names and param_matches then
-- clone table including the __index metamethod
local opts_vars_meta = getmetatable(opts_vars)
opts_vars = clone_tab(opts_vars)
boekkooi-lengoo marked this conversation as resolved.
Show resolved Hide resolved
if opts_vars_meta then
setmetatable(opts_vars, { __index = opts_vars_meta.__index })
end

for i, v in ipairs(param_matches) do
local name = param_names[i]
if name and v then
opts_vars["uri_param_" .. name] = v
end
end
end
end

if route.vars then
local ok, err = route.vars:eval(opts.vars, opts)
local ok, err = route.vars:eval(opts_vars, opts)
if not ok then
if ok == nil then
log_err("failed to eval expression: ", err)
Expand All @@ -792,30 +818,39 @@ local function match_route_opts(route, opts, args)
-- now we can safely clear the self.args
local args_len = args[0]
args[0] = nil
ok = fn(opts.vars or ngx_var, opts, unpack(args, 1, args_len))
ok = fn(opts_vars, opts, unpack(args, 1, args_len))
else
ok = fn(opts.vars or ngx_var, opts)
ok = fn(opts_vars, opts)
end

if not ok then
return false
end
end

-- Add matched info
if opts_matched_exists then
opts.matched._path = route.path_org

-- Add matched uri parameters
if param_names and param_matches then
for i, v in ipairs(param_matches) do
local name = param_names[i]
if name and v then
opts.matched[name] = v
end
end
end
end

return true
end


local function _match_from_routes(routes, path, opts, args)
local opts_matched_exists = (opts.matched ~= nil)
for _, route in ipairs(routes) do
if match_route_opts(route, opts, args) then
if compare_param(path, route, opts) then
if opts_matched_exists then
opts.matched._path = route.path_org
end
return route
end
if match_route_opts(route, path, opts, args) then
return route
end
end

Expand All @@ -834,12 +869,8 @@ local function match_route(self, path, opts, args)

local routes = self.hash_path[path]
if routes then
local opts_matched_exists = (opts.matched ~= nil)
for _, route in ipairs(routes) do
if match_route_opts(route, opts, args) then
if opts_matched_exists then
opts.matched._path = path
end
if match_route_opts(route, path, opts, args) then
return route
end
end
Expand Down
93 changes: 93 additions & 0 deletions t/vars.t
Original file line number Diff line number Diff line change
Expand Up @@ -566,3 +566,96 @@ metadata /aa
metadata /aa
nil
nil



=== TEST 20: param validation
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local radix = require("resty.radixtree")
local rx = radix.new({
{
paths = { "/user/:uuid" },
metadata = "metadata /name",
vars = {
{"uri_param_uuid", "~~", "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"}
}
},
})
local opts = {matched = {}}
local meta = rx:match("/user/5b3c7845-b45c-4dc8-8843-0349465e0e62", opts)
ngx.say("match meta: ", meta)
ngx.say("matched: ", json.encode(opts.matched))
opts.matched = {}
meta = rx:match("/user/1", opts)
ngx.say("match meta: ", meta)
ngx.say("matched: ", json.encode(opts.matched))
}
}
--- request
GET /t
--- no_error_log
[error]
--- response_body
match meta: metadata /name
matched: {"_path":"/user/:uuid","uuid":"5b3c7845-b45c-4dc8-8843-0349465e0e62"}
match meta: nil
matched: []



=== TEST 21: param validation with vars metamethods
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local radix = require("resty.radixtree")
local rx = radix.new({
{
paths = { "/user/:uuid" },
metadata = "metadata /name",
vars = {
{"uri_param_uuid", "~~", "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"},
{"hello", "==", "world"}
}
},
})

local var = { _ctx = { hello = "world" } }
local mt = {
__index = function(t, key)
return t._ctx[key]
end,

__newindex = function(t, key, val)
t._ctx[key] = val
end,
}
setmetatable(var, mt)

local opts = {matched = {}, vars = var}
local meta = rx:match("/user/5b3c7845-b45c-4dc8-8843-0349465e0e62", opts)
ngx.say("match meta: ", meta)
ngx.say("matched: ", json.encode(opts.matched))
ngx.say("vars: ", json.encode(var))

opts.matched = {}
meta = rx:match("/user/1", opts)
ngx.say("match meta: ", meta)
ngx.say("matched: ", json.encode(opts.matched))
ngx.say("vars: ", json.encode(var))
}
}
--- request
GET /t
--- no_error_log
[error]
--- response_body
match meta: metadata /name
matched: {"_path":"/user/:uuid","uuid":"5b3c7845-b45c-4dc8-8843-0349465e0e62"}
vars: {"_ctx":{"hello":"world"}}
match meta: nil
matched: []
vars: {"_ctx":{"hello":"world"}}
Loading