Skip to content

Commit af0ea41

Browse files
committed
Accept a cdata number as value of a Float variable
It is quite usual that a floating point number is encoded using JSON, goes over HTTP, received on tarantool server, decoded from JSON and passed as a variable into the GraphQL executor. A number out of the [-10^14+1; 10^14-1] range is decoded into Lua as cdata number[^1] to don't loss the precision. A JSON decoder don't know that we intend to use this value as the floating point one and chooses the conservative option: decode the value into cdata number to don't loss the precision. In GraphQL we know that it is actually the floating point value (disregarding its representation in Lua). So it looks correct to accept a cdata number as value of a Float variable. The similar idea was expressed in [1] against tarantool itself: let it accept a number without fractional part as a value suitable for the `float` field type. [1]: tarantool/tarantool#5933 [^1]: `cdata<int64_t>` or `cdata<uint64_t>` Fixes #47
1 parent 17b642d commit af0ea41

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed

graphql/types.lua

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,15 @@ types.long = types.scalar({
318318
})
319319

320320
local function isFloat(value)
321-
return type(value) == 'number'
321+
if type(value) == 'number' then
322+
return true
323+
end
324+
325+
if type(value) == 'cdata' then
326+
return ffi.istype('int64_t', value) or ffi.istype('uint64_t', value)
327+
end
328+
329+
return false
322330
end
323331

324332
local function coerceFloat(value)

test/integration/graphql_test.lua

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2051,3 +2051,64 @@ g.test_propagate_defaults_to_callback = function()
20512051
t.assert_equals(errors, nil)
20522052
t.assert_items_equals(json.decode(data.prefix.test_mutation), result)
20532053
end
2054+
2055+
-- gh-47: accept a cdata number as a value of a Float variable.
2056+
function g.test_cdata_number_as_float()
2057+
local query = [[
2058+
query ($x: Float!) { test(arg: $x) }
2059+
]]
2060+
2061+
local function callback(_, args)
2062+
return args[1].value
2063+
end
2064+
2065+
local query_schema = {
2066+
['test'] = {
2067+
kind = types.float.nonNull,
2068+
arguments = {
2069+
arg = types.float.nonNull,
2070+
},
2071+
resolve = callback,
2072+
}
2073+
}
2074+
2075+
-- 2^64-1
2076+
local variables = {x = 18446744073709551615ULL}
2077+
local res = check_request(query, query_schema, nil, nil, {variables = variables})
2078+
t.assert_type(res, 'table')
2079+
t.assert_almost_equals(res.test, 18446744073709551615)
2080+
end
2081+
2082+
-- Accept a large number in a Float argument.
2083+
--
2084+
-- The test is created in the scope of gh-47, but it is not
2085+
-- strictly related to it: the issue is about interpreting
2086+
-- a cdata number in a **variable** as a `Float` value.
2087+
--
2088+
-- Here we check a large number, which is written verbatim as
2089+
-- an argument in a query. Despite that it is not what is
2090+
-- described in gh-47, it worth to have such a test.
2091+
function g.test_large_float_argument()
2092+
-- 2^64-1
2093+
local query = [[
2094+
{ test(arg: 18446744073709551615) }
2095+
]]
2096+
2097+
local function callback(_, args)
2098+
return args[1].value
2099+
end
2100+
2101+
local query_schema = {
2102+
['test'] = {
2103+
kind = types.float.nonNull,
2104+
arguments = {
2105+
arg = types.float.nonNull,
2106+
},
2107+
resolve = callback,
2108+
}
2109+
}
2110+
2111+
local res = check_request(query, query_schema)
2112+
t.assert_type(res, 'table')
2113+
t.assert_almost_equals(res.test, 18446744073709551615)
2114+
end

0 commit comments

Comments
 (0)