lib/graphql/execution/execute.rb in graphql-1.5.15 vs lib/graphql/execution/execute.rb in graphql-1.6.0

- old
+ new

@@ -2,11 +2,14 @@ module GraphQL module Execution # A valid execution strategy # @api private class Execute - PROPAGATE_NULL = :__graphql_propagate_null__ + # @api private + SKIP = Object.new + # @api private + PROPAGATE_NULL = Object.new def execute(ast_operation, root_type, query) result = resolve_selection( query.root_value, root_type, @@ -18,202 +21,213 @@ GraphQL::Execution::Lazy.resolve(result) result.to_h end - private + # @api private + module ExecutionFunctions + module_function - def resolve_selection(object, current_type, selection, query_ctx, mutation: false ) - selection_result = SelectionResult.new + def resolve_selection(object, current_type, selection, query_ctx, mutation: false ) + selection_result = SelectionResult.new - selection.typed_children[current_type].each do |name, subselection| - field_result = resolve_field( - selection_result, - subselection, - current_type, - subselection.definition, - object, - query_ctx - ) + selection.typed_children[current_type].each do |name, subselection| + field_result = resolve_field( + selection_result, + subselection, + current_type, + subselection.definition, + object, + query_ctx + ) - if mutation - GraphQL::Execution::Lazy.resolve(field_result) - end + if field_result == SKIP + next + end - selection_result.set(name, field_result) + if mutation + GraphQL::Execution::Lazy.resolve(field_result) + end - # If the last subselection caused a null to propagate to _this_ selection, - # then we may as well quit executing fields because they - # won't be in the response - if selection_result.invalid_null? - break + selection_result.set(name, field_result) + + # If the last subselection caused a null to propagate to _this_ selection, + # then we may as well quit executing fields because they + # won't be in the response + if selection_result.invalid_null? + break + end end - end - selection_result - end - - def resolve_field(owner, selection, parent_type, field, object, query_ctx) - query = query_ctx.query - field_ctx = query_ctx.spawn( - parent_type: parent_type, - field: field, - key: selection.name, - selection: selection, - ) - - arguments = query.arguments_for(selection, field) - raw_value = begin - query_ctx.schema.middleware.invoke([parent_type, object, field, arguments, field_ctx]) - rescue GraphQL::ExecutionError => err - err + selection_result end - result = if query.schema.lazy?(raw_value) - field.prepare_lazy(raw_value, arguments, field_ctx).then { |inner_value| - continue_resolve_field(owner, selection, parent_type, field, inner_value, field_ctx) - } - elsif raw_value.is_a?(GraphQL::Execution::Lazy) - # It came from a connection resolve, assume it was already instrumented - raw_value.then { |inner_value| - continue_resolve_field(owner, selection, parent_type, field, inner_value, field_ctx) - } - else - continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx) - end - - case result - when PROPAGATE_NULL, GraphQL::Execution::Lazy, SelectionResult - FieldResult.new( - owner: owner, - type: field.type, - value: result, + def resolve_field(owner, selection, parent_type, field, object, query_ctx) + query = query_ctx.query + field_ctx = query_ctx.spawn( + parent_type: parent_type, + field: field, + key: selection.name, + selection: selection, ) - else - result - end - end - def continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx) - query = field_ctx.query - - case raw_value - when GraphQL::ExecutionError - raw_value.ast_node = field_ctx.ast_node - raw_value.path = field_ctx.path - query.context.errors.push(raw_value) - when Array - list_errors = raw_value.each_with_index.select { |value, _| value.is_a?(GraphQL::ExecutionError) } - if list_errors.any? - list_errors.each do |error, index| - error.ast_node = field_ctx.ast_node - error.path = field_ctx.path + [index] - query.context.errors.push(error) - end + arguments = query.arguments_for(selection, field) + raw_value = begin + query_ctx.schema.middleware.invoke([parent_type, object, field, arguments, field_ctx]) + rescue GraphQL::ExecutionError => err + err end - end - resolve_value( - owner, - parent_type, - field, - field.type, - raw_value, - selection, - field_ctx, - ) - end - - def resolve_value(owner, parent_type, field_defn, field_type, value, selection, field_ctx) - if value.nil? - if field_type.kind.non_null? - type_error = GraphQL::InvalidNullError.new(parent_type, field_defn, value) - field_ctx.schema.type_error(type_error, field_ctx) - PROPAGATE_NULL + result = if query.schema.lazy?(raw_value) + field.prepare_lazy(raw_value, arguments, field_ctx).then { |inner_value| + continue_resolve_field(owner, selection, parent_type, field, inner_value, field_ctx) + } + elsif raw_value.is_a?(GraphQL::Execution::Lazy) + # It came from a connection resolve, assume it was already instrumented + raw_value.then { |inner_value| + continue_resolve_field(owner, selection, parent_type, field, inner_value, field_ctx) + } else - nil + continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx) end - elsif value.is_a?(GraphQL::ExecutionError) - if field_type.kind.non_null? - PROPAGATE_NULL + + case result + when PROPAGATE_NULL, GraphQL::Execution::Lazy, SelectionResult + FieldResult.new( + owner: owner, + type: field.type, + value: result, + ) else - nil + result end - else - case field_type.kind - when GraphQL::TypeKinds::SCALAR - field_type.coerce_result(value, field_ctx) - when GraphQL::TypeKinds::ENUM - field_type.coerce_result(value, field_ctx) - when GraphQL::TypeKinds::LIST - inner_type = field_type.of_type - i = 0 - result = [] - value.each do |inner_value| - inner_ctx = field_ctx.spawn( - key: i, - selection: selection, - parent_type: parent_type, - field: field_defn, - ) + end - inner_result = resolve_value( - owner, - parent_type, - field_defn, - inner_type, - inner_value, - selection, - inner_ctx, - ) + def continue_resolve_field(owner, selection, parent_type, field, raw_value, field_ctx) + query = field_ctx.query - result << GraphQL::Execution::FieldResult.new(type: inner_type, owner: owner, value: inner_result) - i += 1 + case raw_value + when GraphQL::ExecutionError + raw_value.ast_node = field_ctx.ast_node + raw_value.path = field_ctx.path + query.context.errors.push(raw_value) + when Array + list_errors = raw_value.each_with_index.select { |value, _| value.is_a?(GraphQL::ExecutionError) } + if list_errors.any? + list_errors.each do |error, index| + error.ast_node = field_ctx.ast_node + error.path = field_ctx.path + [index] + query.context.errors.push(error) + end end - result - when GraphQL::TypeKinds::NON_NULL - wrapped_type = field_type.of_type - resolve_value( - owner, - parent_type, - field_defn, - wrapped_type, - value, - selection, - field_ctx, - ) - when GraphQL::TypeKinds::OBJECT - resolve_selection( - value, - field_type, - selection, - field_ctx - ) - when GraphQL::TypeKinds::UNION, GraphQL::TypeKinds::INTERFACE - query = field_ctx.query - resolved_type = query.resolve_type(value) - possible_types = query.possible_types(field_type) + end - if !possible_types.include?(resolved_type) - type_error = GraphQL::UnresolvedTypeError.new(value, field_defn, parent_type, resolved_type, possible_types) + resolve_value( + owner, + parent_type, + field, + field.type, + raw_value, + selection, + field_ctx, + ) + end + + def resolve_value(owner, parent_type, field_defn, field_type, value, selection, field_ctx) + if value.nil? + if field_type.kind.non_null? + type_error = GraphQL::InvalidNullError.new(parent_type, field_defn, value) field_ctx.schema.type_error(type_error, field_ctx) PROPAGATE_NULL else + nil + end + elsif value.is_a?(GraphQL::ExecutionError) + if field_type.kind.non_null? + PROPAGATE_NULL + else + nil + end + elsif value == SKIP + value + else + case field_type.kind + when GraphQL::TypeKinds::SCALAR + field_type.coerce_result(value, field_ctx) + when GraphQL::TypeKinds::ENUM + field_type.coerce_result(value, field_ctx) + when GraphQL::TypeKinds::LIST + inner_type = field_type.of_type + i = 0 + result = [] + value.each do |inner_value| + inner_ctx = field_ctx.spawn( + key: i, + selection: selection, + parent_type: parent_type, + field: field_defn, + ) + + inner_result = resolve_value( + owner, + parent_type, + field_defn, + inner_type, + inner_value, + selection, + inner_ctx, + ) + + result << GraphQL::Execution::FieldResult.new(type: inner_type, owner: owner, value: inner_result) + i += 1 + end + result + when GraphQL::TypeKinds::NON_NULL + wrapped_type = field_type.of_type resolve_value( owner, parent_type, field_defn, - resolved_type, + wrapped_type, value, selection, field_ctx, ) + when GraphQL::TypeKinds::OBJECT + resolve_selection( + value, + field_type, + selection, + field_ctx + ) + when GraphQL::TypeKinds::UNION, GraphQL::TypeKinds::INTERFACE + query = field_ctx.query + resolved_type = query.resolve_type(value) + possible_types = query.possible_types(field_type) + + if !possible_types.include?(resolved_type) + type_error = GraphQL::UnresolvedTypeError.new(value, field_defn, parent_type, resolved_type, possible_types) + field_ctx.schema.type_error(type_error, field_ctx) + PROPAGATE_NULL + else + resolve_value( + owner, + parent_type, + field_defn, + resolved_type, + value, + selection, + field_ctx, + ) + end + else + raise("Unknown type kind: #{field_type.kind}") end - else - raise("Unknown type kind: #{field_type.kind}") end end end + + include ExecutionFunctions # A `.call`-able suitable to be the last step in a middleware chain module FieldResolveStep # Execute the field's resolve method def self.call(_parent_type, parent_object, field_definition, field_args, context, _next = nil)