graphql 2.0.1 → 2.0.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f37881627f4e4ddcdb6f2f83f395982d624396949b7e26c1c7fa395f34b6c37a
4
- data.tar.gz: c4840087ffff471824df26453876279b2e44014cdf99f9778b35481c917e18fa
3
+ metadata.gz: 823ea48d61ed1b30e6ad87a6817f80206b9c5d8865f6350cbd142c9798162e37
4
+ data.tar.gz: 8e5f383cfe80418109b0f424d06ab7a78c72255d167f21f32b6c8f6bcef10610
5
5
  SHA512:
6
- metadata.gz: 34999652dad1730d8cf51a46b213dc62d34a00e429b734da857aadbe5d5497c1a70c5d6ebc9d0a316db65a2e8e40075ab56d8bb59eb1cdc45b88c9549149ff92
7
- data.tar.gz: 8ff253848a6fcdaaaa831c87377b4b9d4ca494704bba0844336dc3c500b125df5f888f076f77cdf4fae61c03b2d8cb9383e18406bbf8b6ab2d7bd399dd54bf7d
6
+ metadata.gz: 95e9de185715e1b2a752ecaefdd75e8b4daad8afd4125429d58627972805d759c3a842a58bec29b0b5ee1aba5e1d06a6cb1aed9bd1881653d710f71fe4edfdc7
7
+ data.tar.gz: 99ff2f59d77ee74ba4c1af7fcf097afb5415b6d18a95c1a2527413d6f782f8fa6238aafa96430d2d7332f1177e64b70e5aa2eac1557d042ede9eb4a33466b8b5
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require_relative "./query_complexity"
3
2
  module GraphQL
4
3
  module Analysis
5
4
  module AST
@@ -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
@@ -75,7 +75,10 @@ module GraphQL
75
75
  end
76
76
  end
77
77
 
78
- self.validates(validates)
78
+ if validates && !validates.empty?
79
+ self.validates(validates)
80
+ end
81
+
79
82
  if required == :nullable
80
83
  self.owner.validates(required: { argument: arg_name })
81
84
  end
@@ -247,24 +250,30 @@ module GraphQL
247
250
  end
248
251
 
249
252
  loaded_value = nil
250
- coerced_value = context.schema.error_handler.with_error_handling(context) do
253
+ coerced_value = begin
251
254
  type.coerce_input(value, context)
255
+ rescue StandardError => err
256
+ context.schema.handle_or_reraise(context, err)
252
257
  end
253
258
 
254
259
  # If this isn't lazy, then the block returns eagerly and assigns the result here
255
260
  # If it _is_ lazy, then we write the lazy to the hash, then update it later
256
261
  argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
257
262
  if loads && !from_resolver?
258
- loaded_value = context.query.with_error_handling do
263
+ loaded_value = begin
259
264
  load_and_authorize_value(owner, coerced_value, context)
265
+ rescue StandardError => err
266
+ context.schema.handle_or_reraise(context, err)
260
267
  end
261
268
  end
262
269
 
263
270
  maybe_loaded_value = loaded_value || resolved_coerced_value
264
271
  context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
265
272
  owner.validate_directive_argument(self, resolved_loaded_value)
266
- prepared_value = context.schema.error_handler.with_error_handling(context) do
273
+ prepared_value = begin
267
274
  prepare_value(parent_object, resolved_loaded_value, context: context)
275
+ rescue StandardError => err
276
+ context.schema.handle_or_reraise(context, err)
268
277
  end
269
278
 
270
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
@@ -303,7 +321,9 @@ module GraphQL
303
321
  end
304
322
  end
305
323
 
306
- self.validates(validates)
324
+ if !validates.empty?
325
+ self.validates(validates)
326
+ end
307
327
 
308
328
  if definition_block
309
329
  if definition_block.arity == 1
@@ -335,10 +355,12 @@ module GraphQL
335
355
  def description(text = nil)
336
356
  if text
337
357
  @description = text
358
+ elsif defined?(@description)
359
+ @description
338
360
  elsif @resolver_class
339
361
  @description || @resolver_class.description
340
362
  else
341
- @description
363
+ nil
342
364
  end
343
365
  end
344
366
 
@@ -514,19 +536,15 @@ module GraphQL
514
536
  attr_writer :type
515
537
 
516
538
  def type
517
- if @resolver_class && (t = @resolver_class.type)
518
- t
519
- else
520
- @type ||= if @return_type_expr.nil?
521
- # Not enough info to determine type
522
- message = "Can't determine the return type for #{self.path}"
523
- if @resolver_class
524
- message += " (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
525
- end
526
- raise MissingReturnTypeError, message
527
- else
528
- 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)"
529
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)
530
548
  end
531
549
  rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
532
550
  # Let this propagate up
@@ -597,31 +615,94 @@ module GraphQL
597
615
  # @param object [GraphQL::Schema::Object] An instance of some type class, wrapping an application object
598
616
  # @param args [Hash] A symbol-keyed hash of Ruby keyword arguments. (Empty if no args)
599
617
  # @param ctx [GraphQL::Query::Context]
600
- def resolve(object, args, ctx)
601
- if @resolve_proc
602
- raise "Can't run resolve proc for #{path} when using GraphQL::Execution::Interpreter"
603
- end
604
- begin
605
- # Unwrap the GraphQL object to get the application object.
606
- 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
607
637
 
608
- 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:
609
674
 
610
- ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
611
- if is_authorized
612
- public_send_field(object, args, ctx)
613
- else
614
- 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
615
682
  end
683
+ else
684
+ raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: query_ctx, field: self)
616
685
  end
617
- rescue GraphQL::UnauthorizedFieldError => err
618
- err.field ||= self
619
- ctx.schema.unauthorized_field(err)
620
- rescue GraphQL::UnauthorizedError => err
621
- ctx.schema.unauthorized_object(err)
622
- end
623
- rescue GraphQL::ExecutionError => err
624
- 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
625
706
  end
626
707
 
627
708
  # @param ctx [GraphQL::Query::Context]
@@ -637,70 +718,6 @@ module GraphQL
637
718
 
638
719
  private
639
720
 
640
- def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
641
- with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
642
- begin
643
- method_receiver = nil
644
- method_to_call = nil
645
- if @resolver_class
646
- if obj.is_a?(GraphQL::Schema::Object)
647
- obj = obj.object
648
- end
649
- obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
650
- end
651
-
652
- # Find a way to resolve this field, checking:
653
- #
654
- # - A method on the type instance;
655
- # - Hash keys, if the wrapped object is a hash;
656
- # - A method on the wrapped object;
657
- # - Or, raise not implemented.
658
- #
659
- if obj.respond_to?(resolver_method)
660
- method_to_call = resolver_method
661
- method_receiver = obj
662
- # Call the method with kwargs, if there are any
663
- if ruby_kwargs.any?
664
- obj.public_send(resolver_method, **ruby_kwargs)
665
- else
666
- obj.public_send(resolver_method)
667
- end
668
- elsif obj.object.is_a?(Hash)
669
- inner_object = obj.object
670
- if @dig_keys
671
- inner_object.dig(*@dig_keys)
672
- elsif inner_object.key?(@method_sym)
673
- inner_object[@method_sym]
674
- else
675
- inner_object[@method_str]
676
- end
677
- elsif obj.object.respond_to?(@method_sym)
678
- method_to_call = @method_sym
679
- method_receiver = obj.object
680
- if ruby_kwargs.any?
681
- obj.object.public_send(@method_sym, **ruby_kwargs)
682
- else
683
- obj.object.public_send(@method_sym)
684
- end
685
- else
686
- raise <<-ERR
687
- Failed to implement #{@owner.graphql_name}.#{@name}, tried:
688
-
689
- - `#{obj.class}##{resolver_method}`, which did not exist
690
- - `#{obj.object.class}##{@method_sym}`, which did not exist
691
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
692
-
693
- To implement this field, define one of the methods above (and check for typos)
694
- ERR
695
- end
696
- rescue ArgumentError
697
- assert_satisfactory_implementation(method_receiver, method_to_call, ruby_kwargs)
698
- # if the line above doesn't raise, re-raise
699
- raise
700
- end
701
- end
702
- end
703
-
704
721
  def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
705
722
  method_defn = receiver.method(method_name)
706
723
  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
@@ -80,7 +80,7 @@ module GraphQL
80
80
  visible_interfaces.concat(superclass.interfaces(context))
81
81
  end
82
82
 
83
- visible_interfaces
83
+ visible_interfaces.uniq
84
84
  end
85
85
  end
86
86
  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
 
@@ -98,9 +98,14 @@ module GraphQL
98
98
  class << self
99
99
  # Set up a type-specific invalid null error to use when this object's non-null fields wrongly return `nil`.
100
100
  # It should help with debugging and bug tracker integrations.
101
- def inherited(child_class)
102
- child_class.const_set(:InvalidNullError, GraphQL::InvalidNullError.subclass_for(child_class))
103
- super
101
+ def const_missing(name)
102
+ if name == :InvalidNullError
103
+ custom_err_class = GraphQL::InvalidNullError.subclass_for(self)
104
+ const_set(:InvalidNullError, custom_err_class)
105
+ custom_err_class
106
+ else
107
+ super
108
+ end
104
109
  end
105
110
 
106
111
  def kind
@@ -20,7 +20,17 @@ module GraphQL
20
20
  @payload_type ||= generate_payload_type
21
21
  end
22
22
 
23
- # alias :type :payload_type
23
+ def type(new_type = nil, null: nil)
24
+ if new_type
25
+ payload_type(new_type)
26
+ if !null.nil?
27
+ self.null(null)
28
+ end
29
+ else
30
+ super()
31
+ end
32
+ end
33
+
24
34
  alias :type_expr :payload_type
25
35
 
26
36
  def field_class(new_class = nil)
@@ -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.1"
3
+ VERSION = "2.0.4"
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.1
4
+ version: 2.0.4
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-02-21 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.2.32
600
+ signing_key:
601
601
  specification_version: 4
602
602
  summary: A GraphQL language and runtime for Ruby
603
603
  test_files: []