Skip to content

Accept cdata number as float and forbid NaN/Inf #49

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

Merged
merged 2 commits into from
Apr 4, 2022
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
21 changes: 20 additions & 1 deletion graphql/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,27 @@ types.long = types.scalar({
isValueOfTheType = isLong,
})

-- Return `false` for NaN, Negative Infinity or Positive Infinity.
-- Return `true` otherwise.
local function isfinite(n)
local d = n - n
return n == n and d == d
end

local function isFloat(value)
return type(value) == 'number'
-- http://spec.graphql.org/October2021/#sec-Float
--
-- > Non-finite floating-point internal values (NaN and Infinity) cannot be
-- > coerced to Float and must raise a field error.
if type(value) == 'number' then
return isfinite(value)
end

if type(value) == 'cdata' then
return ffi.istype('int64_t', value) or ffi.istype('uint64_t', value)
end

return false
end

local function coerceFloat(value)
Expand Down
96 changes: 96 additions & 0 deletions test/integration/graphql_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2051,3 +2051,99 @@ g.test_propagate_defaults_to_callback = function()
t.assert_equals(errors, nil)
t.assert_items_equals(json.decode(data.prefix.test_mutation), result)
end

-- gh-47: accept a cdata number as a value of a Float variable.
function g.test_cdata_number_as_float()
local query = [[
query ($x: Float!) { test(arg: $x) }
]]

local function callback(_, args)
return args[1].value
end

local query_schema = {
['test'] = {
kind = types.float.nonNull,
arguments = {
arg = types.float.nonNull,
},
resolve = callback,
}
}

-- 2^64-1
local variables = {x = 18446744073709551615ULL}
local res = check_request(query, query_schema, nil, nil, {variables = variables})
t.assert_type(res, 'table')
t.assert_almost_equals(res.test, 18446744073709551615)
end

-- Accept a large number in a Float argument.
--
-- The test is created in the scope of gh-47, but it is not
-- strictly related to it: the issue is about interpreting
-- a cdata number in a **variable** as a `Float` value.
--
-- Here we check a large number, which is written verbatim as
-- an argument in a query. Despite that it is not what is
-- described in gh-47, it worth to have such a test.
function g.test_large_float_argument()
-- 2^64-1
local query = [[
{ test(arg: 18446744073709551615) }
]]

local function callback(_, args)
return args[1].value
end

local query_schema = {
['test'] = {
kind = types.float.nonNull,
arguments = {
arg = types.float.nonNull,
},
resolve = callback,
}
}

local res = check_request(query, query_schema)
t.assert_type(res, 'table')
t.assert_almost_equals(res.test, 18446744073709551615)
end

-- http://spec.graphql.org/October2021/#sec-Float
--
-- > Non-finite floating-point internal values (NaN and Infinity) cannot be
-- > coerced to Float and must raise a field error.
function g.test_non_finite_float()
local query = [[
query ($x: Float!) { test(arg: $x) }
]]

local function callback(_, args)
return args[1].value
end

local query_schema = {
['test'] = {
kind = types.float.nonNull,
arguments = {
arg = types.float.nonNull,
},
resolve = callback,
}
}

local nan = 0 / 0
local inf = 1 / 0
local ninf = -inf

for _, x in pairs({nan, inf, ninf}) do
local variables = {x = x}
t.assert_error_msg_content_equals(
'Wrong variable "x" for the Scalar "Float"', check_request, query,
query_schema, nil, nil, {variables = variables})
end
end