diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d37afd037cbcc0163324545e7273efca6c364157..c504f068300ef5f8dc3ecfef9460f0bccef92ef4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,7 @@ lua_source(lua_sources lua/csv.lua csv_lua) lua_source(lua_sources lua/strict.lua strict_lua) lua_source(lua_sources lua/clock.lua clock_lua) lua_source(lua_sources lua/title.lua title_lua) +lua_source(lua_sources lua/utils.lua utils_lua) lua_source(lua_sources lua/argparse.lua argparse_lua) lua_source(lua_sources lua/env.lua env_lua) lua_source(lua_sources lua/pwd.lua pwd_lua) diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index 40cbfb46911777c0838a4f557a58476ca1f761c4..d3387ee35dc5f975dc48ab361affb458f899c411 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -6,6 +6,7 @@ local msgpack = require('msgpack') local urilib = require('uri') local internal = require('net.box.lib') local trigger = require('internal.trigger') +local utils = require('internal.utils') local this_module @@ -16,7 +17,7 @@ local check_select_opts = box.internal.check_select_opts local check_index_arg = box.internal.check_index_arg local check_space_arg = box.internal.check_space_arg local check_primary_index = box.internal.check_primary_index -local check_param_table = box.internal.check_param_table +local check_param_table = utils.check_param_table local ibuf_t = ffi.typeof('struct ibuf') local is_tuple = box.tuple.is diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 08aff6cfa8a0fa3ef8d5c834b63e700fca141296..8950935fbef680a3dc64c2302f1c5106aaef088f 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -9,6 +9,12 @@ local fiber = require('fiber') local session = box.session local internal = box.internal local utf8 = require('utf8') +local utils = require('internal.utils') + +local check_param = utils.check_param +local check_param_table = utils.check_param_table +local update_param_table = utils.update_param_table + local function setmap(table) return setmetatable(table, { __serialize = 'map' }) end @@ -234,119 +240,6 @@ local function revoke_object_privs(object_type, object_id) end end --- Same as type(), but returns 'number' if 'param' is --- of type 'cdata' and represents a 64-bit integer. -local function param_type(param) - local t = type(param) - if t == 'cdata' and tonumber64(param) ~= nil then - t = 'number' - end - return t -end - ---[[ - @brief Common function to check table with parameters (like options) - @param table - table with parameters - @param template - table with expected types of expected parameters - type could be comma separated string with lua types (number, string etc), - or 'any' if any type is allowed. Instead of this string, function, which will - be used to check if the parameter is correct, can be used too. It should - accept option as an argument and return either true or false + expected_type. - The function checks following: - 1)that parameters table is a table (or nil) - 2)all keys in parameters are present in template - 3)type of every parameter fits (one of) types described in template - The functions calls box.error(box.error.ILLEGAL_PARAMS, ..) on error - @example - check_param_table(options, { user = 'string', - port = 'string, number', - data = 'any', - addr = function(opt) - if not ffi.istype(addr_t, buf) then - return false, "struct addr" - end - return true - end} ) ---]] -local function check_param_table(table, template) - if table == nil then - return - end - if type(table) ~= 'table' then - box.error(box.error.ILLEGAL_PARAMS, - "options should be a table") - end - for k,v in pairs(table) do - if template[k] == nil then - box.error(box.error.ILLEGAL_PARAMS, - "unexpected option '" .. k .. "'") - elseif type(template[k]) == 'function' then - local res, expected_type = template[k](v) - if not res then - box.error(box.error.ILLEGAL_PARAMS, - "options parameter '" .. k .. - "' should be of type " .. expected_type) - end - elseif template[k] == 'any' then -- luacheck: ignore - -- any type is ok - elseif (string.find(template[k], ',') == nil) then - -- one type - if param_type(v) ~= template[k] then - box.error(box.error.ILLEGAL_PARAMS, - "options parameter '" .. k .. - "' should be of type " .. template[k]) - end - else - local good_types = string.gsub(template[k], ' ', '') - local haystack = ',' .. good_types .. ',' - local needle = ',' .. param_type(v) .. ',' - if (string.find(haystack, needle) == nil) then - box.error(box.error.ILLEGAL_PARAMS, - "options parameter '" .. k .. - "' should be one of types: " .. template[k]) - end - end - end -end -box.internal.check_param_table = check_param_table - ---[[ - @brief Common function to check type parameter (of function) - Calls box.error(box.error.ILLEGAL_PARAMS, ) on error - @example: check_param(user, 'user', 'string') ---]] -local function check_param(param, name, should_be_type) - if param_type(param) ~= should_be_type then - box.error(box.error.ILLEGAL_PARAMS, - name .. " should be a " .. should_be_type) - end -end -box.internal.check_param = check_param - ---[[ - Adds to a table key-value pairs from defaults table - that is not present in original table. - Returns updated table. - If nil is passed instead of table, it's treated as empty table {} - For example update_param_table({ type = 'hash', temporary = true }, - { type = 'tree', unique = true }) - will return table { type = 'hash', temporary = true, unique = true } ---]] -local function update_param_table(table, defaults) - local new_table = {} - if defaults ~= nil then - for k,v in pairs(defaults) do - new_table[k] = v - end - end - if table ~= nil then - for k,v in pairs(table) do - new_table[k] = v - end - end - return new_table -end - local function feedback_save_event(event) if internal.feedback_daemon ~= nil then internal.feedback_daemon.save_event(event) diff --git a/src/box/lua/tuple.lua b/src/box/lua/tuple.lua index 1cfe6c2548d185a8ddb928f498dda7ba508ffec5..a38a4f84ef509e3ede17629d103e69b6209077e0 100644 --- a/src/box/lua/tuple.lua +++ b/src/box/lua/tuple.lua @@ -5,9 +5,12 @@ local msgpackffi = require('msgpackffi') local fun = require('fun') local buffer = require('buffer') local compat = require('compat') +local utils = require('internal.utils') + local internal = box.internal local cord_ibuf_take = buffer.internal.cord_ibuf_take local cord_ibuf_put = buffer.internal.cord_ibuf_put +local check_param_table = utils.check_param_table ffi.cdef[[ /** \cond public */ @@ -83,7 +86,7 @@ local new_tuple = function(...) if type(tuple) ~= 'table' and not box.tuple.is(tuple) then tuple = {tuple} end - internal.check_param_table(options, NEW_OPTION_TYPES) + check_param_table(options, NEW_OPTION_TYPES) if options == nil then return internal.tuple.new(tuple) end diff --git a/src/box/lua/tuple_format.lua b/src/box/lua/tuple_format.lua index d4c64fe9dfdf9573883664f9222b4e097b80422c..9870e5e774000a39821b2cef78c1eaa3406f050b 100644 --- a/src/box/lua/tuple_format.lua +++ b/src/box/lua/tuple_format.lua @@ -1,7 +1,9 @@ +local utils = require('internal.utils') + -- new() needs a wrapper in Lua, because format normalization needs to be done -- in Lua. box.tuple.format.new = function(format) - box.internal.check_param(format, 'format', 'table') + utils.check_param(format, 'format', 'table') format = box.internal.space.normalize_format(nil, nil, format) return box.internal.tuple_format.new(format) end diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua index e4de1319b4d97c6703486d15b85d22bfff6986c0..c8ca769387a874f55cf8588bd35c1bedc64b572d 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -5,6 +5,7 @@ local fio = require('fio') local xlog = require('xlog') local ffi = require('ffi') local fun = require('fun') +local utils = require('internal.utils') ffi.cdef([[ uint32_t box_dd_version_id(void); @@ -2116,7 +2117,7 @@ local downgrade_versions = { -- In case of downgrade check for issues is done before making any changes. -- If any issue is found then downgrade is failed and no any changes are done. local function downgrade_impl(version_str, dry_run) - box.internal.check_param(version_str, 'version_str', 'string') + utils.check_param(version_str, 'version_str', 'string') local version = mkversion.parse(version_str) if fun.index(version_str, downgrade_versions) == nil then error("Downgrade is only possible to version listed in" .. diff --git a/src/lua/init.c b/src/lua/init.c index 94de2dac91c90bc98a7e14abae1d7f1e7a38d573..1d741b90a92b371b874e24f1fedad89f645d4c1f 100644 --- a/src/lua/init.c +++ b/src/lua/init.c @@ -132,6 +132,7 @@ extern char minifio_lua[], tap_lua[], fio_lua[], error_lua[], + utils_lua[], argparse_lua[], iconv_lua[], /* jit.* library */ @@ -297,6 +298,7 @@ static const char *lua_modules[] = { "tap", tap_lua, "help.en_US", help_en_US_lua, "help", help_lua, + "internal.utils", utils_lua, "internal.argparse", argparse_lua, "internal.trigger", trigger_lua, "pwd", pwd_lua, diff --git a/src/lua/utils.lua b/src/lua/utils.lua new file mode 100644 index 0000000000000000000000000000000000000000..53797e2834577e8f157a0ffc6f2ba0f8816fa389 --- /dev/null +++ b/src/lua/utils.lua @@ -0,0 +1,114 @@ +local utils = {} + +-- Same as type(), but returns 'number' if 'param' is +-- of type 'cdata' and represents a 64-bit integer. +local function param_type(param) + local t = type(param) + if t == 'cdata' and tonumber64(param) ~= nil then + t = 'number' + end + return t +end + +--[[ + @brief Common function to check type parameter (of function) + Calls box.error(box.error.ILLEGAL_PARAMS, ) on error + @example: check_param(user, 'user', 'string') +--]] +function utils.check_param(param, name, should_be_type) + if param_type(param) ~= should_be_type then + box.error(box.error.ILLEGAL_PARAMS, + name .. " should be a " .. should_be_type) + end +end + +--[[ + @brief Common function to check table with parameters (like options) + @param table - table with parameters + @param template - table with expected types of expected parameters + type could be comma separated string with lua types (number, string etc), + or 'any' if any type is allowed. Instead of this string, function, which will + be used to check if the parameter is correct, can be used too. It should + accept option as an argument and return either true or false + expected_type. + The function checks following: + 1)that parameters table is a table (or nil) + 2)all keys in parameters are present in template + 3)type of every parameter fits (one of) types described in template + The functions calls box.error(box.error.ILLEGAL_PARAMS, ..) on error + @example + check_param_table(options, { user = 'string', + port = 'string, number', + data = 'any', + addr = function(opt) + if not ffi.istype(addr_t, buf) then + return false, "struct addr" + end + return true + end} ) +--]] +function utils.check_param_table(table, template) + if table == nil then + return + end + if type(table) ~= 'table' then + box.error(box.error.ILLEGAL_PARAMS, + "options should be a table") + end + for k,v in pairs(table) do + if template[k] == nil then + box.error(box.error.ILLEGAL_PARAMS, + "unexpected option '" .. k .. "'") + elseif type(template[k]) == 'function' then + local res, expected_type = template[k](v) + if not res then + box.error(box.error.ILLEGAL_PARAMS, + "options parameter '" .. k .. + "' should be of type " .. expected_type) + end + elseif template[k] == 'any' then -- luacheck: ignore + -- any type is ok + elseif (string.find(template[k], ',') == nil) then + -- one type + if param_type(v) ~= template[k] then + box.error(box.error.ILLEGAL_PARAMS, + "options parameter '" .. k .. + "' should be of type " .. template[k]) + end + else + local good_types = string.gsub(template[k], ' ', '') + local haystack = ',' .. good_types .. ',' + local needle = ',' .. param_type(v) .. ',' + if (string.find(haystack, needle) == nil) then + box.error(box.error.ILLEGAL_PARAMS, + "options parameter '" .. k .. + "' should be one of types: " .. template[k]) + end + end + end +end + +--[[ + Adds to a table key-value pairs from defaults table + that is not present in original table. + Returns updated table. + If nil is passed instead of table, it's treated as empty table {} + For example update_param_table({ type = 'hash', temporary = true }, + { type = 'tree', unique = true }) + will return table { type = 'hash', temporary = true, unique = true } +--]] +function utils.update_param_table(table, defaults) + local new_table = {} + if defaults ~= nil then + for k,v in pairs(defaults) do + new_table[k] = v + end + end + if table ~= nil then + for k,v in pairs(table) do + new_table[k] = v + end + end + return new_table +end + +return utils diff --git a/test/app-luatest/utils_test.lua b/test/app-luatest/utils_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..1b210d1ba30805c9371df343774c90792de7b42a --- /dev/null +++ b/test/app-luatest/utils_test.lua @@ -0,0 +1,67 @@ +local utils = require('internal.utils') +local t = require('luatest') + +local g = t.group() + +g.test_check_param = function() + t.assert_error_msg_equals("Illegal parameters, foo should be a string", + utils.check_param, 42, "foo", 'string') + t.assert_error_msg_equals("Illegal parameters, foo should be a number", + utils.check_param, '42', "foo", 'number') + t.assert(pcall(utils.check_param, '42', 'foo', 'string')) + t.assert(pcall(utils.check_param, 42, 'foo', 'number')) + t.assert(pcall(utils.check_param, 123456789123456789ULL, 'foo', 'number')) +end + +g.test_check_param_table = function() + local opts = { + any_opt = 'any', + str_opt = 'string', + str_num_opt = 'string,number', + tuple_opt = function(opt) + return box.tuple.is(opt), 'tuple' + end + } + t.assert_error_msg_equals("Illegal parameters, options should be a table", + utils.check_param_table, 'foo', opts) + t.assert_error_msg_equals("Illegal parameters, unexpected option 'foo'", + utils.check_param_table, {foo = 'bar'}, opts) + t.assert_error_msg_equals("Illegal parameters, options parameter " .. + "'tuple_opt' should be of type tuple", + utils.check_param_table, {tuple_opt = {}}, opts) + t.assert_error_msg_equals("Illegal parameters, options parameter " .. + "'str_opt' should be of type string", + utils.check_param_table, {str_opt = 42}, opts) + t.assert_error_msg_equals("Illegal parameters, options parameter " .. + "'str_num_opt' should be one of types: " .. + "string,number", + utils.check_param_table, {str_num_opt = {}}, opts) + t.assert(pcall(utils.check_param_table, nil, opts)) + t.assert(pcall(utils.check_param_table, {}, opts)) + t.assert(pcall(utils.check_param_table, { + any_opt = 'foo', + str_opt = 'bar', + str_num_opt = 42, + tuple_opt = box.tuple.new(), + }, opts)) + t.assert(pcall(utils.check_param_table, { + any_opt = {}, + str_num_opt = 'foo', + }, opts)) +end + +g.test_update_param_table = function() + local opts = { + a = 'foo', + b = 'bar', + } + local defaults = { + b = 'x', + c = 'y', + } + t.assert_equals(utils.update_param_table(opts, defaults), { + a = 'foo', + b = 'bar', + c = 'y', + }) +end