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

refactor(tools): separate table related functions from utils #11723

Merged
merged 6 commits into from
Oct 25, 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
1 change: 1 addition & 0 deletions kong-3.6.0-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ build = {
["kong.tools.mime_type"] = "kong/tools/mime_type.lua",
["kong.tools.request_aware_table"] = "kong/tools/request_aware_table.lua",
["kong.tools.string"] = "kong/tools/string.lua",
["kong.tools.table"] = "kong/tools/table.lua",

["kong.runloop.handler"] = "kong/runloop/handler.lua",
["kong.runloop.events"] = "kong/runloop/events.lua",
Expand Down
323 changes: 323 additions & 0 deletions kong/tools/table.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
local type = type
local pairs = pairs
local ipairs = ipairs
local select = select
local tostring = tostring
local insert = table.insert
local setmetatable = setmetatable
local getmetatable = getmetatable


local _M = {}


--- packs a set of arguments in a table.
-- Explicitly sets field `n` to the number of arguments, so it is `nil` safe
_M.pack = function(...) return {n = select("#", ...), ...} end


--- unpacks a table to a list of arguments.
-- Explicitly honors the `n` field if given in the table, so it is `nil` safe
_M.unpack = function(t, i, j) return unpack(t, i or 1, j or t.n or #t) end


--- Merges two table together.
-- A new table is created with a non-recursive copy of the provided tables
-- @param t1 The first table
-- @param t2 The second table
-- @return The (new) merged table
function _M.table_merge(t1, t2)
local res = {}
if t1 then
for k,v in pairs(t1) do
res[k] = v
end
end
if t2 then
for k,v in pairs(t2) do
res[k] = v
end
end
return res
end


--- Checks if a value exists in a table.
-- @param arr The table to use
-- @param val The value to check
-- @return Returns `true` if the table contains the value, `false` otherwise
function _M.table_contains(arr, val)
if arr then
for _, v in pairs(arr) do
if v == val then
return true
end
end
end
return false
end


do
local floor = math.floor
local max = math.max

local is_array_fast = require "table.isarray"

local is_array_strict = function(t)
local m, c = 0, 0
for k in pairs(t) do
if type(k) ~= "number" or k < 1 or floor(k) ~= k then
return false
end
m = max(m, k)
c = c + 1
end
return c == m
end

local is_array_lapis = function(t)
if type(t) ~= "table" then
return false
end
local i = 0
for _ in pairs(t) do
i = i + 1
if t[i] == nil and t[tostring(i)] == nil then
return false
end
end
return true
end

--- Checks if a table is an array and not an associative array.
-- @param t The table to check
-- @param mode: `"strict"`: only sequential indices starting from 1 are allowed (no holes)
-- `"fast"`: OpenResty optimized version (holes and negative indices are ok)
-- `"lapis"`: Allows numeric indices as strings (no holes)
-- @return Returns `true` if the table is an array, `false` otherwise
function _M.is_array(t, mode)
if type(t) ~= "table" then
return false
end

if mode == "lapis" then
return is_array_lapis(t)
end

if mode == "fast" then
return is_array_fast(t)
end

return is_array_strict(t)
end
end


--- Checks if a table is an array and not an associative array.
-- *** NOTE *** string-keys containing integers are considered valid array entries!
-- @param t The table to check
-- @return Returns `true` if the table is an array, `false` otherwise
function _M.is_lapis_array(t)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why the original code has two same functions

Copy link
Contributor Author

@chronolaw chronolaw Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have the same feeling with you, but this PR is refactoring, won't change anything.

After this PR, we could check the code carefully.

if type(t) ~= "table" then
return false
end
local i = 0
for _ in pairs(t) do
i = i + 1
if t[i] == nil and t[tostring(i)] == nil then
return false
end
end
return true
end


--- Deep copies a table into a new table.
-- Tables used as keys are also deep copied, as are metatables
-- @param orig The table to copy
-- @param copy_mt Copy metatable (default is true)
-- @return Returns a copy of the input table
function _M.deep_copy(orig, copy_mt)
if copy_mt == nil then
copy_mt = true
end
local copy
if type(orig) == "table" then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[_M.deep_copy(orig_key)] = _M.deep_copy(orig_value, copy_mt)
end
if copy_mt then
setmetatable(copy, _M.deep_copy(getmetatable(orig)))
end
else
copy = orig
end
return copy
end


do
local clone = require "table.clone"

--- Copies a table into a new table.
-- neither sub tables nor metatables will be copied.
-- @param orig The table to copy
-- @return Returns a copy of the input table
function _M.shallow_copy(orig)
local copy
if type(orig) == "table" then
copy = clone(orig)
else -- number, string, boolean, etc
copy = orig
end
return copy
end
end


--- Merges two tables recursively
-- For each sub-table in t1 and t2, an equivalent (but different) table will
-- be created in the resulting merge. If t1 and t2 have a sub-table with the
-- same key k, res[k] will be a deep merge of both sub-tables.
-- Metatables are not taken into account.
-- Keys are copied by reference (if tables are used as keys they will not be
-- duplicated)
-- @param t1 one of the tables to merge
-- @param t2 one of the tables to merge
-- @return Returns a table representing a deep merge of the new table
function _M.deep_merge(t1, t2)
local res = _M.deep_copy(t1)

for k, v in pairs(t2) do
if type(v) == "table" and type(res[k]) == "table" then
res[k] = _M.deep_merge(res[k], v)
else
res[k] = _M.deep_copy(v) -- returns v when it is not a table
end
end

return res
end


--- Cycle aware deep copies a table into a new table.
-- Cycle aware means that a table value is only copied once even
-- if it is referenced multiple times in input table or its sub-tables.
-- Tables used as keys are not deep copied. Metatables are set to same
-- on copies as they were in the original.
-- @param orig The table to copy
-- @param remove_metatables Removes the metatables when set to `true`.
-- @param deep_copy_keys Deep copies the keys (and not only the values) when set to `true`.
-- @param cycle_aware_cache Cached tables that are not copied (again).
-- (the function creates this table when not given)
-- @return Returns a copy of the input table
function _M.cycle_aware_deep_copy(orig, remove_metatables, deep_copy_keys, cycle_aware_cache)
if type(orig) ~= "table" then
return orig
end

cycle_aware_cache = cycle_aware_cache or {}
if cycle_aware_cache[orig] then
return cycle_aware_cache[orig]
end

local copy = _M.shallow_copy(orig)

cycle_aware_cache[orig] = copy

local mt
if not remove_metatables then
mt = getmetatable(orig)
end

for k, v in pairs(orig) do
if type(v) == "table" then
copy[k] = _M.cycle_aware_deep_copy(v, remove_metatables, deep_copy_keys, cycle_aware_cache)
end

if deep_copy_keys and type(k) == "table" then
local new_k = _M.cycle_aware_deep_copy(k, remove_metatables, deep_copy_keys, cycle_aware_cache)
copy[new_k] = copy[k]
copy[k] = nil
end
end

if mt then
setmetatable(copy, mt)
end

return copy
end


--- Cycle aware merges two tables recursively
-- The table t1 is deep copied using cycle_aware_deep_copy function.
-- The table t2 is deep merged into t1. The t2 values takes precedence
-- over t1 ones. Tables used as keys are not deep copied. Metatables
-- are set to same on copies as they were in the original.
-- @param t1 one of the tables to merge
-- @param t2 one of the tables to merge
-- @param remove_metatables Removes the metatables when set to `true`.
-- @param deep_copy_keys Deep copies the keys (and not only the values) when set to `true`.
-- @param cycle_aware_cache Cached tables that are not copied (again)
-- (the function creates this table when not given)
-- @return Returns a table representing a deep merge of the new table
function _M.cycle_aware_deep_merge(t1, t2, remove_metatables, deep_copy_keys, cycle_aware_cache)
cycle_aware_cache = cycle_aware_cache or {}
local merged = _M.cycle_aware_deep_copy(t1, remove_metatables, deep_copy_keys, cycle_aware_cache)
for k, v in pairs(t2) do
if type(v) == "table" then
if type(merged[k]) == "table" then
merged[k] = _M.cycle_aware_deep_merge(merged[k], v, remove_metatables, deep_copy_keys, cycle_aware_cache)
else
merged[k] = _M.cycle_aware_deep_copy(v, remove_metatables, deep_copy_keys, cycle_aware_cache)
end
else
merged[k] = v
end
end
return merged
end


--- Concatenates lists into a new table.
function _M.concat(...)
local result = {}
for _, t in ipairs({...}) do
for _, v in ipairs(t) do insert(result, v) end
end
return result
end


local err_list_mt = {}


--- Add an error message to a key/value table.
-- If the key already exists, a sub table is created with the original and the new value.
-- @param errors (Optional) Table to attach the error to. If `nil`, the table will be created.
-- @param k Key on which to insert the error in the `errors` table.
-- @param v Value of the error
-- @return The `errors` table with the new error inserted.
function _M.add_error(errors, k, v)
if not errors then
errors = {}
end

if errors and errors[k] then
if getmetatable(errors[k]) ~= err_list_mt then
errors[k] = setmetatable({errors[k]}, err_list_mt)
end

insert(errors[k], v)
else
errors[k] = v
end

return errors
end


return _M
Loading