2
0

validate.lua 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. local path = (...):gsub('%.[^%.]+$', '')
  2. local rules = require(path .. '.rules')
  3. local util = require(path .. '.util')
  4. local introspection = require(path .. '.introspection')
  5. local schema = require(path .. '.schema')
  6. local function getParentField(context, name, count)
  7. if introspection.fieldMap[name] then return introspection.fieldMap[name] end
  8. count = count or 1
  9. local parent = context.objects[#context.objects - count]
  10. -- Unwrap lists and non-null types
  11. while parent.ofType do
  12. parent = parent.ofType
  13. end
  14. return parent.fields[name]
  15. end
  16. local visitors = {
  17. document = {
  18. enter = function(node, context)
  19. for _, definition in ipairs(node.definitions) do
  20. if definition.kind == 'fragmentDefinition' then
  21. context.fragmentMap[definition.name.value] = definition
  22. end
  23. end
  24. end,
  25. children = function(node, context)
  26. return node.definitions
  27. end,
  28. rules = { rules.uniqueFragmentNames, exit = { rules.noUnusedFragments } }
  29. },
  30. operation = {
  31. enter = function(node, context)
  32. table.insert(context.objects, context.schema[node.operation])
  33. context.currentOperation = node
  34. context.variableReferences = {}
  35. end,
  36. exit = function(node, context)
  37. table.remove(context.objects)
  38. context.currentOperation = nil
  39. context.variableReferences = nil
  40. end,
  41. children = function(node)
  42. return { node.selectionSet }
  43. end,
  44. rules = {
  45. rules.uniqueOperationNames,
  46. rules.loneAnonymousOperation,
  47. rules.directivesAreDefined,
  48. rules.variablesHaveCorrectType,
  49. rules.variableDefaultValuesHaveCorrectType,
  50. exit = {
  51. rules.variablesAreUsed,
  52. rules.variablesAreDefined
  53. }
  54. }
  55. },
  56. selectionSet = {
  57. children = function(node)
  58. return node.selections
  59. end,
  60. rules = { rules.unambiguousSelections }
  61. },
  62. field = {
  63. enter = function(node, context)
  64. local name = node.name.value
  65. if introspection.fieldMap[name] then
  66. table.insert(context.objects, introspection.fieldMap[name].kind)
  67. else
  68. local parentField = getParentField(context, name, 0)
  69. -- false is a special value indicating that the field was not present in the type definition.
  70. table.insert(context.objects, parentField and parentField.kind or false)
  71. end
  72. end,
  73. exit = function(node, context)
  74. table.remove(context.objects)
  75. end,
  76. children = function(node)
  77. local children = {}
  78. if node.arguments then
  79. for i = 1, #node.arguments do
  80. table.insert(children, node.arguments[i])
  81. end
  82. end
  83. if node.directives then
  84. for i = 1, #node.directives do
  85. table.insert(children, node.directives[i])
  86. end
  87. end
  88. if node.selectionSet then
  89. table.insert(children, node.selectionSet)
  90. end
  91. return children
  92. end,
  93. rules = {
  94. rules.fieldsDefinedOnType,
  95. rules.argumentsDefinedOnType,
  96. rules.scalarFieldsAreLeaves,
  97. rules.compositeFieldsAreNotLeaves,
  98. rules.uniqueArgumentNames,
  99. rules.argumentsOfCorrectType,
  100. rules.requiredArgumentsPresent,
  101. rules.directivesAreDefined,
  102. rules.variableUsageAllowed
  103. }
  104. },
  105. inlineFragment = {
  106. enter = function(node, context)
  107. local kind = false
  108. if node.typeCondition then
  109. kind = context.schema:getType(node.typeCondition.name.value) or false
  110. end
  111. table.insert(context.objects, kind)
  112. end,
  113. exit = function(node, context)
  114. table.remove(context.objects)
  115. end,
  116. children = function(node, context)
  117. if node.selectionSet then
  118. return {node.selectionSet}
  119. end
  120. end,
  121. rules = {
  122. rules.fragmentHasValidType,
  123. rules.fragmentSpreadIsPossible,
  124. rules.directivesAreDefined
  125. }
  126. },
  127. fragmentSpread = {
  128. enter = function(node, context)
  129. context.usedFragments[node.name.value] = true
  130. local fragment = context.fragmentMap[node.name.value]
  131. if not fragment then return end
  132. local fragmentType = context.schema:getType(fragment.typeCondition.name.value) or false
  133. table.insert(context.objects, fragmentType)
  134. if context.currentOperation then
  135. local seen = {}
  136. local function collectTransitiveVariables(referencedNode)
  137. if not referencedNode then return end
  138. if referencedNode.kind == 'selectionSet' then
  139. for _, selection in ipairs(referencedNode.selections) do
  140. if not seen[selection] then
  141. seen[selection] = true
  142. collectTransitiveVariables(selection)
  143. end
  144. end
  145. elseif referencedNode.kind == 'field' then
  146. if referencedNode.arguments then
  147. for _, argument in ipairs(referencedNode.arguments) do
  148. collectTransitiveVariables(argument)
  149. end
  150. end
  151. if referencedNode.selectionSet then
  152. collectTransitiveVariables(referencedNode.selectionSet)
  153. end
  154. elseif referencedNode.kind == 'argument' then
  155. return collectTransitiveVariables(referencedNode.value)
  156. elseif referencedNode.kind == 'listType' or referencedNode.kind == 'nonNullType' then
  157. return collectTransitiveVariables(referencedNode.type)
  158. elseif referencedNode.kind == 'variable' then
  159. context.variableReferences[referencedNode.name.value] = true
  160. elseif referencedNode.kind == 'inlineFragment' then
  161. return collectTransitiveVariables(referencedNode.selectionSet)
  162. elseif referencedNode.kind == 'fragmentSpread' then
  163. local fragment = context.fragmentMap[referencedNode.name.value]
  164. context.usedFragments[referencedNode.name.value] = true
  165. return fragment and collectTransitiveVariables(fragment.selectionSet)
  166. end
  167. end
  168. collectTransitiveVariables(fragment.selectionSet)
  169. end
  170. end,
  171. exit = function(node, context)
  172. table.remove(context.objects)
  173. end,
  174. rules = {
  175. rules.fragmentSpreadTargetDefined,
  176. rules.fragmentSpreadIsPossible,
  177. rules.directivesAreDefined,
  178. rules.variableUsageAllowed
  179. }
  180. },
  181. fragmentDefinition = {
  182. enter = function(node, context)
  183. kind = context.schema:getType(node.typeCondition.name.value) or false
  184. table.insert(context.objects, kind)
  185. end,
  186. exit = function(node, context)
  187. table.remove(context.objects)
  188. end,
  189. children = function(node)
  190. local children = {}
  191. for _, selection in ipairs(node.selectionSet) do
  192. table.insert(children, selection)
  193. end
  194. return children
  195. end,
  196. rules = {
  197. rules.fragmentHasValidType,
  198. rules.fragmentDefinitionHasNoCycles,
  199. rules.directivesAreDefined
  200. }
  201. },
  202. argument = {
  203. enter = function(node, context)
  204. if context.currentOperation then
  205. local value = node.value
  206. while value.kind == 'listType' or value.kind == 'nonNullType' do
  207. value = value.type
  208. end
  209. if value.kind == 'variable' then
  210. context.variableReferences[value.name.value] = true
  211. end
  212. end
  213. end,
  214. rules = { rules.uniqueInputObjectFields }
  215. },
  216. directive = {
  217. children = function(node, context)
  218. return node.arguments
  219. end
  220. }
  221. }
  222. return function(schema, tree)
  223. local context = {
  224. schema = schema,
  225. fragmentMap = {},
  226. operationNames = {},
  227. hasAnonymousOperation = false,
  228. usedFragments = {},
  229. objects = {},
  230. currentOperation = nil,
  231. variableReferences = nil
  232. }
  233. local function visit(node)
  234. local visitor = node.kind and visitors[node.kind]
  235. if not visitor then return end
  236. if visitor.enter then
  237. visitor.enter(node, context)
  238. end
  239. if visitor.rules then
  240. for i = 1, #visitor.rules do
  241. visitor.rules[i](node, context)
  242. end
  243. end
  244. if visitor.children then
  245. local children = visitor.children(node)
  246. if children then
  247. for _, child in ipairs(children) do
  248. visit(child)
  249. end
  250. end
  251. end
  252. if visitor.rules and visitor.rules.exit then
  253. for i = 1, #visitor.rules.exit do
  254. visitor.rules.exit[i](node, context)
  255. end
  256. end
  257. if visitor.exit then
  258. visitor.exit(node, context)
  259. end
  260. end
  261. return visit(tree)
  262. end