graphql 2.0.2 → 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 445a61081bd654dbd59e1d6472e027958f8f88bcb5b3f32351921c5f24f7c572
4
- data.tar.gz: d33ff0a628621fe4ccb4e19fbf727ce1e2cffe950697b33bc3682420bb6d2a81
3
+ metadata.gz: 248d39942011f4175ee77643a6e8d103d17d60ac73ee95418eb25e0eee2bbb27
4
+ data.tar.gz: 062c4d1402adf7f3612d888c6e00df91623340239996b6bc14db59ebbec41814
5
5
  SHA512:
6
- metadata.gz: 5d2f6f4c7556db47fec205db4007958f397e8a6b25c40a1838cf0789bf5e71162b0fe62305cb703ec5d5e61371513e978e9544a2f8622787c488d1fbad547af2
7
- data.tar.gz: c477b6a6c782e0bd5ef1eec47fc5dc6fa4ff56d21352c4ee1f6783debd8506c52cf6eace82c639166033b02436d4eebf5a18116624ad7ffd62d19ed8c5735dc0
6
+ metadata.gz: 87813bc8354e52215099b99f932b615856337649897313982e7945b9f039625fd4a788a1edba62faf4f6ee8438d87feafe39b374504561726fd7b9b5f5959d0d
7
+ data.tar.gz: d58627c8387a891814bf5a362a23035f9c58f2b5959fe3857afdec66cac7a2944601cfa03311c3791cc18038fd4a1c4faf5dd1793c54b9bbab1d2bd487e4b140
@@ -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
 
@@ -177,7 +187,7 @@ module GraphQL
177
187
  # @param name [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API)
178
188
  # @param type [Class, GraphQL::BaseType, Array] The return type of this field
179
189
  # @param owner [Class] The type that this field belongs to
180
- # @param null [Boolean] `true` if this field may return `null`, `false` if it is never `null`
190
+ # @param null [Boolean] (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null`
181
191
  # @param description [String] Field description
182
192
  # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
183
193
  # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
@@ -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: nil, 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
@@ -241,7 +253,13 @@ module GraphQL
241
253
  @resolver_method = resolver_method
242
254
  @complexity = complexity
243
255
  @return_type_expr = type
244
- @return_type_null = null
256
+ @return_type_null = if !null.nil?
257
+ null
258
+ elsif resolver_class
259
+ nil
260
+ else
261
+ true
262
+ end
245
263
  @connection = connection
246
264
  @has_max_page_size = max_page_size != :not_given
247
265
  @max_page_size = max_page_size == :not_given ? nil : max_page_size
@@ -337,10 +355,12 @@ module GraphQL
337
355
  def description(text = nil)
338
356
  if text
339
357
  @description = text
358
+ elsif defined?(@description)
359
+ @description
340
360
  elsif @resolver_class
341
361
  @description || @resolver_class.description
342
362
  else
343
- @description
363
+ nil
344
364
  end
345
365
  end
346
366
 
@@ -516,19 +536,15 @@ module GraphQL
516
536
  attr_writer :type
517
537
 
518
538
  def type
519
- if @resolver_class && (t = @resolver_class.type)
520
- t
521
- else
522
- @type ||= if @return_type_expr.nil?
523
- # Not enough info to determine type
524
- message = "Can't determine the return type for #{self.path}"
525
- if @resolver_class
526
- message += " (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
527
- end
528
- raise MissingReturnTypeError, message
529
- else
530
- Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
539
+ if @resolver_class
540
+ return_type = @return_type_expr || @resolver_class.type_expr
541
+ if return_type.nil?
542
+ raise MissingReturnTypeError, "Can't determine the return type for #{self.path} (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
531
543
  end
544
+ nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null
545
+ Member::BuildType.parse_type(return_type, null: nullable)
546
+ else
547
+ @type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
532
548
  end
533
549
  rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
534
550
  # Let this propagate up
@@ -599,31 +615,94 @@ module GraphQL
599
615
  # @param object [GraphQL::Schema::Object] An instance of some type class, wrapping an application object
600
616
  # @param args [Hash] A symbol-keyed hash of Ruby keyword arguments. (Empty if no args)
601
617
  # @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
618
+ def resolve(object, args, query_ctx)
619
+ # Unwrap the GraphQL object to get the application object.
620
+ application_object = object.object
621
+ method_receiver = nil
622
+ method_to_call = nil
623
+ method_args = nil
624
+
625
+ Schema::Validator.validate!(validators, application_object, query_ctx, args)
626
+
627
+ query_ctx.schema.after_lazy(self.authorized?(application_object, args, query_ctx)) do |is_authorized|
628
+ if is_authorized
629
+ with_extensions(object, args, query_ctx) do |obj, ruby_kwargs|
630
+ method_args = ruby_kwargs
631
+ if @resolver_class
632
+ if obj.is_a?(GraphQL::Schema::Object)
633
+ obj = obj.object
634
+ end
635
+ obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
636
+ end
609
637
 
610
- Schema::Validator.validate!(validators, application_object, ctx, args)
638
+ # Find a way to resolve this field, checking:
639
+ #
640
+ # - A method on the type instance;
641
+ # - Hash keys, if the wrapped object is a hash;
642
+ # - A method on the wrapped object;
643
+ # - Or, raise not implemented.
644
+ #
645
+ if obj.respond_to?(resolver_method)
646
+ method_to_call = resolver_method
647
+ method_receiver = obj
648
+ # Call the method with kwargs, if there are any
649
+ if ruby_kwargs.any?
650
+ obj.public_send(resolver_method, **ruby_kwargs)
651
+ else
652
+ obj.public_send(resolver_method)
653
+ end
654
+ elsif obj.object.is_a?(Hash)
655
+ inner_object = obj.object
656
+ if @dig_keys
657
+ inner_object.dig(*@dig_keys)
658
+ elsif inner_object.key?(@method_sym)
659
+ inner_object[@method_sym]
660
+ else
661
+ inner_object[@method_str]
662
+ end
663
+ elsif obj.object.respond_to?(@method_sym)
664
+ method_to_call = @method_sym
665
+ method_receiver = obj.object
666
+ if ruby_kwargs.any?
667
+ obj.object.public_send(@method_sym, **ruby_kwargs)
668
+ else
669
+ obj.object.public_send(@method_sym)
670
+ end
671
+ else
672
+ raise <<-ERR
673
+ Failed to implement #{@owner.graphql_name}.#{@name}, tried:
611
674
 
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)
675
+ - `#{obj.class}##{resolver_method}`, which did not exist
676
+ - `#{obj.object.class}##{@method_sym}`, which did not exist
677
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
678
+
679
+ To implement this field, define one of the methods above (and check for typos)
680
+ ERR
681
+ end
617
682
  end
683
+ else
684
+ raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: query_ctx, field: self)
618
685
  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
686
+ end
687
+ rescue GraphQL::UnauthorizedFieldError => err
688
+ err.field ||= self
689
+ begin
690
+ query_ctx.schema.unauthorized_field(err)
691
+ rescue GraphQL::ExecutionError => err
692
+ err
693
+ end
694
+ rescue GraphQL::UnauthorizedError => err
695
+ begin
696
+ query_ctx.schema.unauthorized_object(err)
697
+ rescue GraphQL::ExecutionError => err
698
+ err
699
+ end
700
+ rescue ArgumentError
701
+ if method_receiver && method_to_call
702
+ assert_satisfactory_implementation(method_receiver, method_to_call, method_args)
703
+ end
704
+ # if the line above doesn't raise, re-raise
705
+ raise
627
706
  end
628
707
 
629
708
  # @param ctx [GraphQL::Query::Context]
@@ -639,70 +718,6 @@ module GraphQL
639
718
 
640
719
  private
641
720
 
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
721
  def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
707
722
  method_defn = receiver.method(method_name)
708
723
  unsatisfied_ruby_kwargs = ruby_kwargs.dup
@@ -148,7 +148,19 @@ module GraphQL
148
148
  end
149
149
  elsif defined?(@resolver_class) && @resolver_class
150
150
  all_defns = {}
151
- all_defns.merge!(@resolver_class.own_field_arguments)
151
+ @resolver_class.all_field_argument_definitions.each do |arg_defn|
152
+ key = arg_defn.graphql_name
153
+ case (current_value = all_defns[key])
154
+ when nil
155
+ all_defns[key] = arg_defn
156
+ when Array
157
+ current_value << arg_defn
158
+ when GraphQL::Schema::Argument
159
+ all_defns[key] = [current_value, arg_defn]
160
+ else
161
+ raise "Invariant: Unexpected argument definition, #{current_value.class}: #{current_value.inspect}"
162
+ end
163
+ end
152
164
  all_defns.merge!(own_arguments)
153
165
  else
154
166
  all_defns = own_arguments
@@ -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
 
@@ -92,6 +92,10 @@ module GraphQL
92
92
  dummy.own_arguments
93
93
  end
94
94
 
95
+ def all_field_argument_definitions
96
+ dummy.all_argument_definitions
97
+ end
98
+
95
99
  # Also apply this argument to the input type:
96
100
  def argument(*args, own_argument: false, **kwargs, &block)
97
101
  it = input_type # make sure any inherited arguments are already added to it
@@ -216,8 +216,8 @@ module GraphQL
216
216
  get_argument(name, context)
217
217
  end
218
218
 
219
- def own_field_arguments
220
- own_arguments
219
+ def all_field_argument_definitions
220
+ all_argument_definitions
221
221
  end
222
222
 
223
223
  # Default `:resolve` set below.
@@ -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.5"
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.5
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-28 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.2.32
600
+ signing_key:
601
601
  specification_version: 4
602
602
  summary: A GraphQL language and runtime for Ruby
603
603
  test_files: []