Skip to content

Commit

Permalink
Custom parameter/segment regex
Browse files Browse the repository at this point in the history
Allow users to use a custom regex to match a parameter.
  • Loading branch information
boekkooi-lengoo committed Dec 19, 2023
1 parent 2f6e491 commit 546c739
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 17 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,21 @@ local rx = radix.new({
})
```

### Parameters in Path with a custom regex

You can specify parameters on a path and use a regex to match the parameter segment of the path (see `hsegment` of [RFC1738](https://www.rfc-editor.org/rfc/rfc1738.txt).
It is not possible to match multiple path segements in this way.

```lua
local rx = radix.new({
{
-- matches with `/user/john` but not `/user/` or `/user`
paths = {"/user/: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}"}, -- for `/user/5b3c7845-b45c-4dc8-8843-0349465e0e62`, `opts.matched.user` will be `5b3c7845-b45c-4dc8-8843-0349465e0e62`
metadata = "metadata /user",
}
})
```

[Back to TOC](#table-of-contents)

# Installation
Expand Down
49 changes: 32 additions & 17 deletions lib/resty/radixtree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ end
local function fetch_pat(path)
local pat = lru_pat:get(path)
if pat then
return pat[1], pat[2] -- pat, names
return pat -- pat
end

clear_tab(tmp)
Expand All @@ -646,28 +646,39 @@ local function fetch_pat(path)
return false
end

local names = {}
local name, pos, regex
for i, item in ipairs(res) do
local first_byte = item:byte(1, 1)
if first_byte == string.byte(":") then
table.insert(names, res[i]:sub(2))
-- See https://www.rfc-editor.org/rfc/rfc1738.txt BNF for specific URL schemes
res[i] = [=[([\w\-_;:@&=!',\%\$\.\+\*\(\)]+)]=]
pos = str_find(res[i], ":", 3, true)
if pos and pos+1 < res[i]:len() then
name = res[i]:sub(2, pos - 1)
regex = res[i]:sub(pos+1)
else
name = res[i]:sub(2)
-- See https://www.rfc-editor.org/rfc/rfc1738.txt BNF for specific URL schemes
regex = [=[[\w\-_;:@&=!',\%\$\.\+\*\(\)]+]=]
end

res[i] = '(?<' .. name .. '>' .. regex .. ')'
elseif first_byte == string.byte("*") then
local name = res[i]:sub(2)
name = res[i]:sub(2)
if name == "" then
name = ":ext"
name = "__ext"
end
table.insert(names, name)

-- '.' matches any character except newline
res[i] = [=[((.|\n)*)]=]
res[i] = '(?<' .. name .. '>' .. [=[(?:.|\n)*)]=]
end
end

if name == nil then
return false
end

pat = table.concat(res, [[\/]])
lru_pat:set(path, {pat, names}, 60 * 60)
return pat, names
lru_pat:set(path, pat, 60 * 60)
return pat
end


Expand All @@ -676,9 +687,9 @@ local function compare_param(req_path, route, opts)
return true
end

local pat, names = fetch_pat(route.path_org)
local pat = fetch_pat(route.path_org)
log_debug("pcre pat: ", pat)
if #names == 0 then
if pat == false then
return true
end

Expand All @@ -695,10 +706,14 @@ local function compare_param(req_path, route, opts)
return true
end

for i, v in ipairs(m) do
local name = names[i]
if name and v then
opts.matched[name] = v
for k, v in pairs(m) do
if type(k) == "string" and v then
-- Capture groups can't start with a colon.
if k == "__ext" then
opts.matched[":ext"] = v
else
opts.matched[k] = v
end
end
end
return true
Expand Down
36 changes: 36 additions & 0 deletions t/parameter.t
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,39 @@ GET /t
--- response_body
match meta: /user/:user
match meta: /user/:user/age/:age
=== TEST 14: /user/: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}
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local radix = require("resty.radixtree")
local rx = radix.new({
{
paths = { "/user/: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})" },
metadata = "metadata /name",
},
})
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:([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})","uuid":"5b3c7845-b45c-4dc8-8843-0349465e0e62"}
match meta: nil
matched: []

0 comments on commit 546c739

Please sign in to comment.