execute.lua 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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 getFieldResponseKey(field)
  17. return field.alias and field.alias.name.value or field.name.value
  18. end
  19. local function shouldIncludeNode(selection, context)
  20. if selection.directives then
  21. local function isDirectiveActive(key, _type)
  22. local directive = util.find(selection.directives, function(directive)
  23. return directive.name.value == key
  24. end)
  25. if not directive then return end
  26. local ifArgument = util.find(directive.arguments, function(argument)
  27. return argument.name.value == 'if'
  28. end)
  29. if not ifArgument then return end
  30. return util.coerceValue(ifArgument.value, _type.arguments['if'])
  31. end
  32. if isDirectiveActive('skip', types.skip) then return false end
  33. if isDirectiveActive('include', types.include) == false then return false end
  34. end
  35. return true
  36. end
  37. local function doesFragmentApply(fragment, type, context)
  38. if not fragment.typeCondition then return true end
  39. local innerType = typeFromAST(fragment.typeCondition, context.schema)
  40. if innerType == type then
  41. return true
  42. elseif innerType.__type == 'Interface' then
  43. return schema:getImplementors(type)[innerType]
  44. elseif innerType.__type == 'Union' then
  45. return util.find(type.types, function(member)
  46. return member == innerType
  47. end)
  48. end
  49. end
  50. local function mergeSelectionSets(fields)
  51. local selectionSet = {}
  52. for i = 1, #fields do
  53. local selectionSet = fields[i].selectionSet
  54. if selectionSet then
  55. for j = 1, #selectionSet.selections do
  56. table.insert(selectionSet, selectionSet.selections[j])
  57. end
  58. end
  59. end
  60. return selectionSet
  61. end
  62. local function defaultResolver(object, fields, info)
  63. return object[fields[1].name.value]
  64. end
  65. local function buildContext(schema, tree, variables, operationName)
  66. local context = {
  67. schema = schema,
  68. variables = variables,
  69. operation = nil,
  70. fragmentMap = {}
  71. }
  72. for _, definition in ipairs(tree.definitions) do
  73. if definition.kind == 'operation' then
  74. if not operationName and context.operation then
  75. error('Operation name must be specified if more than one operation exists.')
  76. end
  77. if not operationName or definition.name.value == operationName then
  78. context.operation = definition
  79. end
  80. elseif definition.kind == 'fragmentDefinition' then
  81. context.fragmentMap[definition.name.value] = definition
  82. end
  83. end
  84. if not context.operation then
  85. if operationName then
  86. error('Unknown operation "' .. operationName .. '"')
  87. else
  88. error('Must provide an operation')
  89. end
  90. end
  91. return context
  92. end
  93. local function collectFields(objectType, selectionSet, visitedFragments, result, context)
  94. for _, selection in ipairs(selectionSet.selections) do
  95. if selection.kind == 'field' then
  96. if shouldIncludeNode(selection) then
  97. local name = getFieldResponseKey(selection)
  98. result[name] = result[name] or {}
  99. table.insert(result[name], selection)
  100. end
  101. elseif selection.kind == 'inlineFragment' then
  102. if shouldIncludeNode(selection) and doesFragmentApply(selection, objectType, context) then
  103. collectFields(objectType, selection.selectionSet, visitedFragments, result, context)
  104. end
  105. elseif selection.kind == 'fragmentSpread' then
  106. local fragmentName = selection.name.value
  107. if shouldIncludeNode(selection) and not visitedFragments[fragmentName] then
  108. visitedFragments[fragmentName] = true
  109. local fragment = context.fragmentMap[fragmentName]
  110. if fragment and shouldIncludeNode(fragment) and doesFragmentApply(fragment, objectType, context) then
  111. collectFields(objectType, fragment.selectionSet, visitedFragments, result, context)
  112. end
  113. end
  114. end
  115. end
  116. return result
  117. end
  118. local function completeValue(fieldType, result, subSelectionSet)
  119. return result -- TODO
  120. end
  121. local function getFieldEntry(objectType, object, fields)
  122. local firstField = fields[1]
  123. local responseKey = getFieldResponseKey(firstField)
  124. local fieldType = objectType.fields[firstField.name.value]
  125. if fieldType == nil then
  126. return nil
  127. end
  128. -- TODO correct arguments to resolve
  129. local resolvedObject = (fieldType.resolve or defaultResolver)(object, fields, {})
  130. if not resolvedObject then
  131. return nil -- TODO null
  132. end
  133. local subSelectionSet = mergeSelectionSets(fields)
  134. local responseValue = completeValue(fieldType, resolvedObject, subSelectionSet)
  135. return responseValue
  136. end
  137. local function evaluateSelectionSet(objectType, object, selectionSet, context)
  138. local groupedFieldSet = collectFields(objectType, selectionSet, {}, {}, context)
  139. return util.map(groupedFieldSet, function(fields)
  140. return getFieldEntry(objectType, object, fields)
  141. end)
  142. end
  143. return function(schema, tree, variables, operationName, rootValue)
  144. local context = buildContext(schema, tree, variables, operationName)
  145. local rootType = schema[context.operation.operation]
  146. if not rootType then
  147. error('Unsupported operation "' .. context.operation.operation .. '"')
  148. end
  149. return evaluateSelectionSet(rootType, rootValue, context.operation.selectionSet, context)
  150. end