class GraphQL::Execution::Interpreter::Runtime

I think it would be even better if we could somehow make `continue_field` not recursive. “Trampolining” it somehow.

@api private

Constants

HALT
NO_ARGS

Attributes

context[R]

@return [GraphQL::Query::Context]

progress_path[R]
query[R]

@return [GraphQL::Query]

schema[R]

@return [Class<GraphQL::Schema>]

Public Class Methods

new(query:) click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 151
def initialize(query:)
  @query = query
  @dataloader = query.multiplex.dataloader
  @schema = query.schema
  @context = query.context
  @multiplex_context = query.multiplex.context
  @interpreter_context = @context.namespace(:interpreter)
  @response = GraphQLResultHash.new(nil, nil)
  # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
  @runtime_directive_names = []
  noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
  @schema_directives = schema.directives
  @schema_directives.each do |name, dir_defn|
    if dir_defn.method(:resolve).owner != noop_resolve_owner
      @runtime_directive_names << name
    end
  end
  # A cache of { Class => { String => Schema::Field } }
  # Which assumes that MyObject.get_field("myField") will return the same field
  # during the lifetime of a query
  @fields_cache = Hash.new { |h, k| h[k] = {} }
  # { Class => Boolean }
  @lazy_cache = {}
end

Public Instance Methods

after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true) { |inner_obj| ... } click to toggle source

@param obj [Object] Some user-returned value that may want to be batched @param path [Array<String>] @param field [GraphQL::Schema::Field] @param eager [Boolean] Set to `true` for mutation root fields only @param trace [Boolean] If `false`, don't wrap this with field tracing @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.

# File lib/graphql/execution/interpreter/runtime.rb, line 880
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
  if lazy?(lazy_obj)
    lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
      set_all_interpreter_context(owner_object, field, arguments, path)
      context.scoped_context = scoped_context
      # Wrap the execution of _this_ method with tracing,
      # but don't wrap the continuation below
      inner_obj = begin
        query.with_error_handling do
          begin
            if trace
              query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
                schema.sync_lazy(lazy_obj)
              end
            else
              schema.sync_lazy(lazy_obj)
            end
          rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
            err
          end
        end
      rescue GraphQL::ExecutionError => ex_err
        ex_err
      end
      yield(inner_obj)
    end

    if eager
      lazy.value
    else
      set_result(result, result_name, lazy)
      lazy
    end
  else
    set_all_interpreter_context(owner_object, field, arguments, path)
    yield(lazy_obj)
  end
end
arguments(graphql_object, arg_owner, ast_node) click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 919
def arguments(graphql_object, arg_owner, ast_node)
  if arg_owner.arguments_statically_coercible?
    query.arguments_for(ast_node, arg_owner)
  else
    # The arguments must be prepared in the context of the given object
    query.arguments_for(ast_node, arg_owner, parent_object: graphql_object)
  end
end
authorized_new(type, value, context) click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 957
def authorized_new(type, value, context)
  type.authorized_new(value, context)
end
call_method_on_directives(method_name, object, directives) { || ... } click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 811
def call_method_on_directives(method_name, object, directives, &block)
  return yield if directives.nil? || directives.empty?
  run_directive(method_name, object, directives, 0, &block)
end
continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result) click to toggle source

The resolver for `field` returned `value`. Continue to execute the query, treating `value` as `type` (probably the return type of the field).

Use `next_selections` to resolve object fields, if there are any.

Location information from `path` and `ast_node`.

@return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later

# File lib/graphql/execution/interpreter/runtime.rb, line 680
def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
  if current_type.non_null?
    current_type = current_type.of_type
    is_non_null = true
  end

  case current_type.kind.name
  when "SCALAR", "ENUM"
    r = current_type.coerce_result(value, context)
    set_result(selection_result, result_name, r)
    r
  when "UNION", "INTERFACE"
    resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
    resolved_value ||= value

    after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type|
      possible_types = query.possible_types(current_type)

      if !possible_types.include?(resolved_type)
        parent_type = field.owner_type
        err_class = current_type::UnresolvedTypeError
        type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
        schema.type_error(type_error, context)
        set_result(selection_result, result_name, nil)
        nil
      else
        continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
      end
    end
  when "OBJECT"
    object_proxy = begin
      authorized_new(current_type, value, context)
    rescue GraphQL::ExecutionError => err
      err
    end
    after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
      continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
      if HALT != continue_value
        response_hash = GraphQLResultHash.new(result_name, selection_result)
        set_result(selection_result, result_name, response_hash)
        gathered_selections = gather_selections(continue_value, current_type, next_selections)
        # There are two possibilities for `gathered_selections`:
        # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
        #    This case is handled below, and the result can be written right into the main `response_hash` above.
        #    In this case, `gathered_selections` is a hash of selections.
        # 2. Some selections of this object have runtime directives that may or may not modify execution.
        #    That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
        #    eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
        #    (Technically, it's possible that one of those entries _doesn't_ require isolation.)
        tap_or_each(gathered_selections) do |selections, is_selection_array|
          if is_selection_array
            this_result = GraphQLResultHash.new(result_name, selection_result)
            final_result = response_hash
          else
            this_result = response_hash
            final_result = nil
          end
          set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
          call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
            evaluate_selections(
              path,
              context.scoped_context,
              continue_value,
              current_type,
              false,
              selections,
              this_result,
              final_result,
              owner_object.object,
            )
            this_result
          end
        end
      end
    end
  when "LIST"
    inner_type = current_type.of_type
    # This is true for objects, unions, and interfaces
    use_dataloader_job = !inner_type.unwrap.kind.input?
    response_list = GraphQLResultArray.new(result_name, selection_result)
    response_list.graphql_non_null_list_items = inner_type.non_null?
    set_result(selection_result, result_name, response_list)

    idx = 0
    scoped_context = context.scoped_context
    begin
      value.each do |inner_value|
        break if dead_result?(response_list)
        next_path = path.dup
        next_path << idx
        this_idx = idx
        next_path.freeze
        idx += 1
        if use_dataloader_job
          @dataloader.append_job do
            resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
          end
        else
          resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
        end
      end
    rescue NoMethodError => err
      # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
      if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
        # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
        raise ListResultFailedError.new(value: value, field: field, path: path)
      else
        # This was some other NoMethodError -- let it bubble to reveal the real error.
        raise
      end
    end

    response_list
  else
    raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
  end
end
continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 586
def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
  case value
  when nil
    if is_non_null
      set_result(selection_result, result_name, nil) do
        # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
        err = parent_type::InvalidNullError.new(parent_type, field, value)
        schema.type_error(err, context)
      end
    else
      set_result(selection_result, result_name, nil)
    end
    HALT
  when GraphQL::Error
    # Handle these cases inside a single `when`
    # to avoid the overhead of checking three different classes
    # every time.
    if value.is_a?(GraphQL::ExecutionError)
      if selection_result.nil? || !dead_result?(selection_result)
        value.path ||= path
        value.ast_node ||= ast_node
        context.errors << value
        if selection_result
          set_result(selection_result, result_name, nil)
        end
      end
      HALT
    elsif value.is_a?(GraphQL::UnauthorizedError)
      # this hook might raise & crash, or it might return
      # a replacement value
      next_value = begin
        schema.unauthorized_object(value)
      rescue GraphQL::ExecutionError => err
        err
      end
      continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
    elsif GraphQL::Execution::Execute::SKIP == value
      # It's possible a lazy was already written here
      case selection_result
      when GraphQLResultHash
        selection_result.delete(result_name)
      when GraphQLResultArray
        selection_result.graphql_skip_at(result_name)
      when nil
        # this can happen with directives
      else
        raise "Invariant: unexpected result class #{selection_result.class} (#{selection_result.inspect})"
      end
      HALT
    else
      # What could this actually _be_? Anyhow,
      # preserve the default behavior of doing nothing with it.
      value
    end
  when Array
    # It's an array full of execution errors; add them all.
    if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
      list_type_at_all = (field && (field.type.list?))
      if selection_result.nil? || !dead_result?(selection_result)
        value.each_with_index do |error, index|
          error.ast_node ||= ast_node
          error.path ||= path + (list_type_at_all ? [index] : [])
          context.errors << error
        end
        if selection_result
          if list_type_at_all
            result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
            set_result(selection_result, result_name, result_without_errors)
          else
            set_result(selection_result, result_name, nil)
          end
        end
      end
      HALT
    else
      value
    end
  when GraphQL::Execution::Interpreter::RawValue
    # Write raw value directly to the response without resolving nested objects
    set_result(selection_result, result_name, value.resolve)
    HALT
  else
    value
  end
end
dead_result?(selection_result) click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 537
def dead_result?(selection_result)
  selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
end
deep_merge_selection_result(from_result, into_result) click to toggle source

@return [void]

# File lib/graphql/execution/interpreter/runtime.rb, line 255
def deep_merge_selection_result(from_result, into_result)
  from_result.each do |key, value|
    if !into_result.key?(key)
      into_result[key] = value
    else
      case value
      when GraphQLResultHash
        deep_merge_selection_result(value, into_result[key])
      else
        # We have to assume that, since this passed the `fields_will_merge` selection,
        # that the old and new values are the same.
        # There's no special handling of arrays because currently, there's no way to split the execution
        # of a list over several concurrent flows.
        into_result[key] = value
      end
    end
  end
  from_result.graphql_merged_into = into_result
  nil
end
delete_interpreter_context(key) click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 935
def delete_interpreter_context(key)
  @interpreter_context.delete(key)
  @context.delete(key)
end
directives_include?(node, graphql_object, parent_type) click to toggle source

Check {Schema::Directive.include?} for each directive that's present

# File lib/graphql/execution/interpreter/runtime.rb, line 848
def directives_include?(node, graphql_object, parent_type)
  node.directives.each do |dir_node|
    dir_defn = @schema_directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
    args = arguments(graphql_object, dir_defn, dir_node)
    if !dir_defn.include?(graphql_object, args, context)
      return false
    end
  end
  true
end
evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field, selections_result, parent_object) click to toggle source

@return [void]

# File lib/graphql/execution/interpreter/runtime.rb, line 375
def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
  return if dead_result?(selections_result)
  # As a performance optimization, the hash key will be a `Node` if
  # there's only one selection of the field. But if there are multiple
  # selections of the field, it will be an Array of nodes
  if field_ast_nodes_or_ast_node.is_a?(Array)
    field_ast_nodes = field_ast_nodes_or_ast_node
    ast_node = field_ast_nodes.first
  else
    field_ast_nodes = nil
    ast_node = field_ast_nodes_or_ast_node
  end
  field_name = ast_node.name
  # This can't use `query.get_field` because it gets confused on introspection below if `field_defn` isn't `nil`,
  # because of how `is_introspection` is used to call `.authorized_new` later on.
  field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name, @context)
  is_introspection = false
  if field_defn.nil?
    field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
      is_introspection = true
      entry_point_field
    elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
      is_introspection = true
      dynamic_field
    else
      raise "Invariant: no field for #{owner_type}.#{field_name}"
    end
  end
  return_type = field_defn.type

  next_path = path.dup
  next_path << result_name
  next_path.freeze

  # This seems janky, but we need to know
  # the field's return type at this path in order
  # to propagate `null`
  if return_type.non_null?
    (selections_result.graphql_non_null_field_names ||= []).push(result_name)
  end
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
  set_all_interpreter_context(nil, field_defn, nil, next_path)

  context.scoped_context = scoped_context
  object = owner_object

  if is_introspection
    object = authorized_new(field_defn.owner, object, context)
  end

  total_args_count = field_defn.arguments(context).size
  if total_args_count == 0
    resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
    evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
  else
    # TODO remove all arguments(...) usages?
    @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
      evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
    end
  end
end
evaluate_selection_with_args(arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selection_result, parent_object) click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 437
def evaluate_selection_with_args(arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selection_result, parent_object)  # rubocop:disable Metrics/ParameterLists
  context.scoped_context = scoped_context
  return_type = field_defn.type
  after_lazy(arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
    if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
      continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
      next
    end

    kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
      # We can avoid allocating the `{ Symbol => Object }` hash in this case
      NO_ARGS
    else
      # Bundle up the extras, then make a new arguments instance
      # that includes the extras, too.
      extra_args = {}
      field_defn.extras.each do |extra|
        case extra
        when :ast_node
          extra_args[:ast_node] = ast_node
        when :execution_errors
          extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
        when :path
          extra_args[:path] = next_path
        when :lookahead
          if !field_ast_nodes
            field_ast_nodes = [ast_node]
          end

          extra_args[:lookahead] = Execution::Lookahead.new(
            query: query,
            ast_nodes: field_ast_nodes,
            field: field_defn,
          )
        when :argument_details
          # Use this flag to tell Interpreter::Arguments to add itself
          # to the keyword args hash _before_ freezing everything.
          extra_args[:argument_details] = :__arguments_add_self
        when :irep_node
          # This is used by `__typename` in order to support the legacy runtime,
          # but it has no use here (and it's always `nil`).
          # Stop adding it here to avoid the overhead of `.merge_extras` below.
        when :parent
          extra_args[:parent] = parent_object
        else
          extra_args[extra] = field_defn.fetch_extra(extra, context)
        end
      end
      if extra_args.any?
        resolved_arguments = resolved_arguments.merge_extras(extra_args)
      end
      resolved_arguments.keyword_arguments
    end

    set_all_interpreter_context(nil, nil, resolved_arguments, nil)

    # Optimize for the case that field is selected only once
    if field_ast_nodes.nil? || field_ast_nodes.size == 1
      next_selections = ast_node.selections
      directives = ast_node.directives
    else
      next_selections = []
      directives = []
      field_ast_nodes.each { |f|
        next_selections.concat(f.selections)
        directives.concat(f.directives)
      }
    end

    field_result = call_method_on_directives(:resolve, object, directives) do
      # Actually call the field resolver and capture the result
      app_result = begin
        query.with_error_handling do
          query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments}) do
            field_defn.resolve(object, kwarg_arguments, context)
          end
        end
      rescue GraphQL::ExecutionError => err
        err
      end
      after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
        continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
        if HALT != continue_value
          continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
        end
      end
    end

    # If this field is a root mutation field, immediately resolve
    # all of its child fields before moving on to the next root mutation field.
    # (Subselections of this mutation will still be resolved level-by-level.)
    if is_eager_field
      Interpreter::Resolve.resolve_all([field_result], @dataloader)
    else
      # Return this from `after_lazy` because it might be another lazy that needs to be resolved
      field_result
    end
  end
end
evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) click to toggle source

@return [void]

# File lib/graphql/execution/interpreter/runtime.rb, line 352
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
  set_all_interpreter_context(owner_object, nil, nil, path)

  finished_jobs = 0
  enqueued_jobs = gathered_selections.size
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
    @dataloader.append_job {
      evaluate_selection(
        path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result, parent_object
      )
      finished_jobs += 1
      if target_result && finished_jobs == enqueued_jobs
        deep_merge_selection_result(selections_result, target_result)
      end
    }
  end

  selections_result
end
final_result() click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 176
def final_result
  @response && @response.graphql_result_data
end
gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new) click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 276
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
  selections.each do |node|
    # Skip gathering this if the directive says so
    if !directives_include?(node, owner_object, owner_type)
      next
    end

    if node.is_a?(GraphQL::Language::Nodes::Field)
      response_key = node.alias || node.name
      selections = selections_by_name[response_key]
      # if there was already a selection of this field,
      # use an array to hold all selections,
      # otherise, use the single node to represent the selection
      if selections
        # This field was already selected at least once,
        # add this node to the list of selections
        s = Array(selections)
        s << node
        selections_by_name[response_key] = s
      else
        # No selection was found for this field yet
        selections_by_name[response_key] = node
      end
    else
      # This is an InlineFragment or a FragmentSpread
      if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
        next_selections = GraphQLSelectionSet.new
        next_selections.graphql_directives = node.directives
        if selections_to_run
          selections_to_run << next_selections
        else
          selections_to_run = []
          selections_to_run << selections_by_name
          selections_to_run << next_selections
        end
      else
        next_selections = selections_by_name
      end

      case node
      when GraphQL::Language::Nodes::InlineFragment
        if node.type
          type_defn = schema.get_type(node.type.name, context)

          # Faster than .map{}.include?()
          query.warden.possible_types(type_defn).each do |t|
            if t == owner_type
              gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
              break
            end
          end
        else
          # it's an untyped fragment, definitely continue
          gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
        end
      when GraphQL::Language::Nodes::FragmentSpread
        fragment_def = query.fragments[node.name]
        type_defn = query.get_type(fragment_def.type.name)
        possible_types = query.warden.possible_types(type_defn)
        possible_types.each do |t|
          if t == owner_type
            gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
            break
          end
        end
      else
        raise "Invariant: unexpected selection class: #{node.class}"
      end
    end
  end
  selections_to_run || selections_by_name
end
inspect() click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 180
def inspect
  "#<#{self.class.name} response=#{@response.inspect}>"
end
lazy?(object) click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 961
def lazy?(object)
  @lazy_cache.fetch(object.class) {
    @lazy_cache[object.class] = @schema.lazy?(object)
  }
end
resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 798
def resolve_list_item(inner_value, inner_type, next_path, ast_node, scoped_context, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
  set_all_interpreter_context(nil, nil, nil, next_path)
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
    # This will update `response_list` with the lazy
    after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
      continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
      if HALT != continue_value
        continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
      end
    end
  end
end
resolve_type(type, value, path) click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 940
def resolve_type(type, value, path)
  trace_payload = { context: context, type: type, object: value, path: path }
  resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
    query.resolve_type(type, value)
  end

  if lazy?(resolved_type)
    GraphQL::Execution::Lazy.new do
      query.trace("resolve_type_lazy", trace_payload) do
        schema.sync_lazy(resolved_type)
      end
    end
  else
    [resolved_type, resolved_value]
  end
end
run_directive(method_name, object, directives, idx) { || ... } click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 816
def run_directive(method_name, object, directives, idx, &block)
  dir_node = directives[idx]
  if !dir_node
    yield
  else
    dir_defn = @schema_directives.fetch(dir_node.name)
    if !dir_defn.is_a?(Class)
      dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
    end
    raw_dir_args = arguments(nil, dir_defn, dir_node)
    dir_args = continue_value(
      @context[:current_path], # path
      raw_dir_args, # value
      dir_defn, # parent_type
      nil, # field
      false, # is_non_null
      dir_node, # ast_node
      nil, # result_name
      nil, # selection_result
    )

    if dir_args == HALT
      nil
    else
      dir_defn.public_send(method_name, object, dir_args, context) do
        run_directive(method_name, object, directives, idx + 1, &block)
      end
    end
  end
end
run_eager() click to toggle source

This begins the execution. Some deferred work might be stored up in lazies. @return [void]

# File lib/graphql/execution/interpreter/runtime.rb, line 197
def run_eager
  root_operation = query.selected_operation
  root_op_type = root_operation.operation_type || "query"
  root_type = schema.root_type_for_operation(root_op_type)
  path = []
  set_all_interpreter_context(query.root_value, nil, nil, path)
  object_proxy = authorized_new(root_type, query.root_value, context)
  object_proxy = schema.sync_lazy(object_proxy)

  if object_proxy.nil?
    # Root .authorized? returned false.
    @response = nil
  else
    call_method_on_directives(:resolve, object_proxy, root_operation.directives) do # execute query level directives
      gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
      # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
      # require isolation during execution (because of runtime directives). In that case,
      # make a new, isolated result hash for writing the result into. (That isolated response
      # is eventually merged back into the main response)
      #
      # Otherwise, `gathered_selections` is a hash of selections which can be
      # directly evaluated and the results can be written right into the main response hash.
      tap_or_each(gathered_selections) do |selections, is_selection_array|
        if is_selection_array
          selection_response = GraphQLResultHash.new(nil, nil)
          final_response = @response
        else
          selection_response = @response
          final_response = nil
        end

        @dataloader.append_job {
          set_all_interpreter_context(query.root_value, nil, nil, path)
          call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
            evaluate_selections(
              path,
              context.scoped_context,
              object_proxy,
              root_type,
              root_op_type == "mutation",
              selections,
              selection_response,
              final_response,
              nil,
            )
          end
        }
      end
    end
  end
  delete_interpreter_context(:current_path)
  delete_interpreter_context(:current_field)
  delete_interpreter_context(:current_object)
  delete_interpreter_context(:current_arguments)
  nil
end
set_all_interpreter_context(object, field, arguments, path) click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 859
def set_all_interpreter_context(object, field, arguments, path)
  if object
    @context[:current_object] = @interpreter_context[:current_object] = object
  end
  if field
    @context[:current_field] = @interpreter_context[:current_field] = field
  end
  if arguments
    @context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
  end
  if path
    @context[:current_path] = @interpreter_context[:current_path] = path
  end
end
set_graphql_dead(selection_result) click to toggle source

Mark this node and any already-registered children as dead, so that it accepts no more writes.

# File lib/graphql/execution/interpreter/runtime.rb, line 572
def set_graphql_dead(selection_result)
  case selection_result
  when GraphQLResultArray
    selection_result.graphql_dead = true
    selection_result.values.each { |v| set_graphql_dead(v) }
  when GraphQLResultHash
    selection_result.graphql_dead = true
    selection_result.each { |k, v| set_graphql_dead(v) }
  else
    # It's a scalar, no way to mark it dead.
  end
end
set_interpreter_context(key, value) click to toggle source

Set this pair in the Query context, but also in the interpeter namespace, for compatibility.

# File lib/graphql/execution/interpreter/runtime.rb, line 930
def set_interpreter_context(key, value)
  @interpreter_context[key] = value
  @context[key] = value
end
set_result(selection_result, result_name, value) { || ... } click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 541
def set_result(selection_result, result_name, value)
  if !dead_result?(selection_result)
    if value.nil? &&
        ( # there are two conditions under which `nil` is not allowed in the response:
          (selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
          ((nn = selection_result.graphql_non_null_field_names) && nn.include?(result_name)) # this value would be written into a field that doesn't allow nils
        )
      # This is an invalid nil that should be propagated
      # One caller of this method passes a block,
      # namely when application code returns a `nil` to GraphQL and it doesn't belong there.
      # The other possibility for reaching here is when a field returns an ExecutionError, so we write
      # `nil` to the response, not knowing whether it's an invalid `nil` or not.
      # (And in that case, we don't have to call the schema's handler, since it's not a bug in the application.)
      # TODO the code is trying to tell me something.
      yield if block_given?
      parent = selection_result.graphql_parent
      name_in_parent = selection_result.graphql_result_name
      if parent.nil? # This is a top-level result hash
        @response = nil
      else
        set_result(parent, name_in_parent, nil)
        set_graphql_dead(selection_result)
      end
    else
      selection_result[result_name] = value
    end
  end
end
tap_or_each(obj_or_array) { |item, true| ... } click to toggle source
# File lib/graphql/execution/interpreter/runtime.rb, line 184
def tap_or_each(obj_or_array)
  if obj_or_array.is_a?(Array)
    obj_or_array.each do |item|
      yield(item, true)
    end
  else
    yield(obj_or_array, false)
  end
end