diff --git a/doc/acid/typeutil.md b/doc/acid/typeutil.md index 7be37b2..b2377d7 100644 --- a/doc/acid/typeutil.md +++ b/doc/acid/typeutil.md @@ -1,4 +1,4 @@ -e!-- START doctoc generated TOC please keep comment here to allow nuto update --> + # Table of Content @@ -7,15 +7,25 @@ e!-- START doctoc generated TOC please keep comment here to allow nuto update -- - [Description](#description) - [Synopsis](#synopsis) - [Methods](#methods) - - [typeutil.check_int_range](#typeutilcheck_int_range) + - [typeutil.check_integer_range](#typeutilcheck_integer_range) - [typeutil.check_number_range](#typeutilcheck_number_range) - - [typeutil.check_number_string_range](#typeutilcheck_number_range) - - [typeutil.is_int](#typeutilis_int) + - [typeutil.check_string_number_range](#typeutilcheck_string_number_range) + - [typeutil.check_length_range](#typeutilcheck_length_range) + - [typeutil.check_fixed_length](#typeutilcheck_fixed_length) + - [typeutil.is_string](#typeutilis_string) + - [typeutil.is_number](#typeutilis_number) + - [typeutil.is_integer](#typeutilis_integer) + - [typeutil.is_boolean](#typeutilis_boolean) + - [typeutil.is_string_number](#typeutilis_string_number) + - [typeutil.is_array](#typeutilis_array) + - [typeutil.is_dict](#typeutilis_dict) + - [typeutil.is_empty_table](#typeutilis_empty_table) - [Author](#author) - [Copyright and License](#copyright-and-license) + # Name acid.typeutil @@ -36,36 +46,43 @@ local acid_typeutil = require("acid.typeutil") local is_true = acid_typeutil.check_number_range(1, 0, 2) print(is_true) -- true -local is_true = acid_typeutil.is_int(0.5) +local is_true = acid_typeutil.is_integer(0.5) print(is_true) -- false ``` # Methods -## typeutil.check_int_range +## typeutil.check_integer_range **syntax**: -`typeutil.check_int_range(val, min, max)` +`typeutil.check_integer_range(val, min, max, opts)` Return `true` if `val` is an integer and in the range, `false` otherwise. The range is closed. It means `val` can be `min` or `max`. -Unlike `check_number_range()` in which a user is able to specify whether -left/right boundary is closed or opened. **arguments**: - `val`: - is a number. + is a number. - `min`: - is the minimum of the range. + is the minimum of the range. - `max`: - is the maximum of the range. + is the maximum of the range. + +- `opts`: + `opts.left_closed` can be true or false. + Default is true. + If `opts.left_closed` is true, range contains min value. + + `opts.right_closed` can be true or false. + Default is true. + If `opts.right_closed` is true, range contains max value. **return**: bool @@ -74,7 +91,7 @@ bool ## typeutil.check_number_range **syntax**: -`typeutil.check_number_range(val, min, max, left_closed, right_closed)` +`typeutil.check_number_range(val, min, max, opts)` Return `true` if `val` is in the range, `false` otherwise. @@ -82,41 +99,121 @@ Return `true` if `val` is in the range, **arguments**: - `val`: - is a number. + is a number. - `min`: - is the minimum of the range. + is the minimum of the range. - `max`: - is the maximum of the range. + is the maximum of the range. + +- `opts`: + `opts.left_closed` can be true or false. + Default is true. + If `opts.left_closed` is true, range contains min value. + + `opts.right_closed` can be true or false. + Default is true. + If `opts.right_closed` is true, range contains max value. + +**return**: +bool. + + +## typeutil.check_string_number_range + +**syntax**: +`typeutil.check_string_number_range(val, min, max, opts)` + +Same as `typeutil.check_number_range` except the `val` is a string number. + +## typeutil.check_length_range + +**syntax**: +`typeutil.check_length_range(val, min, max, opts)` + +Return `true` if `val`'s length is in the range, +`false` otherwise. + +**arguments**: + +- `val`: + can be a string or a table. + +- `min`: + is the minimum of the range. + +- `max`: + is the maximum of the range. + +- `opts`: + `opts.left_closed` can be true or false. + Default is true. + If `opts.left_closed` is true, range contains min value. + + `opts.right_closed` can be true or false. + Default is true. + If `opts.right_closed` is true, range contains max value. + +**return**: +bool. + +## typeutil.check_fixed_length + +**syntax**: +`typeutil.check_fixed_length(val, length)` -- `left_closed`: - specifies the open or closed on the left of the range. +Return `true` if `val`'s length equals `length`, +`false` otherwise. - By default it is `true`. +**arguments**: -- `right_closed`: - specifies the open or closed on the right of the range. +- `val`: + can be a string or a table. - By default it is `true`. +- `length`: + is a number. **return**: bool. -## typeutil.check_number_string_range +## typeutil.is_string **syntax**: -`typeutil.check_number_string_range(val, min, max, left_closed, right_closed)` +`typeutil.is_string(val)` -Same as `typeutil.check_number_range` except the `val` is a number or a number -string. +Return `true` if `val` is a string, +`false` otherwise. + +**arguments**: +- `val`: + any. + +**return**; +bool. -## typeutil.is_int +## typeutil.is_number **syntax**: -`typeutil.is_int(val)` +`typeutil.is_number(val)` + +Return `true` if `val` is a number, +`false` otherwise. + +**arguments**: + +- `val`: + any. + +**return**; +bool. + +## typeutil.is_integer + +**syntax**: +`typeutil.is_integer(val)` Return `true` if `val` is an integer, `false` otherwise. @@ -124,7 +221,91 @@ Return `true` if `val` is an integer, **arguments**: - `val`: - is a number. + any. + +**return**; +bool. + +## typeutil.is_boolean + +**syntax**: +`typeutil.is_boolean(val)` + +Return `true` if `val` is a bool, +`false` otherwise. + +**arguments**: + +- `val`: + any. + +**return**; +bool. + +## typeutil.is_string_number + +**syntax**: +`typeutil.is_string_number(val)` + +Return `true` if `val` is a string number, +`false` otherwise. + +**arguments**: + +- `val`: + any. + +**return**; +bool. + +## typeutil.is_array + +**syntax**: +`typeutil.is_array(val)` + +Return `true` if `val` is an array, +`false` otherwise. + +Note that the array follows the cjson definition. +See [encode_sparse_array](https://www.kyne.com.au/~mark/software/lua-cjson-manual.html#encode_sparse_array) +The excessively sparse array is considered to be a dict. +The empty_table is both an array and a dict. +**arguments**: + +- `val`: + any. + +**return**; +bool. + +## typeutil.is_dict + +**syntax**: +`typeutil.is_dict(val)` + +Return `true` if `val` is a dict, +`false` otherwise. + +**arguments**: + +- `val`: + any. + +**return**; +bool. + +## typeutil.is_empty_table + +**syntax**: +`typeutil.is_empty_table(val)` + +Return `true` if `val` is `{}`, +`false` otherwise. + +**arguments**: + +- `val`: + any. **return**; bool. diff --git a/lib/acid/guid.lua b/lib/acid/guid.lua index 566f939..92b3eb8 100644 --- a/lib/acid/guid.lua +++ b/lib/acid/guid.lua @@ -43,9 +43,9 @@ function _M.new(shared_guid, shared_lock, len_ts, len_seq, len_mid) 'shared dict is not exists: %s,%s', shared_guid, shared_lock) end - if not typeutil.check_int_range(len_ts, 1, max_len_ts) - or not typeutil.check_int_range(len_seq, 1) - or not typeutil.check_int_range(len_mid, 1) then + if not typeutil.check_integer_range(len_ts, 1, max_len_ts) + or not typeutil.check_integer_range(len_seq, 1) + or not typeutil.check_integer_range(len_mid, 1) then return nil, 'InvalidLength', string.format( 'lengths of guid parts are not integer or beyond range, %s, %s, %s', @@ -82,7 +82,7 @@ end function _M.generate(self, mid, max_wait_ms) max_wait_ms = max_wait_ms or 500 - if not typeutil.check_int_range(mid, 0, self.max_mid) then + if not typeutil.check_integer_range(mid, 0, self.max_mid) then return nil, 'InvalidMachineId', string.format( 'mid %s is beyond range [0, %d]', tostring(mid), self.max_mid) end diff --git a/lib/acid/typeutil.lua b/lib/acid/typeutil.lua index 487803c..d2ffa66 100644 --- a/lib/acid/typeutil.lua +++ b/lib/acid/typeutil.lua @@ -2,60 +2,153 @@ local _M = {} local INF = math.huge +function _M.is_string(val) + if type(val) ~= 'string' then + return false + end + return true +end -function _M.check_number_range(val, min, max, left_closed, right_closed) - min = min or -INF - max = max or INF +function _M.is_number(val) + if type(val) ~= 'number' then + return false + end + return true +end - if left_closed == nil then - left_closed = true +function _M.is_integer(val) + if _M.is_number(val) ~= true then + return false end + if val % 1 ~= 0 then + return false + end + return true +end - if right_closed == nil then - right_closed = true +function _M.is_boolean(val) + if type(val) ~= 'boolean' then + return false end + return true +end - if type(val) ~= 'number' then +function _M.is_string_number(val) + if type(val) ~= 'string' then return false end + if tonumber(val) == nil then + return false + end + return true +end - if val < min or (val == min and not left_closed) then +-- CJSON array +function _M.is_array(val) + if type(val) ~= 'table' then return false end - if val > max or (val == max and not right_closed) then + if _M.is_empty_table(val) then + return true + end + + local ratio = 2 + local safe = 10 + + local max = 0 + local count = 0 + + for k, _ in pairs(val) do + if type(k) == 'number' then + if k > max then + max = k + end + count = count + 1 + else + return false + end + end + + -- This is a excessively sparse array. + if max > safe and max > ratio * count then return false end return true end +function _M.is_dict(val) + if type(val) ~= 'table' then + return false + end + + if _M.is_empty_table(val) then + return true + end + + return _M.is_array(val) == false +end + +function _M.is_empty_table(val) -function _M.check_number_string_range(val, min, max, left_closed, right_closed) - val = tonumber(val) - if val == nil then + if type(val) ~= 'table' then return false end - return _M.check_number_range(val, min, max, left_closed, right_closed) + if next(val) == nil then + return true + end + + return false end +function _M.check_number_range(val, min, max, opts) + + if _M.is_number(val) == false then + return false + end + + local min = min or -INF + local max = max or INF + + if opts == nil then + opts = {} + end -function _M.is_int(val) - if type(val) ~= 'number' or val % 1 ~= 0 then + if val < min or (val == min and opts.left_closed == false) then + return false + end + + if val > max or (val == max and opts.right_closed == false) then return false end return true end +function _M.check_string_number_range(val, min, max, opts) + if _M.is_string_number(val) then + return _M.check_number_range(tonumber(val), min, max, opts) + end + + return false +end -function _M.check_int_range(val, min, max) - if _M.is_int(val) and _M.check_number_range(val, min, max, true, true) then +function _M.check_integer_range(val, min, max, opts) + if _M.is_integer(val) and _M.check_number_range(val, min, max, opts) then return true end return false end +function _M.check_length_range(val, min, max, opts) + return _M.check_number_range(#val, min, max, opts) +end + +function _M.check_fixed_length(val, length) + return #val == length +end + return _M diff --git a/lib/test_typeutil.lua b/lib/test_typeutil.lua index b6443e5..b9e9d3d 100644 --- a/lib/test_typeutil.lua +++ b/lib/test_typeutil.lua @@ -29,12 +29,12 @@ function test.check_number_range(t) {true, 0, 1, nil, nil, false, 'range = [0, 1]' }, }) do - local res = typeutil.check_number_range(value, min, max, left_closed, right_closed) + local res = typeutil.check_number_range(value, min, max, { left_closed = left_closed, right_closed = right_closed }) t:eq(expected, res, desc) end end -function test.check_number_string_range(t) +function test.check_string_number_range(t) for _, value, min, max, left_closed, right_closed, expected, desc in t:case_iter(6, { {'0', 0, 1, nil, nil, true, 'range = [0, 1]' }, @@ -49,11 +49,11 @@ function test.check_number_string_range(t) {'0', nil, 0, nil, false, false, 'range = [-inf, 0)' }, {'0', nil, nil, nil, nil, true, 'range = [-inf, inf]' }, {'0', nil, nil, false, false, true, 'range = (-inf, inf)' }, - {0, 0, 1, nil, nil, true, 'range = [0, 1]' }, - {1, 0, 1, nil, nil, true, 'range = [0, 1]' }, + {0, 0, 1, nil, nil, false, 'range = [0, 1]' }, + {1, 0, 1, nil, nil, false, 'range = [0, 1]' }, {0, 0, 1, false, nil, false, 'range = (0, 1]' }, - {1, 0, 1, false, nil, true, 'range = (0, 1]' }, - {0, 0, 1, nil, false, true, 'range = [0, 1)' }, + {1, 0, 1, false, nil, false, 'range = (0, 1]' }, + {0, 0, 1, nil, false, false, 'range = [0, 1)' }, {1, 0, 1, nil, false, false, 'range = [0, 1)' }, {0, 0, 1, false, false, false, 'range = (0, 1)' }, {1, 0, 1, false, false, false, 'range = (0, 1)' }, @@ -64,32 +64,156 @@ function test.check_number_string_range(t) {true, 0, 1, nil, nil, false, 'range = [0, 1]' }, }) do - local res = typeutil.check_number_string_range( - value, min, max, left_closed, right_closed) + local res = typeutil.check_string_number_range( + value, min, max, { left_closed = left_closed, right_closed = right_closed }) t:eq(expected, res, desc) end end -function test.is_int(t) +function test.is_integer(t) for _, value, expected, desc in t:case_iter(2, { {0, true }, {0.5, false }, {1, true }, + {1.0, true }, {'0', false }, {'1', false }, {nil, false }, {'abc', false }, }) do - local res = typeutil.is_int(value) + local res = typeutil.is_integer(value) + t:eq(expected, res) + end +end + +function test.is_string(t) + for _, value, expected, desc in t:case_iter(2, { + {1, false }, + {'0', true }, + {nil, false }, + {'abc', true }, + {{a=1}, false }, + }) do + + local res = typeutil.is_string(value) + t:eq(expected, res) + end +end + +function test.is_number(t) + for _, value, expected, desc in t:case_iter(2, { + {1, true }, + {1.1, true }, + {-1.1, true }, + {nil, false }, + {'abc', false }, + {{a=1}, false }, + }) do + + local res = typeutil.is_number(value) + t:eq(expected, res) + end +end + +function test.is_boolean(t) + for _, value, expected, desc in t:case_iter(2, { + {true, true }, + {false, true }, + {-1.1, false }, + {nil, false }, + {'abc', false }, + {{a=1}, false }, + }) do + + local res = typeutil.is_boolean(value) + t:eq(expected, res) + end +end + +function test.is_string_number(t) + for _, value, expected, desc in t:case_iter(2, { + {false, false }, + {-1.1, false }, + {nil, false }, + {'abc', false }, + {{a=1}, false }, + { 1, false }, + {'10.0',true }, + {'1', true }, + }) do + + local res = typeutil.is_string_number(value) + t:eq(expected, res) + end +end + +function test.is_array(t) + for _, value, expected, desc in t:case_iter(2, { + {false, false }, + {-1.1, false }, + {nil, false }, + {'abc', false }, + {{a=1}, false }, + { 1, false }, + {'10.0', false }, + {{}, true }, + {{{}}, true }, + {{1,2,3}, true }, + {{[1]=1,2,3}, true }, + {{[1]=1,2,[100]=3}, false }, + {{[1]=1,2,[9]=3}, true }, + {{1,2,3,4,5,6,7,8,9,[15]=11}, true }, + }) do + + local res = typeutil.is_array(value) + t:eq(expected, res) + end +end + +function test.is_dict(t) + for _, value, expected, desc in t:case_iter(2, { + {false, false }, + {-1.1, false }, + {nil, false }, + {'abc', false }, + { 1, false }, + {'10.0', false }, + {{a=1}, true }, + {{}, true }, + {{{}}, false }, + {{1,2,3}, false }, + {{[1]=1,2,3}, false }, + {{[1]=1,2,[100]=3}, true }, + {{[1]=1,2,[9]=3}, false }, + {{1,2,3,4,5,6,7,8,9,[15]=11}, false }, + }) do + + local res = typeutil.is_dict(value) + t:eq(expected, res) + end +end + +function test.is_empty_table(t) + for _, value, expected, desc in t:case_iter(2, { + {false, false }, + {-1.1, false }, + {nil, false }, + {'abc', false }, + { 1, false }, + {'10.0', false }, + {{a=1}, false }, + {{{}}, false }, + {{}, true }, + }) do - dd(value, res) + local res = typeutil.is_empty_table(value) t:eq(expected, res) end end -function test.check_int_range(t) +function test.check_integer_range(t) for _, value, min, max, expected, desc in t:case_iter(4, { {0, 0, 1, true }, @@ -103,9 +227,54 @@ function test.check_int_range(t) {'abc', 0, 1, false }, }) do - local res = typeutil.check_int_range(value, min, max) + local res = typeutil.check_integer_range(value, min, max) + t:eq(expected, res) + end +end + +function test.check_length_range(t) + for _, value, min, max, left_closed, right_closed, expected, desc in t:case_iter(6, { + {'', 0, 1, nil, nil, true, 'range = [0, 1]' }, + {'1', 0, 1, nil, nil, true, 'range = [0, 1]' }, + {'', 0, 1, false, nil, false, 'range = (0, 1]' }, + {'a', 0, 1, false, nil, true, 'range = (0, 1]' }, + {'', 0, 1, nil, false, true, 'range = [0, 1)' }, + {'a', 0, 1, nil, false, false, 'range = [0, 1)' }, + {'', 0, 1, false, false, false, 'range = (0, 1)' }, + {'a', 0, 1, false, false, false, 'range = (0, 1)' }, + {'', 0, nil, nil, nil, true, 'range = [0, inf]' }, + {'', 0, nil, false, nil, false, 'range = (0, inf]' }, + {'', nil, 0, nil, nil, true, 'range = [-inf, 0]' }, + {'', nil, 0, nil, false, false, 'range = [-inf, 0)' }, + {'', nil, nil, nil, nil, true, 'range = [-inf, inf]' }, + {'', nil, nil, false, false, true, 'range = (-inf, inf)' }, + {'1', 0, 1, false, false, false, 'range = (0, 1)' }, + {{}, 0, 1, nil, nil, true, 'range = [0, 1]' }, + {{1}, 0, 1, nil, nil, true, 'range = [0, 1]' }, + {{1,[10]=1}, 0, 1, nil, nil, true, 'range = [0, 1]' }, + {{1,2}, 0, 1, nil, nil, false, 'range = [0, 1]' }, + + }) do + + local res = typeutil.check_length_range(value, min, max, { left_closed = left_closed, right_closed = right_closed }) + t:eq(expected, res, desc) + - dd(value, min, max, res) - t:eq(expected, res, value) end end + +function test.check_fixed_length(t) + + for _, value, length, expected, desc in t:case_iter(3, { + {'', 0, true }, + {'1a', 2, true }, + {'12', 1, false }, + {{}, 0, true }, + {{1,2}, 2, true }, + {{1,[10]=1}, 1, true }, + }) do + + local res = typeutil.check_fixed_length(value, length) + t:eq(expected, res) + end +end \ No newline at end of file