class GraphQL::Execution::Errors

A plugin that wraps query execution with error handling. Supports class-based schemas and the new {Interpreter} runtime only.

@example Handling ActiveRecord::NotFound

class MySchema < GraphQL::Schema
  use GraphQL::Execution::Errors

  rescue_from(ActiveRecord::NotFound) do |err, obj, args, ctx, field|
    ErrorTracker.log("Not Found: #{err.message}")
    nil
  end
end

Constants

NEW_HANDLER_HASH

Public Class Methods

new(schema) click to toggle source
# File lib/graphql/execution/errors.rb, line 33
def initialize(schema)
  @schema = schema
  @handlers = {
    class: nil,
    handler: nil,
    subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
  }
end
use(schema) click to toggle source
# File lib/graphql/execution/errors.rb, line 20
def self.use(schema)
  definition_line = caller(2, 1).first
  GraphQL::Deprecation.warn("GraphQL::Execution::Errors is now installed by default, remove `use GraphQL::Execution::Errors` from #{definition_line}")
end

Public Instance Methods

each_rescue() { |handler, handler| ... } click to toggle source

@api private

# File lib/graphql/execution/errors.rb, line 43
def each_rescue
  handlers = @handlers.values
  while (handler = handlers.shift) do
    yield(handler[:class], handler[:handler])
    handlers.concat(handler[:subclass_handlers].values)
  end
end
find_handler_for(error_class) click to toggle source

@return [Proc, nil] The handler for `error_class`, if one was registered on this schema or inherited

# File lib/graphql/execution/errors.rb, line 126
def find_handler_for(error_class)
  handlers = @handlers[:subclass_handlers]
  handler = nil
  while (handlers) do
    _err_class, next_handler = handlers.find { |err_class, handler| error_class <= err_class }
    if next_handler
      handlers = next_handler[:subclass_handlers]
      handler = next_handler
    else
      # Don't reassign `handler` --
      # let the previous assignment carry over outside this block.
      break
    end
  end

  # check for a handler from a parent class:
  if @schema.superclass.respond_to?(:error_handler) && (parent_errors = @schema.superclass.error_handler)
    parent_handler = parent_errors.find_handler_for(error_class)
  end

  # If the inherited handler is more specific than the one defined here,
  # use it.
  # If it's a tie (or there is no parent handler), use the one defined here.
  # If there's an inherited one, but not one defined here, use the inherited one.
  # Otherwise, there's no handler for this error, return `nil`.
  if parent_handler && handler && parent_handler[:class] < handler[:class]
    parent_handler
  elsif handler
    handler
  elsif parent_handler
    parent_handler
  else
    nil
  end
end
rescue_from(error_class, error_handler) click to toggle source

Register this handler, updating the internal handler index to maintain least-to-most specific.

@param error_class [Class<Exception>] @param error_handler [Proc] @return [void]

# File lib/graphql/execution/errors.rb, line 57
def rescue_from(error_class, error_handler)
  subclasses_handlers = {}
  this_level_subclasses = []
  # During this traversal, do two things:
  # - Identify any already-registered subclasses of this error class
  #   and gather them up to be inserted _under_ this class
  # - Find the point in the index where this handler should be inserted
  #   (That is, _under_ any superclasses, or at top-level, if there are no superclasses registered)
  handlers = @handlers[:subclass_handlers]
  while (handlers) do
    this_level_subclasses.clear
    # First, identify already-loaded handlers that belong
    # _under_ this one. (That is, they're handlers
    # for subclasses of `error_class`.)
    handlers.each do |err_class, handler|
      if err_class < error_class
        subclasses_handlers[err_class] = handler
        this_level_subclasses << err_class
      end
    end
    # Any handlers that we'll be moving, delete them from this point in the index
    this_level_subclasses.each do |err_class|
      handlers.delete(err_class)
    end

    # See if any keys in this hash are superclasses of this new class:
    next_index_point = handlers.find { |err_class, handler| error_class < err_class }
    if next_index_point
      handlers = next_index_point[1][:subclass_handlers]
    else
      # this new handler doesn't belong to any sub-handlers,
      # so insert it in the current set of `handlers`
      break
    end
  end
  # Having found the point at which to insert this handler,
  # register it and merge any subclass handlers back in at this point.
  this_class_handlers = handlers[error_class]
  this_class_handlers[:handler] = error_handler
  this_class_handlers[:subclass_handlers].merge!(subclasses_handlers)
  nil
end
with_error_handling(ctx) { || ... } click to toggle source

Call the given block with the schema's configured error handlers.

If the block returns a lazy value, it's not wrapped with error handling. That area will have to be wrapped itself.

@param ctx [GraphQL::Query::Context] @return [Object] Either the result of the given block, or some object to replace the result, in case of error handling.

# File lib/graphql/execution/errors.rb, line 106
def with_error_handling(ctx)
  yield
rescue StandardError => err
  handler = find_handler_for(err.class)
  if handler
    runtime_info = ctx.namespace(:interpreter) || {}
    obj = runtime_info[:current_object]
    args = runtime_info[:current_arguments]
    args = args && args.keyword_arguments
    field = runtime_info[:current_field]
    if obj.is_a?(GraphQL::Schema::Object)
      obj = obj.object
    end
    handler[:handler].call(err, obj, args, ctx, field)
  else
    raise err
  end
end