123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- local types = require 'types'
- local util = require 'util'
- local function typeFromAST(node, schema)
- local innerType
- if node.kind == 'listType' then
- innerType = typeFromAST(node.type)
- return innerType and types.list(innerType)
- elseif node.kind == 'nonNullType' then
- innerType = typeFromAST(node.type)
- return innerType and types.nonNull(innerType)
- else
- assert(node.kind == 'namedType', 'Variable must be a named type')
- return schema:getType(node.name.value)
- end
- end
- local function getFieldResponseKey(field)
- return field.alias and field.alias.name.value or field.name.value
- end
- local function shouldIncludeNode(selection, context)
- if selection.directives then
- local function isDirectiveActive(key, _type)
- local directive = util.find(selection.directives, function(directive)
- return directive.name.value == key
- end)
- if not directive then return end
- local ifArgument = util.find(directive.arguments, function(argument)
- return argument.name.value == 'if'
- end)
- if not ifArgument then return end
- return util.coerceValue(ifArgument.value, _type.arguments['if'])
- end
- if isDirectiveActive('skip', types.skip) then return false end
- if isDirectiveActive('include', types.include) == false then return false end
- end
- return true
- end
- local function doesFragmentApply(fragment, type, context)
- if not fragment.typeCondition then return true end
- local innerType = typeFromAST(fragment.typeCondition, context.schema)
- if innerType == type then
- return true
- elseif innerType.__type == 'Interface' then
- return schema:getImplementors(type)[innerType]
- elseif innerType.__type == 'Union' then
- return util.find(type.types, function(member)
- return member == innerType
- end)
- end
- end
- local function mergeSelectionSets(fields)
- local selectionSet = {}
- for i = 1, #fields do
- local selectionSet = fields[i].selectionSet
- if selectionSet then
- for j = 1, #selectionSet.selections do
- table.insert(selectionSet, selectionSet.selections[j])
- end
- end
- end
- return selectionSet
- end
- local function defaultResolver(object, fields, info)
- return object[fields[1].name.value]
- end
- local function buildContext(schema, tree, variables, operationName)
- local context = {
- schema = schema,
- variables = variables,
- operation = nil,
- fragmentMap = {}
- }
- for _, definition in ipairs(tree.definitions) do
- if definition.kind == 'operation' then
- if not operationName and context.operation then
- error('Operation name must be specified if more than one operation exists.')
- end
- if not operationName or definition.name.value == operationName then
- context.operation = definition
- end
- elseif definition.kind == 'fragmentDefinition' then
- context.fragmentMap[definition.name.value] = definition
- end
- end
- if not context.operation then
- if operationName then
- error('Unknown operation "' .. operationName .. '"')
- else
- error('Must provide an operation')
- end
- end
- return context
- end
- local function collectFields(objectType, selectionSet, visitedFragments, result, context)
- for _, selection in ipairs(selectionSet.selections) do
- if selection.kind == 'field' then
- if shouldIncludeNode(selection) then
- local name = getFieldResponseKey(selection)
- result[name] = result[name] or {}
- table.insert(result[name], selection)
- end
- elseif selection.kind == 'inlineFragment' then
- if shouldIncludeNode(selection) and doesFragmentApply(selection, objectType, context) then
- collectFields(objectType, selection.selectionSet, visitedFragments, result, context)
- end
- elseif selection.kind == 'fragmentSpread' then
- local fragmentName = selection.name.value
- if shouldIncludeNode(selection) and not visitedFragments[fragmentName] then
- visitedFragments[fragmentName] = true
- local fragment = context.fragmentMap[fragmentName]
- if fragment and shouldIncludeNode(fragment) and doesFragmentApply(fragment, objectType, context) then
- collectFields(objectType, fragment.selectionSet, visitedFragments, result, context)
- end
- end
- end
- end
- return result
- end
- local function completeValue(fieldType, result, subSelectionSet)
- return result -- TODO
- end
- local function getFieldEntry(objectType, object, fields)
- local firstField = fields[1]
- local responseKey = getFieldResponseKey(firstField)
- local fieldType = objectType.fields[firstField.name.value]
- if fieldType == nil then
- return nil
- end
- -- TODO correct arguments to resolve
- local resolvedObject = (fieldType.resolve or defaultResolver)(object, fields, {})
- if not resolvedObject then
- return nil -- TODO null
- end
- local subSelectionSet = mergeSelectionSets(fields)
- local responseValue = completeValue(fieldType, resolvedObject, subSelectionSet)
- return responseValue
- end
- local function evaluateSelectionSet(objectType, object, selectionSet, context)
- local groupedFieldSet = collectFields(objectType, selectionSet, {}, {}, context)
- return util.map(groupedFieldSet, function(fields)
- return getFieldEntry(objectType, object, fields)
- end)
- end
- return function(schema, tree, variables, operationName, rootValue)
- local context = buildContext(schema, tree, variables, operationName)
- local rootType = schema[context.operation.operation]
- if not rootType then
- error('Unsupported operation "' .. context.operation.operation .. '"')
- end
- return evaluateSelectionSet(rootType, rootValue, context.operation.selectionSet, context)
- end
|