From 27992ddffbdd6b150c0b8e0acb3172a11a53c654 Mon Sep 17 00:00:00 2001 From: Yaroslav Shumakov Date: Tue, 19 Apr 2022 09:02:58 +0300 Subject: [PATCH] Fix returning gapped arrays --- graphql/execute.lua | 2 +- graphql/util.lua | 44 +++++++++++-------------------- test/integration/graphql_test.lua | 33 +++++++++++++++++++++++ test/unit/graphql_test.lua | 15 +++++++++++ 4 files changed, 64 insertions(+), 30 deletions(-) diff --git a/graphql/execute.lua b/graphql/execute.lua index 8622736..fe1d1bd 100644 --- a/graphql/execute.lua +++ b/graphql/execute.lua @@ -208,7 +208,7 @@ local function completeValue(fieldType, result, subSelections, context, opts) local innerType = fieldType.ofType local values = {} - for i, value in ipairs(result) do + for i, value in pairs(result) do values[i] = completeValue(innerType, value, subSelections, context) end diff --git a/graphql/util.lua b/graphql/util.lua index 7d6901f..852e26c 100644 --- a/graphql/util.lua +++ b/graphql/util.lua @@ -228,37 +228,23 @@ local function check(obj, obj_name, type_1, type_2, type_3) end end ---- Check whether table is an array. ---- ---- Based on [that][1] implementation. ---- [1]: https://github.com/mpx/lua-cjson/blob/db122676/lua/cjson/util.lua ---- ---- @tparam table table to check ---- @return[1] `true` if passed table is an array (includes the empty table ---- case) ---- @return[2] `false` otherwise -local function is_array(table) - if type(table) ~= 'table' then - return false - end - - local max = 0 - local count = 0 - for k, _ in pairs(table) do - if type(k) == 'number' then - if k > max then - max = k - end - count = count + 1 - else - return false - end - end - if max > count * 2 then +local function is_array(t) + if type(t) ~= 'table' then + return false + end + local n = #t + if n > 0 then + for k in next, t, n do + if type(k) ~= 'number' or k < 0 then return false end + end + return true + end + for k in pairs(t) do + if type(k) ~= 'number' or k < 0 then return false end - - return max >= 0 + end + return true end -- Copied from tarantool/tap diff --git a/test/integration/graphql_test.lua b/test/integration/graphql_test.lua index 40c8c8c..b8dc12e 100644 --- a/test/integration/graphql_test.lua +++ b/test/integration/graphql_test.lua @@ -2236,3 +2236,36 @@ function g.test_huge_cdata_number() t.assert_equals(json.encode(res), v[2]) end end + +function g.test_gapped_arrays_output() + local query = [[ + query ($x: [String]) { test(arg: $x) } + ]] + + local function callback(_, args) + setmetatable(args.arg, {__serialize='array'}) + return args.arg + end + + local query_schema = { + ['test'] = { + kind = types.list(types.string), + arguments = { + arg = types.list(types.string), + }, + resolve = callback, + } + } + + local test_values = { + {[3] = 'a', [1] = 'b', [6] = 'c'}, + {[3] = 'a', [1] = 'b', [7] = 'c'}, + } + + for _, v in ipairs(test_values) do + local variables = {x = v} + local res = check_request(query, query_schema, nil, nil, {variables = variables}) + t.assert_type(res, 'table') + t.assert_equals(res.test, v) + end +end diff --git a/test/unit/graphql_test.lua b/test/unit/graphql_test.lua index c6d9f2b..41c9dcb 100644 --- a/test/unit/graphql_test.lua +++ b/test/unit/graphql_test.lua @@ -1092,3 +1092,18 @@ end g.test_version = function() t.assert_type(require('graphql').VERSION, 'string') end + +function g.test_is_array() + t.assert_equals(util.is_array({[3] = 'a', [1] = 'b', [6] = 'c'}), true) + t.assert_equals(util.is_array({[3] = 'a', [1] = 'b', [7] = 'c'}), true) + t.assert_equals(util.is_array({[3] = 'a', nil, [6] = 'c'}), true) + t.assert_equals(util.is_array({[3] = 'a', nil, [7] = 'c'}), true) + t.assert_equals(util.is_array({[0] = 'a', [1] = 'b', [6] = 'c'}), true) + t.assert_equals(util.is_array({[-1] = 'a', [1] = 'b', [6] = 'c'}), false) + t.assert_equals(util.is_array({[3] = 'a', b = 'b', [6] = 'c'}), false) + t.assert_equals(util.is_array({}), true) + t.assert_equals(util.is_array(), false) + t.assert_equals(util.is_array(''), false) + t.assert_equals(util.is_array({a = 'a', b = 'b', c = 'c'}), false) + t.assert_equals(util.is_array({a = 'a', nil, c = 'c'}), false) +end