execute.lua 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. local types = require 'types'
  2. local util = require 'util'
  3. local function typeFromAST(node, schema)
  4. local innerType
  5. if node.kind == 'listType' then
  6. innerType = typeFromAST(node.type)
  7. return innerType and types.list(innerType)
  8. elseif node.kind == 'nonNullType' then
  9. innerType = typeFromAST(node.type)
  10. return innerType and types.nonNull(innerType)
  11. else
  12. assert(node.kind == 'namedType', 'Variable must be a named type')
  13. return schema:getType(node.name.value)
  14. end
  15. end
  16. local function defaultResolver(source, arguments, info)
  17. local property = source[info.fieldName]
  18. return type(property) == 'function' and property(source) or property
  19. end
  20. local function getFieldEntryKey(selection)
  21. return selection.alias and selection.alias.name.value or selection.name.value
  22. end
  23. local function shouldIncludeNode(selection, context)
  24. if selection.directives then
  25. local function isDirectiveActive(key, _type)
  26. local directive = util.find(selection.directives, function(directive)
  27. return directive.name.value == key
  28. end)
  29. if not directive then return end
  30. local ifArgument = util.find(directive.arguments, function(argument)
  31. return argument.name.value == 'if'
  32. end)
  33. if not ifArgument then return end
  34. return util.coerceValue(ifArgument.value, _type.arguments['if'])
  35. end
  36. if isDirectiveActive('skip', types.skip) then return false end
  37. if isDirectiveActive('include', types.include) == false then return false end
  38. end
  39. return true
  40. end
  41. local function doesFragmentApply(fragment, type, context)
  42. if not fragment.typeCondition then return true end
  43. local innerType = typeFromAST(fragment.typeCondition, context.schema)
  44. if innerType == type then
  45. return true
  46. elseif innerType.__type == 'Interface' then
  47. return schema:getImplementors(type)[innerType]
  48. elseif innerType.__type == 'Union' then
  49. return util.find(type.types, function(member)
  50. return member == innerType
  51. end)
  52. end
  53. end
  54. local function collectFields(selectionSet, type, fields, visitedFragments, context)
  55. for _, selection in ipairs(selectionSet.selections) do
  56. if selection.kind == 'field' then
  57. if shouldIncludeNode(selection) then
  58. local name = getFieldEntryKey(selection)
  59. fields[name] = fields[name] or {}
  60. table.insert(fields[name], selection)
  61. end
  62. elseif selection.kind == 'inlineFragment' then
  63. if shouldIncludeNode(selection) and doesFragmentApply(selection, type, context) then
  64. collectFields(selection.selectionSet, type, fields, visitedFragments, context)
  65. end
  66. elseif selection.kind == 'fragmentSpread' then
  67. local fragmentName = selection.name.value
  68. if shouldIncludeNode(selection) and not visitedFragments[fragmentName] then
  69. visitedFragments[fragmentName] = true
  70. local fragment = context.fragmentMap[fragmentName]
  71. if fragment and shouldIncludeNode(fragment) and doesFragmentApply(fragment, type, context) then
  72. collectFields(fragment.selectionSet, type, fields, visitedFragments, context)
  73. end
  74. end
  75. end
  76. end
  77. return fields
  78. end
  79. local function buildContext(schema, tree, variables, operationName)
  80. local context = {
  81. schema = schema,
  82. variables = variables,
  83. operation = nil,
  84. fragmentMap = {}
  85. }
  86. for _, definition in ipairs(tree.definitions) do
  87. if definition.kind == 'operation' then
  88. if not operationName and context.operation then
  89. error('Operation name must be specified if more than one operation exists.')
  90. end
  91. if not operationName or definition.name.value == operationName then
  92. context.operation = definition
  93. end
  94. elseif definition.kind == 'fragmentDefinition' then
  95. context.fragmentMap[definition.name.value] = definition
  96. end
  97. end
  98. if not context.operation then
  99. if operationName then
  100. error('Unknown operation "' .. operationName .. '"')
  101. else
  102. error('Must provide an operation')
  103. end
  104. end
  105. return context
  106. end
  107. local function executeFields(parentType, rootValue, fieldGroups, context)
  108. local result = {}
  109. for name, fieldGroup in pairs(fieldGroups) do
  110. result[name] = resolveField(parentType, rootValue, fieldGroup, context)
  111. end
  112. return result
  113. end
  114. local function resolveField(parentType, rootValue, fields, context)
  115. local field = fields[1]
  116. local fieldName = field.name.value
  117. local fieldType = parentType.fields[fieldName]
  118. local returnType = fieldType.kind
  119. local info = {
  120. fieldName = fieldName,
  121. fields = fields,
  122. returnType = returnType,
  123. parentType = parentType,
  124. schema = context.schema,
  125. fragments = context.fragmentMap,
  126. rootValue = rootValue,
  127. operation = context.operation,
  128. variables = context.variables
  129. }
  130. local resolve = fieldType.resolve or defaultResolver
  131. local result = resolve(source, {}, info)
  132. end
  133. return function(schema, tree, rootValue, variables, operationName)
  134. local context = buildContext(schema, tree, variables, operationName)
  135. local rootType = schema[context.operation.operation]
  136. if not rootType then
  137. error('Unsupported operation "' .. context.operation.operation .. '"')
  138. end
  139. local fieldGroups = collectFields(context.operation.selectionSet, rootType, {}, {}, context)
  140. return executeFields(rootType, rootValue, fieldGroups, context)
  141. end