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(router): lua validate an expression against a schema #39

Merged
merged 29 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ build: target/release/libatc_router.$(SHLIB_EXT) target/release/libatc_router.a
target/release/libatc_router.%: src/*.rs
cargo build --release

install: build
install-lualib:
$(INSTALL) -d $(DESTDIR)$(LUA_LIB_DIR)/resty/router/
$(INSTALL) -m 664 lib/resty/router/*.lua $(DESTDIR)$(LUA_LIB_DIR)/resty/router/

install: build install-lualib
$(INSTALL) -m 775 target/release/libatc_router.$(SHLIB_EXT) $(DESTDIR)$(LUA_LIB_DIR)/libatc_router.$(SHLIB_EXT)

clean:
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Table of Contents
* [remove\_matcher](#remove_matcher)
* [execute](#execute)
* [get\_fields](#get_fields)
* [validate](#validate)
* [resty.router.context](#restyroutercontext)
* [new](#new)
* [add\_value](#add_value)
Expand Down Expand Up @@ -144,6 +145,21 @@ actually used by the user supplied matchers.

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

### validate

**syntax:** *ok, err = router.validate(schema, expr)*

**context:** *any*

Validates an expression against a given schema.

Returns `true` when the expression is valid.

If the expression is invalid,
`nil` and a string describing the reason will be returned.

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

## resty.router.context

### new
Expand Down
29 changes: 29 additions & 0 deletions lib/resty/router/router.lua
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,33 @@ function _M:get_fields()
end


do
local ROUTERS = setmetatable({}, { __mode = "k" })
local DEFAULT_UUID = "00000000-0000-0000-0000-000000000000"
local DEFAULT_PRIORITY = 0

-- validate an expression against a schema
-- @param expr the expression to validate
-- @param schema the schema to validate against
-- @return true if the expression is valid, (nil, error) otherwise
function _M.validate(schema, expr)
local r = ROUTERS[schema]

if not r then
r = _M.new(schema, 1)
ROUTERS[schema] = r
end

local ok, err = r:add_matcher(DEFAULT_PRIORITY, DEFAULT_UUID, expr)
if not ok then
return nil, err
end

assert(r:remove_matcher(DEFAULT_UUID))

return true
end
end


return _M
242 changes: 242 additions & 0 deletions t/06-validate.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
# vim:set ft= ts=4 sw=4 et:

use Test::Nginx::Socket::Lua;
use Cwd qw(cwd);

repeat_each(2);

plan tests => repeat_each() * blocks() * 5;

my $pwd = cwd();

our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
lua_package_cpath "$pwd/target/debug/?.so;;";
};

no_long_string();
no_diff();

run_tests();

__DATA__

=== TEST 1: test valid schema + expr
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local schema = require("resty.router.schema")
local router = require("resty.router.router")

local s = schema.new()
s:add_field("http.headers.foo", "String")

local expr = "http.headers.foo == \"bar\""
local r, err = router.validate(s, expr)

ngx.say(r)
ngx.say(err)
}
}
--- request
GET /t
--- response_body
true
nil
--- no_error_log
[error]
[warn]
[crit]


=== TEST 2: test type inconsistency (schema is String, expr is Int)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local schema = require("resty.router.schema")
local router = require("resty.router.router")

local s = schema.new()
s:add_field("http.headers.foo", "String")

local expr = "http.headers.foo == 123"
local r, err = router.validate(s, expr)

ngx.say(r)
ngx.say(err)
}
}
--- request
GET /t
--- response_body_like
nil
Type mismatch between the LHS and RHS values of predicate
--- no_error_log
[error]
[warn]
[crit]


=== TEST 3: test invalid schema + invalid expr
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local schema = require("resty.router.schema")
local router = require("resty.router.router")

local s = schema.new()
s:add_field("http.headers.foo", "String")

local expr = "== 123"
local r, err = router.validate(s, expr)

ngx.say(r)
ngx.say(err)
}
}
--- request
GET /t
--- response_body
nil
--> 1:1
|
1 | == 123
| ^---
|
= expected term

--- no_error_log
[error]
[warn]
[crit]


=== TEST 4: test valid schema + invalid expr
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local schema = require("resty.router.schema")
local router = require("resty.router.router")

local s = schema.new()
s:add_field("http.headers.foo", "String")

local expr = "== \"bar\""
local r, err = router.validate(s, expr)

ngx.say(r)
ngx.say(err)
}
}
--- request
GET /t
--- response_body
nil
--> 1:1
|
1 | == "bar"
| ^---
|
= expected term

--- no_error_log
[error]
[warn]
[crit]

chronolaw marked this conversation as resolved.
Show resolved Hide resolved

=== TEST 5: valid regex
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local schema = require("resty.router.schema")
local router = require("resty.router.router")

local s = schema.new()
s:add_field("http.headers.foo", "String")

local expr = [[http.headers.foo ~ "/\\\\/*user$"]]
local r, err = router.validate(s, expr)
ngx.say(r)
ngx.say(err)
}
}
--- request
GET /t
--- response_body
true
nil
--- no_error_log
[error]
[warn]
[crit]


=== TEST 6: invalid regex
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local schema = require("resty.router.schema")
local router = require("resty.router.router")

local s = schema.new()
s:add_field("http.headers.foo", "String")

local expr = [[http.headers.foo ~ "([."]]
local r, err = router.validate(s, expr)
ngx.say(r)
ngx.say(err)
}
}
--- request
GET /t
--- response_body
nil
--> 1:1
|
1 | http.headers.foo ~ "([."
| ^----------------------^
|
= regex parse error:
([.
^
error: unclosed character class

--- no_error_log
[error]
[warn]
[crit]


=== TEST 7: Rust regex 1.8.x will not think the regex is invalid
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local schema = require("resty.router.schema")
local router = require("resty.router.router")

local s = schema.new()
s:add_field("http.headers.foo", "String")

local expr = [[http.headers.foo ~ "/\\/*user$"]]
local r, err = router.validate(s, expr)
ngx.say(r)
ngx.say(err)
}
}
--- request
GET /t
--- response_body
true
nil
--- no_error_log
[error]
[warn]
[crit]