|
| 1 | +local utils = require('graphql.utils') |
| 2 | +local core_util = require('graphql.core.util') |
| 3 | + |
| 4 | +-- short name |
| 5 | +local check = utils.check |
| 6 | + |
| 7 | +local execute = {} |
| 8 | + |
| 9 | +--- Get graphql-lua object representing operation. |
| 10 | +--- |
| 11 | +--- @tparam table query_ast |
| 12 | +--- @tparam string operation_name |
| 13 | +--- |
| 14 | +--- @treturn table `operation` |
| 15 | +local function get_operation(query_ast, operation_name) |
| 16 | + -- XXX: make operation name optional in case of an one operation in the |
| 17 | + -- schema, see query_util.buildContext |
| 18 | + check(operation_name, 'operation_name', 'string') |
| 19 | + |
| 20 | + local operation |
| 21 | + |
| 22 | + for _, definition in ipairs(query_ast.definitions) do |
| 23 | + if definition.kind == 'operation' then |
| 24 | + if definition.name.value == operation_name then |
| 25 | + operation = definition |
| 26 | + end |
| 27 | + end |
| 28 | + end |
| 29 | + |
| 30 | + assert(operation ~= nil, |
| 31 | + ('cannot find operation "%s"'):format(operation_name)) |
| 32 | + return operation |
| 33 | +end |
| 34 | + |
| 35 | +--- XXX |
| 36 | +local function get_argument_type(field_type, argument_name) |
| 37 | + for name, argument_type in pairs(field_type.arguments) do |
| 38 | + if name == argument_name then |
| 39 | + return argument_type |
| 40 | + end |
| 41 | + end |
| 42 | + error(('cannot find argument "%s"'):format(argument_name)) |
| 43 | +end |
| 44 | + |
| 45 | +local function get_argument_values(field_type, selection, variables) |
| 46 | + local args = {} |
| 47 | + for _, argument in ipairs(selection.arguments or {}) do |
| 48 | + local argument_name = argument.name.value |
| 49 | + assert(argument_name ~= nil) |
| 50 | + local argument_type = get_argument_type(field_type, argument_name) |
| 51 | + local value = core_util.coerceValue(argument.value, argument_type, |
| 52 | + variables) |
| 53 | + args[argument_name] = value |
| 54 | + end |
| 55 | + return args |
| 56 | +end |
| 57 | + |
| 58 | +local function filter_object(object, object_type, selections, qcontext, |
| 59 | + variables) |
| 60 | + local required_fields = {} |
| 61 | + local selections_per_fields = {} |
| 62 | + |
| 63 | + -- XXX: allow null for object if object_type is nullable (we should not |
| 64 | + -- skip NonNull below for an element) |
| 65 | + |
| 66 | + -- XXX: support aliases, fragments, directives |
| 67 | + for _, selection in ipairs(selections) do |
| 68 | + assert(selection.alias == nil, 'NIY: aliases') |
| 69 | + local field_name = selection.name.value |
| 70 | + required_fields[field_name] = true |
| 71 | + selections_per_fields[field_name] = selection |
| 72 | + end |
| 73 | + |
| 74 | + local filtered_object = {} |
| 75 | + local fields_info = {} |
| 76 | + |
| 77 | + for field_name, _ in pairs(required_fields) do |
| 78 | + local field_type = object_type.fields[field_name] |
| 79 | + assert(field_type ~= nil) |
| 80 | + |
| 81 | + if field_type.prepare_resolve then |
| 82 | + -- XXX: attend NonNull somehow |
| 83 | + -- set deduce inner_type and set is_list |
| 84 | + local is_list = false |
| 85 | + local inner_type = field_type.kind |
| 86 | + while inner_type.ofType ~= nil do |
| 87 | + if inner_type.__type == 'List' then |
| 88 | + is_list = true |
| 89 | + inner_type = inner_type.ofType |
| 90 | + elseif inner_type.__type == 'NonNull' then |
| 91 | + inner_type = inner_type.ofType |
| 92 | + else |
| 93 | + error('unknown __type: ' .. tostring(inner_type.__type)) |
| 94 | + end |
| 95 | + end |
| 96 | + |
| 97 | + local selection = selections_per_fields[field_name] |
| 98 | + local args = get_argument_values(field_type, selection, variables) |
| 99 | + |
| 100 | + local info = {qcontext = qcontext} |
| 101 | + local prepared_resolve = field_type.prepare_resolve( |
| 102 | + object, args, info) |
| 103 | + fields_info[field_name] = { |
| 104 | + is_list = is_list, |
| 105 | + kind = inner_type, |
| 106 | + prepared_resolve = prepared_resolve, |
| 107 | + selections = selection.selectionSet.selections, |
| 108 | + } |
| 109 | + else |
| 110 | + -- XXX: is call to coerceValue needed here? |
| 111 | + if object[field_name] ~= nil then |
| 112 | + filtered_object[field_name] = object[field_name] |
| 113 | + end |
| 114 | + end |
| 115 | + end |
| 116 | + |
| 117 | + local prepared_object = { |
| 118 | + filtered_object = filtered_object, |
| 119 | + fields_info = fields_info, |
| 120 | + } |
| 121 | + return prepared_object |
| 122 | +end |
| 123 | + |
| 124 | +local function filter_object_list(object_list, object_type, selections, |
| 125 | + qcontext, variables) |
| 126 | + local prepared_object_list = {} |
| 127 | + |
| 128 | + for _, object in ipairs(object_list) do |
| 129 | + local prepared_object = filter_object(object, object_type, selections, |
| 130 | + qcontext, variables) |
| 131 | + table.insert(prepared_object_list, prepared_object) |
| 132 | + end |
| 133 | + |
| 134 | + return prepared_object_list |
| 135 | +end |
| 136 | + |
| 137 | +local function invoke_resolve(prepared_object, qcontext, variables) |
| 138 | + local open_set = {} |
| 139 | + |
| 140 | + for field_name, field_info in pairs(prepared_object.fields_info) do |
| 141 | + local object_or_list = field_info.prepared_resolve:invoke() |
| 142 | + local object_type = field_info.kind |
| 143 | + local selections = field_info.selections |
| 144 | + |
| 145 | + -- XXX: maybe we can always process lists and deduce is_list from |
| 146 | + -- graphql types |
| 147 | + |
| 148 | + local child |
| 149 | + if field_info.is_list then |
| 150 | + local child_prepared_object_list = filter_object_list( |
| 151 | + object_or_list, object_type, selections, qcontext, |
| 152 | + variables) |
| 153 | + |
| 154 | + -- construction |
| 155 | + prepared_object.filtered_object[field_name] = {} |
| 156 | + for _, child_prepared_object in |
| 157 | + ipairs(child_prepared_object_list) do |
| 158 | + table.insert(prepared_object.filtered_object[field_name], |
| 159 | + child_prepared_object.filtered_object) |
| 160 | + end |
| 161 | + |
| 162 | + child = { |
| 163 | + prepared_object_list = child_prepared_object_list, |
| 164 | + } |
| 165 | + else |
| 166 | + local child_prepared_object = filter_object(object_or_list, |
| 167 | + object_type, selections, qcontext, variables) |
| 168 | + |
| 169 | + -- construction |
| 170 | + prepared_object.filtered_object[field_name] = |
| 171 | + child_prepared_object.filtered_object |
| 172 | + |
| 173 | + child = { |
| 174 | + prepared_object = child_prepared_object, |
| 175 | + } |
| 176 | + end |
| 177 | + |
| 178 | + table.insert(open_set, child) |
| 179 | + end |
| 180 | + |
| 181 | + return open_set |
| 182 | +end |
| 183 | + |
| 184 | +local function invoke_resolve_list(prepared_object_list, qcontext, variables) |
| 185 | + local open_set = {} |
| 186 | + |
| 187 | + for _, prepared_object in ipairs(prepared_object_list) do |
| 188 | + local child_open_set = invoke_resolve(prepared_object, qcontext, |
| 189 | + variables) |
| 190 | + utils.expand_list(open_set, child_open_set) |
| 191 | + end |
| 192 | + |
| 193 | + return open_set |
| 194 | +end |
| 195 | + |
| 196 | +--- XXX |
| 197 | +function execute.execute(schema, query_ast, variables, operation_name) |
| 198 | + local operation = get_operation(query_ast, operation_name) |
| 199 | + local root_object_type = schema[operation.operation] |
| 200 | + assert(root_object_type ~= nil, |
| 201 | + ('cannot find root type for operation "%s"'):format(operation_name)) |
| 202 | + local root_selections = operation.selectionSet.selections |
| 203 | + |
| 204 | + local qcontext = {} |
| 205 | + local root_object = {} |
| 206 | + |
| 207 | + local prepared_root_object = filter_object( |
| 208 | + root_object, root_object_type, root_selections, qcontext, variables) |
| 209 | + local filtered_root_object = prepared_root_object.filtered_object |
| 210 | + local open_set = invoke_resolve(prepared_root_object, qcontext, variables) |
| 211 | + |
| 212 | + -- XXX: don't mix requests to different fields, make all requests for an |
| 213 | + -- one field, the for the next: add requests resort stage |
| 214 | + |
| 215 | + while true do |
| 216 | + local item = table.remove(open_set, 1) |
| 217 | + if item == nil then break end |
| 218 | + local child_open_set |
| 219 | + if item.prepared_object ~= nil then |
| 220 | + child_open_set = invoke_resolve(item.prepared_object, qcontext, |
| 221 | + variables) |
| 222 | + elseif item.prepared_object_list ~= nil then |
| 223 | + child_open_set = invoke_resolve_list(item.prepared_object_list, |
| 224 | + qcontext, variables) |
| 225 | + else |
| 226 | + assert(false, |
| 227 | + 'cannot find prepared_object nor prepared_object_list') |
| 228 | + end |
| 229 | + |
| 230 | + utils.expand_list(open_set, child_open_set) |
| 231 | + end |
| 232 | + |
| 233 | + return filtered_root_object |
| 234 | +end |
| 235 | + |
| 236 | +return execute |
0 commit comments