graphql 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 445a61081bd654dbd59e1d6472e027958f8f88bcb5b3f32351921c5f24f7c572
4
- data.tar.gz: d33ff0a628621fe4ccb4e19fbf727ce1e2cffe950697b33bc3682420bb6d2a81
3
+ metadata.gz: 35d89f9a897b7897a59009d2b66b436750540da911e3c8e86e185f317d74b0e8
4
+ data.tar.gz: 13c06d0144815bbafcdff091815fa939969e51d693e1e33138358131df509783
5
5
  SHA512:
6
- metadata.gz: 5d2f6f4c7556db47fec205db4007958f397e8a6b25c40a1838cf0789bf5e71162b0fe62305cb703ec5d5e61371513e978e9544a2f8622787c488d1fbad547af2
7
- data.tar.gz: c477b6a6c782e0bd5ef1eec47fc5dc6fa4ff56d21352c4ee1f6783debd8506c52cf6eace82c639166033b02436d4eebf5a18116624ad7ffd62d19ed8c5735dc0
6
+ metadata.gz: '063911bc20713d486f3dca9cec21a881a4558a300b8cf03a19f34a82f23cbcd808f3a9b0b51b886b795c3c42e6b559b954e64b6a2a3f69ddd009145ae09cee4f'
7
+ data.tar.gz: cbe4c5cda73c9fcc7bfb6a0e2249ba8c8dbad4e26ad3c2baf7cc5775fac6f7f19ac036baaea25d80b2fbaa58e9cade521a26d7b3c5bdd936b63efc9971c28ce1
@@ -2,51 +2,15 @@
2
2
 
3
3
  module GraphQL
4
4
  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
- #
16
5
  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
-
43
6
  # Register this handler, updating the
44
7
  # internal handler index to maintain least-to-most specific.
45
8
  #
46
9
  # @param error_class [Class<Exception>]
10
+ # @param error_handlers [Hash]
47
11
  # @param error_handler [Proc]
48
12
  # @return [void]
49
- def rescue_from(error_class, error_handler)
13
+ def self.register_rescue_from(error_class, error_handlers, error_handler)
50
14
  subclasses_handlers = {}
51
15
  this_level_subclasses = []
52
16
  # During this traversal, do two things:
@@ -54,13 +18,12 @@ module GraphQL
54
18
  # and gather them up to be inserted _under_ this class
55
19
  # - Find the point in the index where this handler should be inserted
56
20
  # (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
59
22
  this_level_subclasses.clear
60
23
  # First, identify already-loaded handlers that belong
61
24
  # _under_ this one. (That is, they're handlers
62
25
  # for subclasses of `error_class`.)
63
- handlers.each do |err_class, handler|
26
+ error_handlers.each do |err_class, handler|
64
27
  if err_class < error_class
65
28
  subclasses_handlers[err_class] = handler
66
29
  this_level_subclasses << err_class
@@ -68,13 +31,13 @@ module GraphQL
68
31
  end
69
32
  # Any handlers that we'll be moving, delete them from this point in the index
70
33
  this_level_subclasses.each do |err_class|
71
- handlers.delete(err_class)
34
+ error_handlers.delete(err_class)
72
35
  end
73
36
 
74
37
  # 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 }
76
39
  if next_index_point
77
- handlers = next_index_point[1][:subclass_handlers]
40
+ error_handlers = next_index_point[1][:subclass_handlers]
78
41
  else
79
42
  # this new handler doesn't belong to any sub-handlers,
80
43
  # so insert it in the current set of `handlers`
@@ -83,40 +46,15 @@ module GraphQL
83
46
  end
84
47
  # Having found the point at which to insert this handler,
85
48
  # 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]
87
50
  this_class_handlers[:handler] = error_handler
88
51
  this_class_handlers[:subclass_handlers].merge!(subclasses_handlers)
89
52
  nil
90
53
  end
91
54
 
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
-
117
55
  # @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]
120
58
  handler = nil
121
59
  while (handlers) do
122
60
  _err_class, next_handler = handlers.find { |err_class, handler| error_class <= err_class }
@@ -131,8 +69,8 @@ module GraphQL
131
69
  end
132
70
 
133
71
  # 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)
73
+ parent_handler = find_handler_for(schema.superclass, error_class)
136
74
  end
137
75
 
138
76
  # If the inherited handler is more specific than the one defined here,
@@ -498,13 +498,17 @@ module GraphQL
498
498
  field_result = call_method_on_directives(:resolve, object, directives) do
499
499
  # Actually call the field resolver and capture the result
500
500
  app_result = begin
501
- query.with_error_handling do
502
- 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
503
- field_defn.resolve(object, kwarg_arguments, context)
504
- end
501
+ 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
502
+ field_defn.resolve(object, kwarg_arguments, context)
505
503
  end
506
504
  rescue GraphQL::ExecutionError => err
507
505
  err
506
+ rescue StandardError => err
507
+ begin
508
+ query.handle_or_reraise(err)
509
+ rescue GraphQL::ExecutionError => ex_err
510
+ ex_err
511
+ end
508
512
  end
509
513
  after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
510
514
  continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
@@ -871,21 +875,21 @@ module GraphQL
871
875
  # Wrap the execution of _this_ method with tracing,
872
876
  # but don't wrap the continuation below
873
877
  inner_obj = begin
874
- query.with_error_handling do
875
- begin
876
- if trace
877
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
878
- schema.sync_lazy(lazy_obj)
879
- end
880
- else
881
- schema.sync_lazy(lazy_obj)
882
- end
883
- rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
884
- err
878
+ if trace
879
+ query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
880
+ schema.sync_lazy(lazy_obj)
885
881
  end
882
+ else
883
+ schema.sync_lazy(lazy_obj)
886
884
  end
887
- rescue GraphQL::ExecutionError => ex_err
885
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
888
886
  ex_err
887
+ rescue StandardError => err
888
+ begin
889
+ query.handle_or_reraise(err)
890
+ rescue GraphQL::ExecutionError => ex_err
891
+ ex_err
892
+ end
889
893
  end
890
894
  yield(inner_obj)
891
895
  end
@@ -12,9 +12,6 @@ module GraphQL
12
12
  end
13
13
 
14
14
  class NullQuery
15
- def with_error_handling
16
- yield
17
- end
18
15
  end
19
16
 
20
17
  class NullSchema < GraphQL::Schema
data/lib/graphql/query.rb CHANGED
@@ -331,10 +331,8 @@ module GraphQL
331
331
  end
332
332
 
333
333
  # @api private
334
- def with_error_handling
335
- schema.error_handler.with_error_handling(context) do
336
- yield
337
- end
334
+ def handle_or_reraise(err)
335
+ schema.handle_or_reraise(context, err)
338
336
  end
339
337
 
340
338
  private
@@ -31,25 +31,18 @@ module GraphQL
31
31
 
32
32
  # @param collection [Object] The list of items to wrap in a connection
33
33
  # @param item [Object] The newly-added item (will be wrapped in `edge_class`)
34
+ # @param context [GraphQL::Query::Context] The surrounding `ctx`, will be passed to the connection
34
35
  # @param parent [Object] The owner of `collection`, will be passed to the connection if provided
35
- # @param context [GraphQL::Query::Context] The surrounding `ctx`, will be passed to the connection if provided (this is required for cursor encoders)
36
36
  # @param edge_class [Class] The class to wrap `item` with (defaults to the connection's edge class)
37
- def initialize(collection:, item:, parent: nil, context: nil, edge_class: nil)
38
- if context && context.schema.new_connections?
39
- conn_class = context.schema.connections.wrapper_for(collection)
40
- # The rest will be added by ConnectionExtension
41
- @connection = conn_class.new(collection, parent: parent, context: context, edge_class: edge_class)
42
- # Check if this connection supports it, to support old versions of GraphQL-Pro
43
- @edge = if @connection.respond_to?(:range_add_edge)
44
- @connection.range_add_edge(item)
45
- else
46
- @connection.edge_class.new(item, @connection)
47
- end
37
+ def initialize(collection:, item:, context:, parent: nil, edge_class: nil)
38
+ conn_class = context.schema.connections.wrapper_for(collection)
39
+ # The rest will be added by ConnectionExtension
40
+ @connection = conn_class.new(collection, parent: parent, context: context, edge_class: edge_class)
41
+ # Check if this connection supports it, to support old versions of GraphQL-Pro
42
+ @edge = if @connection.respond_to?(:range_add_edge)
43
+ @connection.range_add_edge(item)
48
44
  else
49
- connection_class = BaseConnection.connection_for_nodes(collection)
50
- @connection = connection_class.new(collection, {}, parent: parent, context: context)
51
- edge_class ||= Relay::Edge
52
- @edge = edge_class.new(item, @connection)
45
+ @connection.edge_class.new(item, @connection)
53
46
  end
54
47
 
55
48
  @parent = parent
@@ -107,6 +107,11 @@ module GraphQL
107
107
  if !pt.include?(owner) && owner.is_a?(Class)
108
108
  pt << owner
109
109
  end
110
+ int.interfaces.each do |indirect_int|
111
+ if indirect_int.is_a?(LateBoundType) && (indirect_int_type = get_type(indirect_int.graphql_name))
112
+ update_type_owner(owner, indirect_int_type)
113
+ end
114
+ end
110
115
  end
111
116
  end
112
117
  when nil
@@ -250,24 +250,30 @@ module GraphQL
250
250
  end
251
251
 
252
252
  loaded_value = nil
253
- coerced_value = context.schema.error_handler.with_error_handling(context) do
253
+ coerced_value = begin
254
254
  type.coerce_input(value, context)
255
+ rescue StandardError => err
256
+ context.schema.handle_or_reraise(context, err)
255
257
  end
256
258
 
257
259
  # If this isn't lazy, then the block returns eagerly and assigns the result here
258
260
  # If it _is_ lazy, then we write the lazy to the hash, then update it later
259
261
  argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
260
262
  if loads && !from_resolver?
261
- loaded_value = context.query.with_error_handling do
263
+ loaded_value = begin
262
264
  load_and_authorize_value(owner, coerced_value, context)
265
+ rescue StandardError => err
266
+ context.schema.handle_or_reraise(context, err)
263
267
  end
264
268
  end
265
269
 
266
270
  maybe_loaded_value = loaded_value || resolved_coerced_value
267
271
  context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
268
272
  owner.validate_directive_argument(self, resolved_loaded_value)
269
- prepared_value = context.schema.error_handler.with_error_handling(context) do
273
+ prepared_value = begin
270
274
  prepare_value(parent_object, resolved_loaded_value, context: context)
275
+ rescue StandardError => err
276
+ context.schema.handle_or_reraise(context, err)
271
277
  end
272
278
 
273
279
  # TODO code smell to access such a deeply-nested constant in a distant module
@@ -145,8 +145,18 @@ module GraphQL
145
145
  if !@scope.nil?
146
146
  # The default was overridden
147
147
  @scope
148
+ elsif @return_type_expr
149
+ # Detect a list return type, but don't call `type` since that may eager-load an otherwise lazy-loaded type
150
+ @return_type_expr.is_a?(Array) ||
151
+ (@return_type_expr.is_a?(String) && @return_type_expr.include?("[")) ||
152
+ connection?
153
+ elsif @resolver_class
154
+ resolver_type = @resolver_class.type_expr
155
+ resolver_type.is_a?(Array) ||
156
+ (resolver_type.is_a?(String) && resolver_type.include?("[")) ||
157
+ connection?
148
158
  else
149
- @return_type_expr && (@return_type_expr.is_a?(Array) || (@return_type_expr.is_a?(String) && @return_type_expr.include?("[")) || connection?)
159
+ false
150
160
  end
151
161
  end
152
162
 
@@ -201,7 +211,7 @@ module GraphQL
201
211
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
202
212
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
203
213
  # @param validates [Array<Hash>] Configurations for validating this field
204
- def initialize(type: nil, name: nil, owner: nil, null: true, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, &definition_block)
214
+ def initialize(type: nil, name: nil, owner: nil, null: true, description: :not_given, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, &definition_block)
205
215
  if name.nil?
206
216
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
207
217
  end
@@ -214,7 +224,9 @@ module GraphQL
214
224
  name_s = -name.to_s
215
225
  @underscored_name = -Member::BuildType.underscore(name_s)
216
226
  @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
217
- @description = description
227
+ if description != :not_given
228
+ @description = description
229
+ end
218
230
  self.deprecation_reason = deprecation_reason
219
231
 
220
232
  if method && hash_key && dig
@@ -337,10 +349,12 @@ module GraphQL
337
349
  def description(text = nil)
338
350
  if text
339
351
  @description = text
352
+ elsif defined?(@description)
353
+ @description
340
354
  elsif @resolver_class
341
355
  @description || @resolver_class.description
342
356
  else
343
- @description
357
+ nil
344
358
  end
345
359
  end
346
360
 
@@ -516,19 +530,19 @@ module GraphQL
516
530
  attr_writer :type
517
531
 
518
532
  def type
519
- if @resolver_class && (t = @resolver_class.type)
520
- t
521
- else
522
- @type ||= if @return_type_expr.nil?
533
+ if @return_type_expr.nil?
534
+ if @resolver_class && (t = @resolver_class.type)
535
+ t
536
+ else
523
537
  # Not enough info to determine type
524
538
  message = "Can't determine the return type for #{self.path}"
525
539
  if @resolver_class
526
540
  message += " (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
527
541
  end
528
542
  raise MissingReturnTypeError, message
529
- else
530
- Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
531
543
  end
544
+ else
545
+ @type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
532
546
  end
533
547
  rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
534
548
  # Let this propagate up
@@ -599,31 +613,94 @@ module GraphQL
599
613
  # @param object [GraphQL::Schema::Object] An instance of some type class, wrapping an application object
600
614
  # @param args [Hash] A symbol-keyed hash of Ruby keyword arguments. (Empty if no args)
601
615
  # @param ctx [GraphQL::Query::Context]
602
- def resolve(object, args, ctx)
603
- if @resolve_proc
604
- raise "Can't run resolve proc for #{path} when using GraphQL::Execution::Interpreter"
605
- end
606
- begin
607
- # Unwrap the GraphQL object to get the application object.
608
- application_object = object.object
616
+ def resolve(object, args, query_ctx)
617
+ # Unwrap the GraphQL object to get the application object.
618
+ application_object = object.object
619
+ method_receiver = nil
620
+ method_to_call = nil
621
+ method_args = nil
622
+
623
+ Schema::Validator.validate!(validators, application_object, query_ctx, args)
624
+
625
+ query_ctx.schema.after_lazy(self.authorized?(application_object, args, query_ctx)) do |is_authorized|
626
+ if is_authorized
627
+ with_extensions(object, args, query_ctx) do |obj, ruby_kwargs|
628
+ method_args = ruby_kwargs
629
+ if @resolver_class
630
+ if obj.is_a?(GraphQL::Schema::Object)
631
+ obj = obj.object
632
+ end
633
+ obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
634
+ end
635
+
636
+ # Find a way to resolve this field, checking:
637
+ #
638
+ # - A method on the type instance;
639
+ # - Hash keys, if the wrapped object is a hash;
640
+ # - A method on the wrapped object;
641
+ # - Or, raise not implemented.
642
+ #
643
+ if obj.respond_to?(resolver_method)
644
+ method_to_call = resolver_method
645
+ method_receiver = obj
646
+ # Call the method with kwargs, if there are any
647
+ if ruby_kwargs.any?
648
+ obj.public_send(resolver_method, **ruby_kwargs)
649
+ else
650
+ obj.public_send(resolver_method)
651
+ end
652
+ elsif obj.object.is_a?(Hash)
653
+ inner_object = obj.object
654
+ if @dig_keys
655
+ inner_object.dig(*@dig_keys)
656
+ elsif inner_object.key?(@method_sym)
657
+ inner_object[@method_sym]
658
+ else
659
+ inner_object[@method_str]
660
+ end
661
+ elsif obj.object.respond_to?(@method_sym)
662
+ method_to_call = @method_sym
663
+ method_receiver = obj.object
664
+ if ruby_kwargs.any?
665
+ obj.object.public_send(@method_sym, **ruby_kwargs)
666
+ else
667
+ obj.object.public_send(@method_sym)
668
+ end
669
+ else
670
+ raise <<-ERR
671
+ Failed to implement #{@owner.graphql_name}.#{@name}, tried:
609
672
 
610
- Schema::Validator.validate!(validators, application_object, ctx, args)
673
+ - `#{obj.class}##{resolver_method}`, which did not exist
674
+ - `#{obj.object.class}##{@method_sym}`, which did not exist
675
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
611
676
 
612
- ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
613
- if is_authorized
614
- public_send_field(object, args, ctx)
615
- else
616
- raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
677
+ To implement this field, define one of the methods above (and check for typos)
678
+ ERR
679
+ end
617
680
  end
681
+ else
682
+ raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: query_ctx, field: self)
618
683
  end
619
- rescue GraphQL::UnauthorizedFieldError => err
620
- err.field ||= self
621
- ctx.schema.unauthorized_field(err)
622
- rescue GraphQL::UnauthorizedError => err
623
- ctx.schema.unauthorized_object(err)
624
- end
625
- rescue GraphQL::ExecutionError => err
626
- err
684
+ end
685
+ rescue GraphQL::UnauthorizedFieldError => err
686
+ err.field ||= self
687
+ begin
688
+ query_ctx.schema.unauthorized_field(err)
689
+ rescue GraphQL::ExecutionError => err
690
+ err
691
+ end
692
+ rescue GraphQL::UnauthorizedError => err
693
+ begin
694
+ query_ctx.schema.unauthorized_object(err)
695
+ rescue GraphQL::ExecutionError => err
696
+ err
697
+ end
698
+ rescue ArgumentError
699
+ if method_receiver && method_to_call
700
+ assert_satisfactory_implementation(method_receiver, method_to_call, method_args)
701
+ end
702
+ # if the line above doesn't raise, re-raise
703
+ raise
627
704
  end
628
705
 
629
706
  # @param ctx [GraphQL::Query::Context]
@@ -639,70 +716,6 @@ module GraphQL
639
716
 
640
717
  private
641
718
 
642
- def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
643
- with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
644
- begin
645
- method_receiver = nil
646
- method_to_call = nil
647
- if @resolver_class
648
- if obj.is_a?(GraphQL::Schema::Object)
649
- obj = obj.object
650
- end
651
- obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
652
- end
653
-
654
- # Find a way to resolve this field, checking:
655
- #
656
- # - A method on the type instance;
657
- # - Hash keys, if the wrapped object is a hash;
658
- # - A method on the wrapped object;
659
- # - Or, raise not implemented.
660
- #
661
- if obj.respond_to?(resolver_method)
662
- method_to_call = resolver_method
663
- method_receiver = obj
664
- # Call the method with kwargs, if there are any
665
- if ruby_kwargs.any?
666
- obj.public_send(resolver_method, **ruby_kwargs)
667
- else
668
- obj.public_send(resolver_method)
669
- end
670
- elsif obj.object.is_a?(Hash)
671
- inner_object = obj.object
672
- if @dig_keys
673
- inner_object.dig(*@dig_keys)
674
- elsif inner_object.key?(@method_sym)
675
- inner_object[@method_sym]
676
- else
677
- inner_object[@method_str]
678
- end
679
- elsif obj.object.respond_to?(@method_sym)
680
- method_to_call = @method_sym
681
- method_receiver = obj.object
682
- if ruby_kwargs.any?
683
- obj.object.public_send(@method_sym, **ruby_kwargs)
684
- else
685
- obj.object.public_send(@method_sym)
686
- end
687
- else
688
- raise <<-ERR
689
- Failed to implement #{@owner.graphql_name}.#{@name}, tried:
690
-
691
- - `#{obj.class}##{resolver_method}`, which did not exist
692
- - `#{obj.object.class}##{@method_sym}`, which did not exist
693
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
694
-
695
- To implement this field, define one of the methods above (and check for typos)
696
- ERR
697
- end
698
- rescue ArgumentError
699
- assert_satisfactory_implementation(method_receiver, method_to_call, ruby_kwargs)
700
- # if the line above doesn't raise, re-raise
701
- raise
702
- end
703
- end
704
- end
705
-
706
719
  def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
707
720
  method_defn = receiver.method(method_name)
708
721
  unsatisfied_ruby_kwargs = ruby_kwargs.dup
@@ -11,7 +11,7 @@ module GraphQL
11
11
  # @return [void]
12
12
  def directive(dir_class, **options)
13
13
  @own_directives ||= []
14
- remove_directive(dir_class)
14
+ remove_directive(dir_class) unless dir_class.repeatable?
15
15
  @own_directives << dir_class.new(self, **options)
16
16
  nil
17
17
  end
@@ -8,7 +8,15 @@ module GraphQL
8
8
  if new_edge_type_class
9
9
  @edge_type_class = new_edge_type_class
10
10
  else
11
- @edge_type_class || find_inherited_value(:edge_type_class, Types::Relay::BaseEdge)
11
+ # Don't call `ancestor.edge_type_class`
12
+ # because we don't want a fallback from any ancestors --
13
+ # only apply the fallback if _no_ ancestor has a configured value!
14
+ for ancestor in self.ancestors
15
+ if ancestor.respond_to?(:configured_edge_type_class, true) && (etc = ancestor.configured_edge_type_class)
16
+ return etc
17
+ end
18
+ end
19
+ Types::Relay::BaseEdge
12
20
  end
13
21
  end
14
22
 
@@ -16,7 +24,15 @@ module GraphQL
16
24
  if new_connection_type_class
17
25
  @connection_type_class = new_connection_type_class
18
26
  else
19
- @connection_type_class || find_inherited_value(:connection_type_class, Types::Relay::BaseConnection)
27
+ # Don't call `ancestor.connection_type_class`
28
+ # because we don't want a fallback from any ancestors --
29
+ # only apply the fallback if _no_ ancestor has a configured value!
30
+ for ancestor in self.ancestors
31
+ if ancestor.respond_to?(:configured_connection_type_class, true) && (ctc = ancestor.configured_connection_type_class)
32
+ return ctc
33
+ end
34
+ end
35
+ Types::Relay::BaseConnection
20
36
  end
21
37
  end
22
38
 
@@ -41,6 +57,16 @@ module GraphQL
41
57
  end
42
58
  end
43
59
  end
60
+
61
+ protected
62
+
63
+ def configured_connection_type_class
64
+ @connection_type_class
65
+ end
66
+
67
+ def configured_edge_type_class
68
+ @edge_type_class
69
+ end
44
70
  end
45
71
  end
46
72
  end
@@ -51,12 +51,12 @@ module GraphQL
51
51
  trace_payload = { context: context, type: self, object: object, path: context[:current_path] }
52
52
 
53
53
  maybe_lazy_auth_val = context.query.trace("authorized", trace_payload) do
54
- context.query.with_error_handling do
55
- begin
56
- authorized?(object, context)
57
- rescue GraphQL::UnauthorizedError => err
58
- context.schema.unauthorized_object(err)
59
- end
54
+ begin
55
+ authorized?(object, context)
56
+ rescue GraphQL::UnauthorizedError => err
57
+ context.schema.unauthorized_object(err)
58
+ rescue StandardError => err
59
+ context.query.handle_or_reraise(err)
60
60
  end
61
61
  end
62
62
 
@@ -42,11 +42,11 @@ module GraphQL
42
42
 
43
43
  def validate_non_null_input(value, ctx)
44
44
  coerced_result = begin
45
- ctx.query.with_error_handling do
46
- coerce_input(value, ctx)
47
- end
45
+ coerce_input(value, ctx)
48
46
  rescue GraphQL::CoercionError => err
49
47
  err
48
+ rescue StandardError => err
49
+ ctx.query.handle_or_reraise(err)
50
50
  end
51
51
 
52
52
  if coerced_result.nil?
@@ -693,7 +693,41 @@ module GraphQL
693
693
 
694
694
  def rescue_from(*err_classes, &handler_block)
695
695
  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
+ # @api private
717
+ def handle_or_reraise(context, err)
718
+ handler = Execution::Errors.find_handler_for(self, err.class)
719
+ if handler
720
+ runtime_info = context.namespace(:interpreter) || {}
721
+ obj = runtime_info[:current_object]
722
+ args = runtime_info[:current_arguments]
723
+ args = args && args.keyword_arguments
724
+ field = runtime_info[:current_field]
725
+ if obj.is_a?(GraphQL::Schema::Object)
726
+ obj = obj.object
727
+ end
728
+ handler[:handler].call(err, obj, args, context, field)
729
+ else
730
+ raise err
697
731
  end
698
732
  end
699
733
 
@@ -821,11 +855,6 @@ module GraphQL
821
855
  ctx.errors.push(parse_err)
822
856
  end
823
857
 
824
- # @return [GraphQL::Execution::Errors]
825
- def error_handler
826
- @error_handler ||= GraphQL::Execution::Errors.new(self)
827
- end
828
-
829
858
  def lazy_resolve(lazy_class, value_method)
830
859
  lazy_methods.set(lazy_class, value_method)
831
860
  end
@@ -48,7 +48,9 @@ module GraphQL
48
48
  end
49
49
 
50
50
  def tracer
51
- options.fetch(:tracer, Datadog.tracer)
51
+ default_tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
52
+
53
+ options.fetch(:tracer, default_tracer)
52
54
  end
53
55
 
54
56
  def analytics_available?
@@ -148,22 +148,6 @@ module GraphQL
148
148
  obj_type.field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination."
149
149
  end
150
150
  end
151
-
152
- # By default this calls through to the ConnectionWrapper's edge nodes method,
153
- # but sometimes you need to override it to support the `nodes` field
154
- def nodes
155
- @object.edge_nodes
156
- end
157
-
158
- def edges
159
- if @object.is_a?(GraphQL::Pagination::Connection)
160
- @object.edges
161
- else
162
- context.schema.after_lazy(object.edge_nodes) do |nodes|
163
- nodes.map { |n| self.class.edge_class.new(n, object) }
164
- end
165
- end
166
- end
167
151
  end
168
152
  end
169
153
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.0.2"
3
+ VERSION = "2.0.3"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-01 00:00:00.000000000 Z
11
+ date: 2022-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -581,7 +581,7 @@ metadata:
581
581
  source_code_uri: https://github.com/rmosolgo/graphql-ruby
582
582
  bug_tracker_uri: https://github.com/rmosolgo/graphql-ruby/issues
583
583
  mailing_list_uri: https://tinyletter.com/graphql-ruby
584
- post_install_message:
584
+ post_install_message:
585
585
  rdoc_options: []
586
586
  require_paths:
587
587
  - lib
@@ -596,8 +596,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
596
596
  - !ruby/object:Gem::Version
597
597
  version: '0'
598
598
  requirements: []
599
- rubygems_version: 3.1.6
600
- signing_key:
599
+ rubygems_version: 3.3.3
600
+ signing_key:
601
601
  specification_version: 4
602
602
  summary: A GraphQL language and runtime for Ruby
603
603
  test_files: []