Skip to content

Commit 8255dda

Browse files
committed
Move error handling into Schema
1 parent ea1c404 commit 8255dda

File tree

4 files changed

+57
-83
lines changed

4 files changed

+57
-83
lines changed

lib/graphql/execution/errors.rb

Lines changed: 12 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,79 +2,42 @@
22

33
module GraphQL
44
module Execution
5-
# A plugin that wraps query execution with error handling. Installed by default.
6-
#
7-
# @example Handling ActiveRecord::NotFound
8-
#
9-
# class MySchema < GraphQL::Schema
10-
# rescue_from(ActiveRecord::NotFound) do |err, obj, args, ctx, field|
11-
# ErrorTracker.log("Not Found: #{err.message}")
12-
# nil
13-
# end
14-
# end
15-
#
165
class Errors
17-
NEW_HANDLER_HASH = ->(h, k) {
18-
h[k] = {
19-
class: k,
20-
handler: nil,
21-
subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
22-
}
23-
}
24-
25-
def initialize(schema)
26-
@schema = schema
27-
@handlers = {
28-
class: nil,
29-
handler: nil,
30-
subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
31-
}
32-
end
33-
34-
# @api private
35-
def each_rescue
36-
handlers = @handlers.values
37-
while (handler = handlers.shift) do
38-
yield(handler[:class], handler[:handler])
39-
handlers.concat(handler[:subclass_handlers].values)
40-
end
41-
end
42-
436
# Register this handler, updating the
447
# internal handler index to maintain least-to-most specific.
458
#
469
# @param error_class [Class<Exception>]
10+
# @param error_handlers [Hash]
4711
# @param error_handler [Proc]
4812
# @return [void]
49-
def rescue_from(error_class, error_handler)
13+
def self.register_rescue_from(error_class, error_handlers, error_handler)
5014
subclasses_handlers = {}
5115
this_level_subclasses = []
5216
# During this traversal, do two things:
5317
# - Identify any already-registered subclasses of this error class
5418
# and gather them up to be inserted _under_ this class
5519
# - Find the point in the index where this handler should be inserted
5620
# (That is, _under_ any superclasses, or at top-level, if there are no superclasses registered)
57-
handlers = @handlers[:subclass_handlers]
58-
while (handlers) do
21+
while (error_handlers) do
5922
this_level_subclasses.clear
6023
# First, identify already-loaded handlers that belong
6124
# _under_ this one. (That is, they're handlers
6225
# for subclasses of `error_class`.)
63-
handlers.each do |err_class, handler|
26+
error_handlers.each do |err_class, handler|
6427
if err_class < error_class
6528
subclasses_handlers[err_class] = handler
6629
this_level_subclasses << err_class
6730
end
6831
end
6932
# Any handlers that we'll be moving, delete them from this point in the index
7033
this_level_subclasses.each do |err_class|
71-
handlers.delete(err_class)
34+
error_handlers.delete(err_class)
7235
end
7336

7437
# See if any keys in this hash are superclasses of this new class:
75-
next_index_point = handlers.find { |err_class, handler| error_class < err_class }
38+
next_index_point = error_handlers.find { |err_class, handler| error_class < err_class }
7639
if next_index_point
77-
handlers = next_index_point[1][:subclass_handlers]
40+
error_handlers = next_index_point[1][:subclass_handlers]
7841
else
7942
# this new handler doesn't belong to any sub-handlers,
8043
# so insert it in the current set of `handlers`
@@ -83,40 +46,15 @@ def rescue_from(error_class, error_handler)
8346
end
8447
# Having found the point at which to insert this handler,
8548
# register it and merge any subclass handlers back in at this point.
86-
this_class_handlers = handlers[error_class]
49+
this_class_handlers = error_handlers[error_class]
8750
this_class_handlers[:handler] = error_handler
8851
this_class_handlers[:subclass_handlers].merge!(subclasses_handlers)
8952
nil
9053
end
9154

92-
# Call the given block with the schema's configured error handlers.
93-
#
94-
# If the block returns a lazy value, it's not wrapped with error handling. That area will have to be wrapped itself.
95-
#
96-
# @param ctx [GraphQL::Query::Context]
97-
# @return [Object] Either the result of the given block, or some object to replace the result, in case of error handling.
98-
def with_error_handling(ctx)
99-
yield
100-
rescue StandardError => err
101-
handler = find_handler_for(err.class)
102-
if handler
103-
runtime_info = ctx.namespace(:interpreter) || {}
104-
obj = runtime_info[:current_object]
105-
args = runtime_info[:current_arguments]
106-
args = args && args.keyword_arguments
107-
field = runtime_info[:current_field]
108-
if obj.is_a?(GraphQL::Schema::Object)
109-
obj = obj.object
110-
end
111-
handler[:handler].call(err, obj, args, ctx, field)
112-
else
113-
raise err
114-
end
115-
end
116-
11755
# @return [Proc, nil] The handler for `error_class`, if one was registered on this schema or inherited
118-
def find_handler_for(error_class)
119-
handlers = @handlers[:subclass_handlers]
56+
def self.find_handler_for(schema, error_class)
57+
handlers = schema.error_handlers[:subclass_handlers]
12058
handler = nil
12159
while (handlers) do
12260
_err_class, next_handler = handlers.find { |err_class, handler| error_class <= err_class }
@@ -131,8 +69,8 @@ def find_handler_for(error_class)
13169
end
13270

13371
# check for a handler from a parent class:
134-
if @schema.superclass.respond_to?(:error_handler) && (parent_errors = @schema.superclass.error_handler)
135-
parent_handler = parent_errors.find_handler_for(error_class)
72+
if schema.superclass.respond_to?(:error_handlers) && (parent_errors = schema.superclass.error_handlers)
73+
parent_handler = find_handler_for(schema.superclass, error_class)
13674
end
13775

13876
# If the inherited handler is more specific than the one defined here,

lib/graphql/query.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ def subscription?
332332

333333
# @api private
334334
def with_error_handling
335-
schema.error_handler.with_error_handling(context) do
335+
schema.with_error_handling(context) do
336336
yield
337337
end
338338
end

lib/graphql/schema.rb

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,48 @@ def context_class(new_context_class = nil)
693693

694694
def rescue_from(*err_classes, &handler_block)
695695
err_classes.each do |err_class|
696-
error_handler.rescue_from(err_class, handler_block)
696+
Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block)
697+
end
698+
end
699+
700+
NEW_HANDLER_HASH = ->(h, k) {
701+
h[k] = {
702+
class: k,
703+
handler: nil,
704+
subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
705+
}
706+
}
707+
708+
def error_handlers
709+
@error_handlers ||= {
710+
class: nil,
711+
handler: nil,
712+
subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
713+
}
714+
end
715+
716+
# Call the given block with the schema's configured error handlers.
717+
#
718+
# If the block returns a lazy value, it's not wrapped with error handling. That area will have to be wrapped itself.
719+
#
720+
# @param ctx [GraphQL::Query::Context]
721+
# @return [Object] Either the result of the given block, or some object to replace the result, in case of error handling.
722+
def with_error_handling(ctx)
723+
yield
724+
rescue StandardError => err
725+
handler = Execution::Errors.find_handler_for(self, err.class)
726+
if handler
727+
runtime_info = ctx.namespace(:interpreter) || {}
728+
obj = runtime_info[:current_object]
729+
args = runtime_info[:current_arguments]
730+
args = args && args.keyword_arguments
731+
field = runtime_info[:current_field]
732+
if obj.is_a?(GraphQL::Schema::Object)
733+
obj = obj.object
734+
end
735+
handler[:handler].call(err, obj, args, ctx, field)
736+
else
737+
raise err
697738
end
698739
end
699740

@@ -821,11 +862,6 @@ def parse_error(parse_err, ctx)
821862
ctx.errors.push(parse_err)
822863
end
823864

824-
# @return [GraphQL::Execution::Errors]
825-
def error_handler
826-
@error_handler ||= GraphQL::Execution::Errors.new(self)
827-
end
828-
829865
def lazy_resolve(lazy_class, value_method)
830866
lazy_methods.set(lazy_class, value_method)
831867
end

lib/graphql/schema/argument.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ def coerce_into_values(parent_object, values, context, argument_values)
247247
end
248248

249249
loaded_value = nil
250-
coerced_value = context.schema.error_handler.with_error_handling(context) do
250+
coerced_value = context.schema.with_error_handling(context) do
251251
type.coerce_input(value, context)
252252
end
253253

@@ -263,7 +263,7 @@ def coerce_into_values(parent_object, values, context, argument_values)
263263
maybe_loaded_value = loaded_value || resolved_coerced_value
264264
context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
265265
owner.validate_directive_argument(self, resolved_loaded_value)
266-
prepared_value = context.schema.error_handler.with_error_handling(context) do
266+
prepared_value = context.schema.with_error_handling(context) do
267267
prepare_value(parent_object, resolved_loaded_value, context: context)
268268
end
269269

0 commit comments

Comments
 (0)