Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit 7c3b14c

Browse files
committed
WIP: Validate a nested variable by an argument type
1 parent ebcdd14 commit 7c3b14c

File tree

4 files changed

+184
-44
lines changed

4 files changed

+184
-44
lines changed

graphql/core/rules.lua

Lines changed: 77 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ local util = require(path .. '.util')
44
local schema = require(path .. '.schema')
55
local introspection = require(path .. '.introspection')
66
local query_util = require(path .. '.query_util')
7+
local graphql_utils = require('graphql.utils')
78

89
local function getParentField(context, name, count)
910
if introspection.fieldMap[name] then return introspection.fieldMap[name] end
@@ -484,66 +485,100 @@ function rules.variableUsageAllowed(node, context)
484485
local parentField = getParentField(context, field)
485486
for i = 1, #arguments[field] do
486487
local argument = arguments[field][i]
487-
if argument.value.kind == 'variable' then
488-
local argumentType = parentField.arguments[argument.name.value]
488+
local argumentType = parentField.arguments[argument.name.value]
489+
local function recursiveValidateVariableType(argument, argumentType)
490+
if argument.value.kind == 'variable' then
491+
local variableName = argument.value.name.value
492+
local variableDefinition = variableMap[variableName]
493+
local hasDefault = variableDefinition.defaultValue ~= nil
494+
495+
local variableType = query_util.typeFromAST(variableDefinition.type,
496+
context.schema)
497+
498+
if hasDefault and variableType.__type ~= 'NonNull' then
499+
variableType = types.nonNull(variableType)
500+
end
489501

490-
local variableName = argument.value.name.value
491-
local variableDefinition = variableMap[variableName]
492-
local hasDefault = variableDefinition.defaultValue ~= nil
502+
local function isTypeSubTypeOf(subType, superType)
503+
if subType == superType then return true end
493504

494-
local variableType = query_util.typeFromAST(variableDefinition.type,
495-
context.schema)
505+
if superType.__type == 'NonNull' then
506+
if subType.__type == 'NonNull' then
507+
return isTypeSubTypeOf(subType.ofType, superType.ofType)
508+
end
496509

497-
if hasDefault and variableType.__type ~= 'NonNull' then
498-
variableType = types.nonNull(variableType)
499-
end
510+
return false
511+
elseif subType.__type == 'NonNull' then
512+
return isTypeSubTypeOf(subType.ofType, superType)
513+
end
514+
515+
if superType.__type == 'List' then
516+
if subType.__type == 'List' then
517+
return isTypeSubTypeOf(subType.ofType, superType.ofType)
518+
end
500519

501-
local function isTypeSubTypeOf(subType, superType)
502-
if subType == superType then return true end
520+
return false
521+
elseif subType.__type == 'List' then
522+
return false
523+
end
503524

504-
if superType.__type == 'NonNull' then
505-
if subType.__type == 'NonNull' then
506-
return isTypeSubTypeOf(subType.ofType, superType.ofType)
525+
-- XXX: InputMap, ...; all named types must be allowed
526+
if subType.__type ~= 'Object' and
527+
subType.__type ~= 'InputObject' then
528+
return false
507529
end
508530

509-
return false
510-
elseif subType.__type == 'NonNull' then
511-
return isTypeSubTypeOf(subType.ofType, superType)
512-
end
531+
if superType.__type == 'Interface' then
532+
local implementors = context.schema:getImplementors(superType.name)
533+
return implementors and implementors[context.schema:getType(subType.name)]
534+
elseif superType.__type == 'Union' or
535+
(superType.__type == 'Scalar' and
536+
superType.subtype == 'InputUnion') then
537+
--false then
538+
local types = superType.types
539+
for i = 1, #types do
540+
if types[i] == subType then
541+
return true
542+
end
543+
end
513544

514-
if superType.__type == 'List' then
515-
if subType.__type == 'List' then
516-
return isTypeSubTypeOf(subType.ofType, superType.ofType)
545+
return false
517546
end
518547

519-
return false
520-
elseif subType.__type == 'List' then
521548
return false
522549
end
523550

524-
if subType.__type ~= 'Object' then return false end
525-
526-
if superType.__type == 'Interface' then
527-
local implementors = context.schema:getImplementors(superType.name)
528-
return implementors and implementors[context.schema:getType(subType.name)]
529-
elseif superType.__type == 'Union' then
530-
local types = superType.types
531-
for i = 1, #types do
532-
if types[i] == subType then
533-
return true
551+
if not isTypeSubTypeOf(variableType, argumentType) then
552+
error('Variable type mismatch')
553+
end
554+
elseif argument.value.kind == 'inputObject' then
555+
for _, child in ipairs(argument.value.values) do
556+
if argumentType.__type == 'InputObject' then
557+
local childArgumentType = argumentType.fields[child.name].kind
558+
recursiveValidateVariableType(child, childArgumentType)
559+
elseif argumentType.__type == 'Scalar' and
560+
argumentType.subtype == 'InputMap' then
561+
local childArgumentType = argumentType.values
562+
recursiveValidateVariableType(child, childArgumentType)
563+
elseif argumentType.__type == 'Scalar' and
564+
argumentType.subtype == 'InputUnion' then
565+
local has_ok
566+
local first_err
567+
for _, childArgumentType in ipairs(argumentType.types) do
568+
local ok, err = pcall(recursiveValidateVariableType, child,
569+
childArgumentType)
570+
has_ok = has_ok or ok
571+
first_err = first_err or graphql_utils.strip_error(err)
572+
if ok then break end
573+
end
574+
if not has_ok then
575+
error(first_err)
534576
end
535577
end
536-
537-
return false
538578
end
539-
540-
return false
541-
end
542-
543-
if not isTypeSubTypeOf(variableType, argumentType) then
544-
error('Variable type mismatch')
545579
end
546580
end
581+
recursiveValidateVariableType(argument, argumentType)
547582
end
548583
end
549584
end

graphql/core/schema.lua

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ function schema:generateTypeMap(node)
5050
node.fields = type(node.fields) == 'function' and node.fields() or node.fields
5151
self.typeMap[node.name] = node
5252

53-
if node.__type == 'Union' then
53+
if node.__type == 'Union' or (node.__type == 'Scalar' and
54+
node.subtype == 'InputUnion') then
5455
for _, type in ipairs(node.types) do
5556
self:generateTypeMap(type)
5657
end
@@ -77,6 +78,10 @@ function schema:generateTypeMap(node)
7778
self:generateTypeMap(field.kind)
7879
end
7980
end
81+
82+
if node.type == 'Scalar' and node.subtype == 'InputMap' then
83+
self:generateTypeMap(node.values)
84+
end
8085
end
8186

8287
function schema:generateDirectiveMap()

graphql/core/types.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ function types.inputUnion(config)
249249
__type = 'Scalar',
250250
subtype = 'InputUnion',
251251
name = config.name,
252+
types = config.types,
252253
serialize = function(value) return value end,
253254
parseValue = function(value) return value end,
254255
parseLiteral = function(node)

test/testdata/common_testdata.lua

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ end
360360

361361
function common_testdata.run_queries(gql_wrapper)
362362
local test = tap.test('common')
363-
test:plan(41)
363+
test:plan(47)
364364

365365
local query_1 = [[
366366
query user_by_order($order_id: String) {
@@ -1423,6 +1423,9 @@ function common_testdata.run_queries(gql_wrapper)
14231423

14241424
-- }}}
14251425

1426+
-- {{{ InputUnion -- XXX
1427+
-- }}}
1428+
14261429
local query_14 = [[
14271430
mutation($order_metainfo: order_metainfo_collection_insert) {
14281431
order_metainfo_collection(insert: $order_metainfo) {
@@ -1472,6 +1475,102 @@ function common_testdata.run_queries(gql_wrapper)
14721475
-- }}}
14731476
-- }}}
14741477

1478+
-- {{{ check variable type againts argument type
1479+
1480+
local query_15 = [[
1481+
mutation($tags: [Int!]) {
1482+
order_metainfo_collection(
1483+
update: {store: {tags: $tags}}
1484+
limit: 1
1485+
) {
1486+
order_metainfo_id
1487+
store { tags }
1488+
}
1489+
}
1490+
]]
1491+
local ok, err = pcall(gql_wrapper.compile, gql_wrapper, query_15)
1492+
local err_exp = 'Variable type mismatch'
1493+
test:is_deeply({ok, utils.strip_error(err)}, {false, err_exp},
1494+
'variable usage inside InputObject')
1495+
1496+
local query_16 = [[
1497+
mutation($tag_value: Int) {
1498+
order_metainfo_collection(
1499+
update: {store: {parametrized_tags: {foo: $tag_value}}}
1500+
limit: 1
1501+
) {
1502+
order_metainfo_id
1503+
}
1504+
}
1505+
]]
1506+
local ok, err = pcall(gql_wrapper.compile, gql_wrapper, query_16)
1507+
local err_exp = 'Variable type mismatch'
1508+
test:is_deeply({ok, utils.strip_error(err)}, {false, err_exp},
1509+
'variable usage inside InputMap')
1510+
1511+
local query_17 = [[
1512+
mutation($map_value: Int) {
1513+
order_metainfo_collection(
1514+
update: {store: {parametrized_tags: $map_value}}
1515+
limit: 1
1516+
) {
1517+
order_metainfo_id
1518+
}
1519+
}
1520+
]]
1521+
local ok, err = pcall(gql_wrapper.compile, gql_wrapper, query_17)
1522+
local err_exp = 'Variable type mismatch'
1523+
test:is_deeply({ok, utils.strip_error(err)}, {false, err_exp},
1524+
'variable usage as InputMap')
1525+
1526+
local query_18 = [[
1527+
mutation($id_value: Float!) {
1528+
order_metainfo_collection(
1529+
update: {store: {external_id: {int: $id_value}}}
1530+
limit: 1
1531+
) {
1532+
order_metainfo_id
1533+
}
1534+
}
1535+
]]
1536+
local ok, err = pcall(gql_wrapper.compile, gql_wrapper, query_18)
1537+
local err_exp = 'Variable type mismatch'
1538+
test:is_deeply({ok, utils.strip_error(err)}, {false, err_exp},
1539+
'variable usage inside InputUnion')
1540+
1541+
local query_19 = [[
1542+
mutation($id_box_value: Int) {
1543+
order_metainfo_collection(
1544+
update: {store: {external_id: $id_box_value}}
1545+
limit: 1
1546+
) {
1547+
order_metainfo_id
1548+
}
1549+
}
1550+
]]
1551+
local ok, err = pcall(gql_wrapper.compile, gql_wrapper, query_19)
1552+
local err_exp = 'Variable type mismatch'
1553+
test:is_deeply({ok, utils.strip_error(err)}, {false, err_exp},
1554+
'variable usage as InputUnion')
1555+
1556+
local box_t = 'arguments___order_metainfo_collection___update___' ..
1557+
'order_metainfo_collection_update___store___store___external_id___' ..
1558+
'external_id___Int_box'
1559+
local query_20 = [[
1560+
mutation($id_box_value: ]] .. box_t .. [[) {
1561+
order_metainfo_collection(
1562+
update: {store: {external_id: $id_box_value}}
1563+
limit: 1
1564+
) {
1565+
order_metainfo_id
1566+
}
1567+
}
1568+
]]
1569+
local ok, err = pcall(gql_wrapper.compile, gql_wrapper, query_20)
1570+
test:is(ok, true, 'correct variable usage as InputUnion')
1571+
1572+
-- }}}
1573+
14751574
assert(test:check(), 'check plan')
14761575
end
14771576

0 commit comments

Comments
 (0)