diff --git a/lib/lor/lib/application.lua b/lib/lor/lib/application.lua index 25ad427..8611153 100755 --- a/lib/lor/lib/application.lua +++ b/lib/lor/lib/application.lua @@ -82,7 +82,7 @@ function App:handle(req, res, callback) local done = callback or function(err) if err then if ngx then ngx.log(ngx.ERR, err) end - res:status(500):send("unknown error.") + res:status(500):send("internal error! please check log.") end end diff --git a/lib/lor/lib/router/group.lua b/lib/lor/lib/router/group.lua index 6bc109d..61a2019 100644 --- a/lib/lor/lib/router/group.lua +++ b/lib/lor/lib/router/group.lua @@ -1,14 +1,19 @@ local setmetatable = setmetatable local pairs = pairs local type = type +local error = error +local next = next local string_format = string.format local string_lower = string.lower +local table_insert = table.insert +local unpack = table.unpack or unpack local supported_http_methods = require("lor.lib.methods") local debug = require("lor.lib.debug") local utils = require("lor.lib.utils.utils") local random = utils.random local clone = utils.clone +local handler_error_tip = "handler must be `function` that matches `function(req, res, next) ... end`" local Group = {} @@ -45,29 +50,83 @@ function Group:get_apis() return self.apis end -function Group:set_api(path, method, func) - if not path or not method or not func then - return error("params should not be nil.") +function Group:set_api(path, method, ...) + if not path or not method then + return error("`path` & `method` should not be nil.") end - if type(path) ~= "string" or type(method) ~= "string" or type(func) ~= "function" then + local handlers = {...} + if not next(handlers) then + return error("handler should not be nil or empty") + end + + if type(path) ~= "string" or type(method) ~= "string" or type(handlers) ~= "table" then return error("params type error.") end + local extended_handlers = {} + for _, h in ipairs(handlers) do + if type(h) == "function" then + table_insert(extended_handlers, h) + elseif type(h) == "table" then + for _, hh in ipairs(h) do + if type(hh) == "function" then + table_insert(extended_handlers, hh) + else + error(handler_error_tip) + end + end + else + error(handler_error_tip) + end + end + method = string_lower(method) if not supported_http_methods[method] then return error(string_format("[%s] method is not supported yet.", method)) end self.apis[path] = self.apis[path] or {} - self.apis[path][method] = func + self.apis[path][method] = extended_handlers end function Group:build_method() for m, _ in pairs(supported_http_methods) do m = string_lower(m) - Group[m] = function(myself, path, func) - Group.set_api(myself, path, m, func) + + -- 1. group_router:get(func1) + -- 2. group_router:get(func1, func2) + -- 3. group_router:get({func1, func2}) + -- 4. group_router:get(path, func1) + -- 5. group_router:get(path, func1, func2) + -- 6. group_router:get(path, {func1, func2}) + Group[m] = function(myself, ...) + local params = {...} + if not next(params) then return error("params should not be nil or empty") end + + -- case 1 or 3 + if #params == 1 then + if type(params[1]) ~= "function" and type(params[1]) ~= "table" then + return error("it must be an function if there's only one param") + end + + if type(params[1]) == "table" and #(params[1]) == 0 then + return error("params should not be nil or empty") + end + + return Group.set_api(myself, "", m, ...) + end + + -- case 2,4,5,6 + if #params > 1 then + if type(params[1]) == "string" then -- case 4,5,6 + return Group.set_api(myself, params[1], m, unpack(params, 2)) + else -- case 2 + return Group.set_api(myself, "", m, ...) + end + end + + error("error params for group route define") end end end diff --git a/lib/lor/lib/router/router.lua b/lib/lor/lib/router/router.lua index dfe7f65..65f7778 100755 --- a/lib/lor/lib/router/router.lua +++ b/lib/lor/lib/router/router.lua @@ -3,6 +3,7 @@ local ipairs = ipairs local pcall = pcall local xpcall = xpcall local type = type +local error = error local setmetatable = setmetatable local traceback = debug.traceback local tinsert = table.insert @@ -287,21 +288,27 @@ function Router:merge_group(prefix, group) if apis then for uri, api_methods in pairs(apis) do if type(api_methods) == "table" then - local path = utils.clear_slash(prefix .. "/" .. uri) + local path + if uri == "" then -- for group index route + path = utils.clear_slash(prefix) + else + path = utils.clear_slash(prefix .. "/" .. uri) + end + local node = self.trie:add_node(path) if not node then return error("cann't define node on router trie, path:" .. path) end - for method, func in pairs(api_methods) do + for method, handlers in pairs(api_methods) do local m = string_lower(method) if supported_http_methods[m] == true then - node:handle(m, func) - end + node:handle(m, handlers) + end -- supported method end end end - end + end -- ugly arrow style for missing `continue` return self end diff --git a/spec/cases/group_index_route_spec.lua b/spec/cases/group_index_route_spec.lua new file mode 100644 index 0000000..dc657ef --- /dev/null +++ b/spec/cases/group_index_route_spec.lua @@ -0,0 +1,165 @@ +before_each(function() + lor = _G.lor + app = lor({ + debug = true + }) + Request = _G.request + Response = _G.response + req = Request:new() + res = Response:new() +end) + +after_each(function() + lor = nil + app = nil + Request = nil + Response = nil + req = nil + res = nil +end) + + +describe("group index route: basic usages", function() + it("should be error when giving wrong params", function() + local flag = 0 + local test_router = lor:Router() + + assert.has_error(function() test_router:get() end, "params should not be nil or empty") + assert.has_error(function() test_router:get({}) end, "params should not be nil or empty") + + assert.has_error(function() test_router:get("/test") end, "it must be an function if there's only one param") + assert.has_error(function() test_router:get("/test", "abc") end) + end) + + it("uri should mathed", function() + local flag = 0 + + local test_router = lor:Router() + test_router:get(function(req, res, next) + flag = 1 + end) + + app:use("/test", test_router()) + + req.path = "/test" + req.method = "get" + app:handle(req, res) + assert.is.equals(1, flag) + end) + + it("uri should not mathed", function() + local flag = 0 + + local test_router = lor:Router() + test_router:get(function(req, res, next) + flag = 1 + end) + + app:use("/test", test_router()) + app:erroruse(function(err, req, res, next) -- 404 error + assert.is.truthy(err) + assert.is.equals(false, req:is_found()) + flag = 999 + end) + + req.path = "/test/" + req.method = "get" + app:handle(req, res) + assert.is.equals(999, flag) + end) +end) + + +describe("group index route: multi funcs", function() + it("array params", function() + local flag = 0 + local test_router = lor:Router() + local func1 = function(req, res, next) + flag = 1 + next() + end + local func2 = function(req, res, next) + flag = 2 + next() + end + local last_func = function(req, res, next) + flag = 3 + end + test_router:post({func1, func2, last_func}) + app:use("/test", test_router()) + + req.path = "/test" + req.method = "post" + app:handle(req, res) + assert.is.equals(3, flag) + end) + + it("unpacked params", function() + local flag = 0 + local test_router = lor:Router() + local func1 = function(req, res, next) + flag = 1 + next() + end + local func2 = function(req, res, next) + flag = 2 + next() + end + local last_func = function(req, res, next) + flag = 3 + end + test_router:put(func1, func2, last_func) + app:use("/test", test_router()) + + req.path = "/test" + req.method = "put" + app:handle(req, res) + assert.is.equals(3, flag) + end) + + it("mixed params, case1", function() + local flag = 0 + local test_router = lor:Router() + local func1 = function(req, res, next) + flag = 1 + next() + end + local func2 = function(req, res, next) + flag = 2 + next() + end + local last_func = function(req, res, next) + flag = 3 + end + test_router:get({func1, func2}, last_func) + app:use("/test", test_router()) + + req.path = "/test" + req.method = "get" + app:handle(req, res) + assert.is.equals(3, flag) + end) + + it("mixed params, case2", function() + local flag = 0 + local test_router = lor:Router() + local func1 = function(req, res, next) + flag = 1 + next() + end + local func2 = function(req, res, next) + flag = 2 + next() + end + local last_func = function(req, res, next) + flag = 3 + end + test_router:put({func1}, func2, {last_func}) + app:use("/test", test_router()) + + req.path = "/test" + req.method = "put" + app:handle(req, res) + assert.is.equals(3, flag) + end) +end) diff --git a/spec/cases/multi_route_spec.lua b/spec/cases/multi_route_spec.lua new file mode 100644 index 0000000..b3b3b0f --- /dev/null +++ b/spec/cases/multi_route_spec.lua @@ -0,0 +1,254 @@ +before_each(function() + lor = _G.lor + app = lor({ + debug = true + }) + Request = _G.request + Response = _G.response + req = Request:new() + res = Response:new() +end) + +after_each(function() + lor = nil + app = nil + Request = nil + Response = nil + req = nil + res = nil +end) + + +describe("multi route: mounted on `app`", function() + it("array param", function() + local flag = 0 + local func1 = function(req, res, next) + flag = 1 + next() + end + local func2 = function(req, res, next) + flag = 2 + next() + end + local last_func = function(req, res, next) + flag = req.query.flag or 3 + end + app:get("/flag", {func1, func2, last_func}) + + app:erroruse(function(err, req, res, next) + assert.is.truthy(err) -- should not reach here. + flag = 999 + end) + + req.path = "/flag" + req.method = "get" + app:handle(req, res) + assert.is.equals(3, flag) + + req.path = "/flag" + req.query = {flag=111} + req.method = "get" + app:handle(req, res) + assert.is.equals(111, flag) + end) + + it("unpacked params", function() + local flag = 0 + local func1 = function(req, res, next) + flag = 1 + next() + end + local func2 = function(req, res, next) + flag = 2 + next() + end + local last_func = function(req, res, next) + flag = req.query.flag or 3 + end + app:get("/flag", func1, func2, last_func) + + app:erroruse(function(err, req, res, next) + assert.is.truthy(err) -- should not reach here. + flag = 999 + end) + + req.path = "/flag" + req.method = "get" + app:handle(req, res) + assert.is.equals(3, flag) + + req.path = "/flag" + req.query = {flag=111} + req.method = "get" + app:handle(req, res) + assert.is.equals(111, flag) + end) +end) + +describe("multi route: mounted on `group router`", function() + it("array param", function() + local flag = 0 + + local test_router = lor:Router() + local func1 = function(req, res, next) + flag = 1 + next() + end + local func2 = function(req, res, next) + flag = 2 + next() + end + local last_func = function(req, res, next) + flag = req.query.flag or 3 + end + test_router:get("/flag", {func1, func2, last_func}) + + app:use("/test", test_router()) + app:erroruse(function(err, req, res, next) + assert.is.truthy(err) -- should not reach here. + flag = 999 + end) + + req.path = "/test/flag" + req.method = "get" + app:handle(req, res) + assert.is.equals(3, flag) + + req.path = "/test/flag" + req.query = {flag=111} + req.method = "get" + app:handle(req, res) + assert.is.equals(111, flag) + end) + + it("unpacked params", function() + local flag = 0 + + local test_router = lor:Router() + local func1 = function(req, res, next) + flag = 1 + next() + end + local func2 = function(req, res, next) + flag = 2 + next() + end + local last_func = function(req, res, next) + flag = req.query.flag or 3 + end + test_router:get("/flag", func1, func2, last_func) + + app:use("/test", test_router()) + app:erroruse(function(err, req, res, next) + assert.is.truthy(err) -- should not reach here. + flag = 999 + end) + + req.path = "/test/flag" + req.method = "get" + app:handle(req, res) + assert.is.equals(3, flag) + + req.path = "/test/flag" + req.query = {flag=111} + req.method = "get" + app:handle(req, res) + assert.is.equals(111, flag) + end) +end) + +describe("multi route: muixed funcs for group router", function() + it("mixed params, case1", function() + local flag = 0 + local test_router = lor:Router() + local func1 = function(req, res, next) + flag = 1 + next() + end + local func2 = function(req, res, next) + flag = 2 + next() + end + local last_func = function(req, res, next) + flag = 3 + end + test_router:put("mixed", {func1, func2}, last_func) + app:use("/test", test_router()) + + req.path = "/test/mixed" + req.method = "put" + app:handle(req, res) + assert.is.equals(3, flag) + end) + + it("mixed params, case2", function() + local flag = 0 + local test_router = lor:Router() + local func1 = function(req, res, next) + flag = 1 + next() + end + local func2 = function(req, res, next) + flag = 2 + next() + end + local last_func = function(req, res, next) + flag = 3 + end + test_router:get("mixed", {func1}, func2, {last_func}) + app:use("/test", test_router()) + + req.path = "/test/mixed" + req.method = "get" + app:handle(req, res) + assert.is.equals(3, flag) + end) +end) + +describe("multi route: muixed funcs for `app`", function() + it("mixed params, case1", function() + local flag = 0 + local func1 = function(req, res, next) + flag = 1 + next() + end + local func2 = function(req, res, next) + flag = 2 + next() + end + local last_func = function(req, res, next) + flag = 3 + end + app:get("mixed", {func1, func2}, last_func) + + req.path = "/mixed" + req.method = "get" + app:handle(req, res) + assert.is.equals(3, flag) + end) + + it("mixed params, case2", function() + local flag = 0 + local func1 = function(req, res, next) + flag = 1 + next() + end + local func2 = function(req, res, next) + flag = 2 + next() + end + local func3 = function(req, res, next) + flag = 3 + next() + end + local last_func = function(req, res, next) + flag = 4 + end + app:get("mixed", {func1}, func2, {func3}, last_func) + + req.path = "/mixed" + req.method = "get" + app:handle(req, res) + assert.is.equals(4, flag) + end) +end) diff --git a/spec/cases/path_pattern_2_spec.lua b/spec/cases/path_pattern_2_spec.lua index 83241d9..e6514f7 100755 --- a/spec/cases/path_pattern_2_spec.lua +++ b/spec/cases/path_pattern_2_spec.lua @@ -59,6 +59,10 @@ describe("path match test", function() it("test case 3", function() req.path = "/test/find/all/1" req.method = "get" + app:erroruse(function(err, req, res, next) -- 404 error + assert.is.truthy(err) + assert.is.equals(false, req:is_found()) + end) app:handle(req, res) assert.is.equals(1, match) assert.is.equals(0, count)