validate.lua 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. local rules = require 'rules'
  2. local visitors = {
  3. document = {
  4. enter = function(node, context)
  5. for _, definition in ipairs(node.definitions) do
  6. if definition.kind == 'fragmentDefinition' then
  7. context.fragmentMap[definition.name.value] = definition
  8. end
  9. end
  10. end,
  11. children = function(node, context)
  12. return node.definitions
  13. end,
  14. rules = { rules.uniqueFragmentNames, exit = { rules.noUnusedFragments } }
  15. },
  16. operation = {
  17. enter = function(node, context)
  18. table.insert(context.objects, context.schema.query)
  19. context.variableReferences = {}
  20. end,
  21. exit = function(node, context)
  22. table.remove(context.objects)
  23. context.variableReferences = nil
  24. end,
  25. children = function(node)
  26. return { node.selectionSet }
  27. end,
  28. rules = {
  29. rules.uniqueOperationNames,
  30. rules.loneAnonymousOperation,
  31. rules.directivesAreDefined,
  32. rules.variablesHaveCorrectType,
  33. rules.variableDefaultValuesHaveCorrectType,
  34. exit = {
  35. rules.variablesAreUsed
  36. }
  37. }
  38. },
  39. selectionSet = {
  40. children = function(node)
  41. return node.selections
  42. end,
  43. rules = { rules.unambiguousSelections }
  44. },
  45. field = {
  46. enter = function(node, context)
  47. local parentField = context.objects[#context.objects].fields[node.name.value]
  48. -- false is a special value indicating that the field was not present in the type definition.
  49. local field = parentField and parentField.kind or false
  50. table.insert(context.objects, field)
  51. end,
  52. exit = function(node, context)
  53. table.remove(context.objects)
  54. end,
  55. children = function(node)
  56. local children = {}
  57. if node.arguments then
  58. for i = 1, #node.arguments do
  59. table.insert(children, node.arguments[i])
  60. end
  61. end
  62. if node.selectionSet then
  63. table.insert(children, node.selectionSet)
  64. end
  65. return children
  66. end,
  67. rules = {
  68. rules.fieldsDefinedOnType,
  69. rules.argumentsDefinedOnType,
  70. rules.scalarFieldsAreLeaves,
  71. rules.compositeFieldsAreNotLeaves,
  72. rules.uniqueArgumentNames,
  73. rules.argumentsOfCorrectType,
  74. rules.requiredArgumentsPresent,
  75. rules.directivesAreDefined
  76. }
  77. },
  78. inlineFragment = {
  79. enter = function(node, context)
  80. local kind = false
  81. if node.typeCondition then
  82. kind = context.schema:getType(node.typeCondition.name.value) or false
  83. end
  84. table.insert(context.objects, kind)
  85. end,
  86. exit = function(node, context)
  87. table.remove(context.objects)
  88. end,
  89. children = function(node, context)
  90. if node.selectionSet then
  91. return {node.selectionSet}
  92. end
  93. end,
  94. rules = {
  95. rules.fragmentHasValidType,
  96. rules.fragmentSpreadIsPossible,
  97. rules.directivesAreDefined
  98. }
  99. },
  100. fragmentSpread = {
  101. enter = function(node, context)
  102. context.usedFragments[node.name.value] = true
  103. local fragment = context.fragmentMap[node.name.value]
  104. local fragmentType = context.schema:getType(fragment.typeCondition.name.value) or false
  105. table.insert(context.objects, fragmentType)
  106. if context.variableReferences then
  107. local function collectTransitiveVariables(node)
  108. if not node then return end
  109. if node.kind == 'selectionSet' then
  110. for _, selection in ipairs(node.selections) do
  111. collectTransitiveVariables(selection)
  112. end
  113. elseif node.kind == 'field' and node.arguments then
  114. for _, argument in ipairs(node.arguments) do
  115. collectTransitiveVariables(argument)
  116. end
  117. elseif node.kind == 'argument' then
  118. return collectTransitiveVariables(node.value)
  119. elseif node.kind == 'listType' or node.kind == 'nonNullType' then
  120. return collectTransitiveVariables(node.type)
  121. elseif node.kind == 'variable' then
  122. context.variableReferences[node.name.value] = node.name.value
  123. elseif node.kind == 'inlineFragment' then
  124. return collectTransitiveVariables(node.selectionSet)
  125. elseif node.kind == 'fragmentSpread' then
  126. local fragment = context.fragmentMap[node.name.value]
  127. return fragment and collectTransitiveVariables(fragment.selectionSet)
  128. end
  129. end
  130. collectTransitiveVariables(fragment.selectionSet)
  131. end
  132. end,
  133. rules = {
  134. rules.fragmentSpreadTargetDefined,
  135. rules.fragmentSpreadIsPossible,
  136. rules.directivesAreDefined
  137. }
  138. },
  139. fragmentDefinition = {
  140. enter = function(node, context)
  141. kind = context.schema:getType(node.typeCondition.name.value) or false
  142. table.insert(context.objects, kind)
  143. end,
  144. exit = function(node, context)
  145. table.remove(context.objects)
  146. end,
  147. children = function(node)
  148. return { node.selectionSet }
  149. end,
  150. rules = {
  151. rules.fragmentHasValidType,
  152. rules.fragmentDefinitionHasNoCycles,
  153. rules.directivesAreDefined
  154. }
  155. },
  156. argument = {
  157. enter = function(node, context)
  158. if context.variableReferences then
  159. local value = node.value
  160. while value.kind == 'listType' or value.kind == 'nonNullType' do
  161. value = value.type
  162. end
  163. if value.kind == 'variable' then
  164. context.variablesUsed[value.name.value] = true
  165. end
  166. end
  167. end,
  168. rules = { rules.uniqueInputObjectFields }
  169. }
  170. }
  171. return function(schema, tree)
  172. local context = {
  173. schema = schema,
  174. fragmentMap = {},
  175. operationNames = {},
  176. hasAnonymousOperation = false,
  177. usedFragments = {},
  178. objects = {},
  179. variableReferences = nil
  180. }
  181. local function visit(node)
  182. local visitor = node.kind and visitors[node.kind]
  183. if not visitor then return end
  184. if visitor.enter then
  185. visitor.enter(node, context)
  186. end
  187. if visitor.rules then
  188. for i = 1, #visitor.rules do
  189. visitor.rules[i](node, context)
  190. end
  191. end
  192. if visitor.children then
  193. local children = visitor.children(node)
  194. if children then
  195. for _, child in ipairs(children) do
  196. visit(child)
  197. end
  198. end
  199. end
  200. if visitor.rules and visitor.rules.exit then
  201. for i = 1, #visitor.rules.exit do
  202. visitor.rules.exit[i](node, context)
  203. end
  204. end
  205. if visitor.exit then
  206. visitor.exit(node, context)
  207. end
  208. end
  209. return visit(tree)
  210. end