Skip to content

Commit

Permalink
feat: define regex pattern in variables
Browse files Browse the repository at this point in the history
  • Loading branch information
vm-001 committed Feb 29, 2024
1 parent f1ca79a commit c968048
Show file tree
Hide file tree
Showing 20 changed files with 484 additions and 66 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/examples.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
name: examples

on:
pull_request:
paths-ignore:
- '**/*.md'
push:
branches:
- main
Expand Down Expand Up @@ -39,3 +36,4 @@ jobs:
run: |
lua examples/example.lua
lua examples/custom-matcher.lua
lua examples/regular-expression.lua
59 changes: 57 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
- '**/*.md'

jobs:
test:
lua:
runs-on: ubuntu-latest

strategy:
Expand Down Expand Up @@ -48,6 +48,7 @@ jobs:
run: |
lua examples/example.lua
lua examples/custom-matcher.lua
lua examples/regular-expression.lua
- name: report test coverage
if: success()
Expand All @@ -58,4 +59,58 @@ jobs:

- name: benchmark
run: |
make bench CMD=lua
make bench CMD=lua
openresty:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
openrestyVersion: [ "1.19.9.1", "1.21.4.3", "1.25.3.1" ]

steps:
- name: checkout source code
uses: actions/checkout@v3

- name: install Lua/LuaJIT
uses: leafo/gh-actions-lua@v8
with:
luaVersion: "luajit-openresty"

- uses: leafo/gh-actions-openresty@v1
with:
openrestyVersion: ${{ matrix.openrestyVersion }}

- name: install LuaRocks
uses: leafo/gh-actions-luarocks@v4

- name: install dependencies
run: |
luarocks install busted
luarocks install luacov-coveralls
- name: build
run: |
make install
- name: run tests
run: |
bin/resty_busted --coverage spec/
- name: samples
run: |
resty examples/example.lua
resty examples/custom-matcher.lua
resty examples/regular-expression.lua
- name: report test coverage
if: success()
continue-on-error: true
run: luacov-coveralls
env:
COVERALLS_REPO_TOKEN: ${{ github.token }}

- name: benchmark
run: |
make bench CMD=resty
4 changes: 4 additions & 0 deletions .luacheckrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
unused_args = false
max_line_length = false
redefined = false

globals = {
"ngx",
}
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ bench:
RADIX_ROUTER_ROUTES=100000 RADIX_ROUTER_TIMES=10000000 $(CMD) benchmark/simple-variable.lua
RADIX_ROUTER_ROUTES=1000000 RADIX_ROUTER_TIMES=10000000 $(CMD) benchmark/simple-variable.lua
RADIX_ROUTER_ROUTES=100000 RADIX_ROUTER_TIMES=10000000 $(CMD) benchmark/simple-prefix.lua
RADIX_ROUTER_ROUTES=100000 RADIX_ROUTER_TIMES=1000000 $(CMD) benchmark/simple-regex.lua
RADIX_ROUTER_ROUTES=100000 RADIX_ROUTER_TIMES=1000000 $(CMD) benchmark/complex-variable.lua
RADIX_ROUTER_ROUTES=100000 RADIX_ROUTER_TIMES=10000000 $(CMD) benchmark/simple-variable-binding.lua
RADIX_ROUTER_TIMES=1000000 $(CMD) benchmark/github-routes.lua
Expand Down
42 changes: 33 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@ The router can be run in different runtimes such as Lua, LuaJIT, or OpenResty.

**Trailing slash match:** You can make the Router to ignore the trailing slash by setting `trailing_slash_match` to true. For example, /foo/ to match the existing /foo, /foo to match the existing /foo/.

**Custom matcher:** The router has two efficient matchers built in, MethodMatcher(`method`) and HostMatcher(`host`). They can be disabled via `opts.matcher_names`. You can also add your custom matchers via `opts.matchers`. For example, an IpMatcher to evaluate whether the `ctx.ip` is matched with the `ips` of a route.
**Custom Matcher:** The router has two efficient matchers built in, MethodMatcher(`method`) and HostMatcher(`host`). They can be disabled via `opts.matcher_names`. You can also add your custom matchers via `opts.matchers`. For example, an IpMatcher to evaluate whether the `ctx.ip` is matched with the `ips` of a route.

**Regex pattern:** You can define regex pattern in variables. a variable without regex pattern is treated as `[^/]+`.

- `/users/{uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}`
- `/users/{id:\\d+}/profile-{year:\\d{4}}.{format:(html|pdf)}`

**Features in the roadmap**:

- Expression condition: defines custom matching conditions by using expression language.
- Regex in variable


## 📖 Getting started

Expand Down Expand Up @@ -106,11 +109,11 @@ local router, err = Router.new(routes, opts)

The available options are as follow

| NAME | TYPE | DEFAULT | DESCRIPTION |
| -------------------- | ------- | ------------------ | --------------------------------------------------- |
| trailing_slash_match | boolean | false | whether to enable the trailing slash match behavior |
| matcher_names | table | {"method", "host"} | enabled built-in macher list |
| matchers | table | { } | custom matcher list |
| NAME | TYPE | DEFAULT | DESCRIPTION |
| -------------------- | ------- | ----------------- | --------------------------------------------------- |
| trailing_slash_match | boolean | false | whether to enable the trailing slash match behavior |
| matcher_names | table | {"method","host"} | enabled built-in macher list |
| matchers | table | { } | custom matcher list |



Expand Down Expand Up @@ -141,6 +144,28 @@ local handler = router:match(path, ctx, params, matched)
- **params**(`table|nil`): the optional table to use for storing the parameters binding result.
- **matched**(`table|nil`): the optional table to use for storing the matched conditions.

## 📝 Examples

#### Regex pattern

Using regex to define the pattern of a variable. Note that at most one URL segment is evaluated when matching a variable's pattern, which means it's not allowed to define a pattern crossing multiple URL segments, for example, `{var:[/0-9a-z]+}`.

```lua
local Router = require "radix-router"
local router = Router.new({
{
paths = { "/users/{id:\\d+}/profile-{year:\\d{4}}.{format:(html|pdf)}" },
handler = "1"
},
{
paths = { "/users/{uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}" },
handler = "2"
},
})
assert("1" == router:match("/users/100/profile-2024.pdf"))
assert("2", router:match("/users/00000000-0000-0000-0000-000000000000"))
```

## 🧠 Data Structure and Implementation

Inside the Router, it has a hash-like table to optimize the static path matching. Due to the LuaJIT optimization, static path matching is the fastest and has lower memory usage. (see [Benchmarks](#-Benchmarks))
Expand Down Expand Up @@ -215,7 +240,6 @@ router.trie = / nil
└─{catchall} { "/src/{*filename}", *<table 3> }
```


## 🚀 Benchmarks

#### Usage
Expand Down
33 changes: 33 additions & 0 deletions benchmark/simple-regex.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
local Router = require "radix-router"
local utils = require "benchmark.utils"

local route_n = os.getenv("RADIX_ROUTER_ROUTES") or 1000 * 100
local times = os.getenv("RADIX_ROUTER_TIMES") or 1000 * 1000 * 10

local router
do
local routes = {}
for i = 1, route_n do
routes[i] = { paths = { string.format("/%d/{name:[^/]+}", i) }, handler = i }
end
router = Router.new(routes)
end

local rss_mb = utils.get_rss()

local path = "/1/a"
local elapsed = utils.timing(function()
for _ = 1, times do
router:match(path)
end
end)

utils.print_result({
title = "regex",
routes = route_n,
times = times,
elapsed = elapsed,
benchmark_path = path,
benchmark_handler = router:match(path),
rss = rss_mb,
})
7 changes: 7 additions & 0 deletions bin/resty_busted
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env resty

if ngx ~= nil then
ngx.exit = function()end
end

require 'busted.runner'({ standalone = false })
3 changes: 2 additions & 1 deletion docs/examples/custom-matcher.lua.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ <h2>Examples</h2>
<ul class="nowrap">
<li><strong>custom-matcher.lua</strong></li>
<li><a href="../examples/example.lua.html">example.lua</a></li>
<li><a href="../examples/regular-expression.lua.html">regular-expression.lua</a></li>
</ul>
<h2>Modules</h2>
<ul class="nowrap">
Expand Down Expand Up @@ -103,7 +104,7 @@ <h2>custom-matcher.lua</h2>
</div> <!-- id="main" -->
<div id="about">
<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
<i style="float:right;">Last updated 2024-02-05 16:00:23 </i>
<i style="float:right;">Last updated 2024-03-01 01:33:25 </i>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
Expand Down
3 changes: 2 additions & 1 deletion docs/examples/example.lua.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ <h2>Examples</h2>
<ul class="nowrap">
<li><a href="../examples/custom-matcher.lua.html">custom-matcher.lua</a></li>
<li><strong>example.lua</strong></li>
<li><a href="../examples/regular-expression.lua.html">regular-expression.lua</a></li>
</ul>
<h2>Modules</h2>
<ul class="nowrap">
Expand Down Expand Up @@ -89,7 +90,7 @@ <h2>example.lua</h2>
</div> <!-- id="main" -->
<div id="about">
<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
<i style="float:right;">Last updated 2024-02-05 16:00:23 </i>
<i style="float:right;">Last updated 2024-03-01 01:33:25 </i>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
Expand Down
77 changes: 77 additions & 0 deletions docs/examples/regular-expression.lua.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head>
<title>Reference</title>
<link rel="stylesheet" href="../ldoc.css" type="text/css" />
</head>
<body>

<div id="container">

<div id="product">
<div id="product_logo"></div>
<div id="product_name"><big><b></b></big></div>
<div id="product_description"></div>
</div> <!-- id="product" -->


<div id="main">


<!-- Menu -->

<div id="navigation">
<br/>
<h1>Radix-Router</h1>





<h2>Examples</h2>
<ul class="nowrap">
<li><a href="../examples/custom-matcher.lua.html">custom-matcher.lua</a></li>
<li><a href="../examples/example.lua.html">example.lua</a></li>
<li><strong>regular-expression.lua</strong></li>
</ul>
<h2>Modules</h2>
<ul class="nowrap">
<li><a href="../index.html">radix-router</a></li>
</ul>

</div>

<div id="content">

<h2>regular-expression.lua</h2>
<pre>
<span class="keyword">local</span> Router = <span class="global">require</span> <span class="string">"radix-router"</span>
<span class="keyword">local</span> router, err = Router.<span class="function-name">new</span>({
{
paths = { <span class="string">"/users/{id:\\d+}/profile-{year:\\d{4}}.{format:(html|pdf)}"</span> },
handler = <span class="string">"1"</span>
},
{
paths = { <span class="string">"/users/{uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}"</span> },
handler = <span class="string">"2"</span>
},
})
<span class="keyword">if</span> <span class="keyword">not</span> router <span class="keyword">then</span>
<span class="global">error</span>(<span class="string">"failed to create router: "</span> .. err)
<span class="keyword">end</span>

<span class="global">assert</span>(<span class="string">"1"</span> == router:<span class="function-name">match</span>(<span class="string">"/users/100/profile-2024.pdf"</span>))
<span class="global">assert</span>(<span class="string">"2"</span>, router:<span class="function-name">match</span>(<span class="string">"/users/00000000-0000-0000-0000-000000000000"</span>))</pre>


</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
<i style="float:right;">Last updated 2024-03-01 01:33:25 </i>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>
3 changes: 2 additions & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ <h2>Examples</h2>
<ul class="nowrap">
<li><a href="examples/custom-matcher.lua.html">custom-matcher.lua</a></li>
<li><a href="examples/example.lua.html">example.lua</a></li>
<li><a href="examples/regular-expression.lua.html">regular-expression.lua</a></li>
</ul>

</div>
Expand Down Expand Up @@ -170,7 +171,7 @@ <h3>Usage:</h3>
</div> <!-- id="main" -->
<div id="about">
<i>generated by <a href="http://github.com/lunarmodules/LDoc">LDoc 1.5.0</a></i>
<i style="float:right;">Last updated 2024-02-05 16:00:23 </i>
<i style="float:right;">Last updated 2024-03-01 01:33:25 </i>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
Expand Down
17 changes: 17 additions & 0 deletions examples/regular-expression.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
local Router = require "radix-router"
local router, err = Router.new({
{
paths = { "/users/{id:\\d+}/profile-{year:\\d{4}}.{format:(html|pdf)}" },
handler = "1"
},
{
paths = { "/users/{uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}" },
handler = "2"
},
})
if not router then
error("failed to create router: " .. err)
end

assert("1" == router:match("/users/100/profile-2024.pdf"))
assert("2", router:match("/users/00000000-0000-0000-0000-000000000000"))
2 changes: 1 addition & 1 deletion radix-router-dev-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ description = {
}

dependencies = {
"lua >= 5.1, < 5.5"
"lrexlib-pcre2",
}

build = {
Expand Down
Loading

0 comments on commit c968048

Please sign in to comment.