graphql 1.11.5 → 1.11.9

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.

Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/object_generator.rb +2 -0
  3. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  4. data/lib/graphql/execution/interpreter/arguments.rb +21 -6
  5. data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
  6. data/lib/graphql/execution/interpreter/runtime.rb +53 -39
  7. data/lib/graphql/integer_decoding_error.rb +17 -0
  8. data/lib/graphql/invalid_null_error.rb +1 -1
  9. data/lib/graphql/pagination/connections.rb +11 -5
  10. data/lib/graphql/query/context.rb +4 -1
  11. data/lib/graphql/query/validation_pipeline.rb +1 -1
  12. data/lib/graphql/query.rb +4 -1
  13. data/lib/graphql/relay/array_connection.rb +2 -2
  14. data/lib/graphql/relay/range_add.rb +14 -5
  15. data/lib/graphql/schema/build_from_definition.rb +11 -7
  16. data/lib/graphql/schema/default_type_error.rb +2 -0
  17. data/lib/graphql/schema/field/connection_extension.rb +8 -7
  18. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  19. data/lib/graphql/schema/field.rb +22 -12
  20. data/lib/graphql/schema/member/has_arguments.rb +51 -52
  21. data/lib/graphql/schema/member/has_fields.rb +2 -2
  22. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  23. data/lib/graphql/schema/unique_within_type.rb +1 -2
  24. data/lib/graphql/schema.rb +39 -11
  25. data/lib/graphql/static_validation/all_rules.rb +1 -0
  26. data/lib/graphql/static_validation/base_visitor.rb +3 -0
  27. data/lib/graphql/static_validation/rules/fields_will_merge.rb +29 -21
  28. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  29. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  30. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  31. data/lib/graphql/static_validation/validation_context.rb +6 -1
  32. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  33. data/lib/graphql/static_validation/validator.rb +33 -10
  34. data/lib/graphql/static_validation.rb +1 -0
  35. data/lib/graphql/tracing/platform_tracing.rb +1 -1
  36. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  37. data/lib/graphql/types/int.rb +9 -2
  38. data/lib/graphql/types/relay/base_connection.rb +2 -1
  39. data/lib/graphql/types/relay/base_edge.rb +2 -1
  40. data/lib/graphql/types/string.rb +7 -1
  41. data/lib/graphql/unauthorized_error.rb +1 -1
  42. data/lib/graphql/version.rb +1 -1
  43. data/lib/graphql.rb +1 -0
  44. data/readme.md +1 -1
  45. metadata +10 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 003526dc3efe3c95425607d76c4beffa6777cb71458e7b5ddbc1deecfc5973df
4
- data.tar.gz: 0aa562c5b69dd2a8294bc90540af71cc4b6e7b8f701f71b3f18a0f47dcf9594b
3
+ metadata.gz: '03288f39c878bbb9e7e0831ec3b7f5238e4063439df8f1a8a297b6d2e88798c4'
4
+ data.tar.gz: 157ad2e363c871c5fe5e13322fe5ea98aedfee77febd0c77ea092507fa11d696
5
5
  SHA512:
6
- metadata.gz: 8563c973af0d9060ae1a90e39affb7b2b8afec3233181f4cb00277d3c754a9446c3f994b8bb9c81689a1d1db92e19f91195b8e2e14a21fec7e2c43c93fc18840
7
- data.tar.gz: 3e61b7f17cc56bb1873e4efe5b6c5b0e8142b2ec7ea211632476411acf6112876fa554f4a12b56b538480367a4f4f174dd089cd8f24a032257974f9b4079876e
6
+ metadata.gz: 6ab05e247b8a3a3a1d08daec2edeb20fdc29b2cf196e34a5f84fbf228da1bd5da7ce445b2972648b6ce4c74f15e6d1dfc8b2eb1fc0929dd0df2b67f2db4e73da
7
+ data.tar.gz: 4ce3349c4cd3623827d1596bfbdb9b8974a26cc5ffc5be162c1f3b6fc1fb8922d8902e166345d14afcba77f8b693607769eee2db52907b79190f6beb5ee2ece2
@@ -40,6 +40,8 @@ module Graphql
40
40
  case type_expression
41
41
  when "Text"
42
42
  ["String", null]
43
+ when "Decimal"
44
+ ["Float", null]
43
45
  when "DateTime", "Datetime"
44
46
  ["GraphQL::Types::ISO8601DateTime", null]
45
47
  when "Date"
@@ -2,9 +2,9 @@
2
2
  module GraphQL
3
3
  module Define
4
4
  module AssignGlobalIdField
5
- def self.call(type_defn, field_name)
5
+ def self.call(type_defn, field_name, **field_kwargs)
6
6
  resolve = GraphQL::Relay::GlobalIdResolve.new(type: type_defn)
7
- GraphQL::Define::AssignObjectField.call(type_defn, field_name, type: GraphQL::ID_TYPE.to_non_null_type, resolve: resolve)
7
+ GraphQL::Define::AssignObjectField.call(type_defn, field_name, **field_kwargs, type: GraphQL::ID_TYPE.to_non_null_type, resolve: resolve)
8
8
  end
9
9
  end
10
10
  end
@@ -14,18 +14,33 @@ module GraphQL
14
14
  # This hash is the one used at runtime.
15
15
  #
16
16
  # @return [Hash<Symbol, Object>]
17
- attr_reader :keyword_arguments
17
+ def keyword_arguments
18
+ @keyword_arguments ||= begin
19
+ kwargs = {}
20
+ argument_values.each do |name, arg_val|
21
+ kwargs[name] = arg_val.value
22
+ end
23
+ kwargs
24
+ end
25
+ end
18
26
 
19
- def initialize(keyword_arguments:, argument_values:)
20
- @keyword_arguments = keyword_arguments
27
+ # @param argument_values [nil, Hash{Symbol => ArgumentValue}]
28
+ def initialize(argument_values:)
21
29
  @argument_values = argument_values
30
+ @empty = argument_values.nil? || argument_values.empty?
22
31
  end
23
32
 
24
33
  # @return [Hash{Symbol => ArgumentValue}]
25
- attr_reader :argument_values
34
+ def argument_values
35
+ @argument_values ||= {}
36
+ end
37
+
38
+ def empty?
39
+ @empty
40
+ end
26
41
 
27
- def_delegators :@keyword_arguments, :key?, :[], :fetch, :keys, :each, :values
28
- def_delegators :@argument_values, :each_value
42
+ def_delegators :keyword_arguments, :key?, :[], :fetch, :keys, :each, :values
43
+ def_delegators :argument_values, :each_value
29
44
 
30
45
  def inspect
31
46
  "#<#{self.class} @keyword_arguments=#{keyword_arguments.inspect}>"
@@ -29,11 +29,16 @@ module GraphQL
29
29
 
30
30
  private
31
31
 
32
+ NO_ARGUMENTS = {}.freeze
33
+
32
34
  NO_VALUE_GIVEN = Object.new
33
35
 
34
36
  def prepare_args_hash(ast_arg_or_hash_or_value)
35
37
  case ast_arg_or_hash_or_value
36
38
  when Hash
39
+ if ast_arg_or_hash_or_value.empty?
40
+ return NO_ARGUMENTS
41
+ end
37
42
  args_hash = {}
38
43
  ast_arg_or_hash_or_value.each do |k, v|
39
44
  args_hash[k] = prepare_args_hash(v)
@@ -42,6 +47,9 @@ module GraphQL
42
47
  when Array
43
48
  ast_arg_or_hash_or_value.map { |v| prepare_args_hash(v) }
44
49
  when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
50
+ if ast_arg_or_hash_or_value.arguments.empty?
51
+ return NO_ARGUMENTS
52
+ end
45
53
  args_hash = {}
46
54
  ast_arg_or_hash_or_value.arguments.each do |arg|
47
55
  v = prepare_args_hash(arg.value)
@@ -47,8 +47,7 @@ module GraphQL
47
47
  root_op_type = root_operation.operation_type || "query"
48
48
  root_type = schema.root_type_for_operation(root_op_type)
49
49
  path = []
50
- set_interpreter_context(:current_object, query.root_value)
51
- set_interpreter_context(:current_path, path)
50
+ set_all_interpreter_context(query.root_value, nil, nil, path)
52
51
  object_proxy = authorized_new(root_type, query.root_value, context, path)
53
52
  object_proxy = schema.sync_lazy(object_proxy)
54
53
  if object_proxy.nil?
@@ -118,9 +117,10 @@ module GraphQL
118
117
  end
119
118
  end
120
119
 
120
+ NO_ARGS = {}.freeze
121
+
121
122
  def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
122
- set_interpreter_context(:current_object, owner_object)
123
- set_interpreter_context(:current_path, path)
123
+ set_all_interpreter_context(owner_object, nil, nil, path)
124
124
  selections_by_name = {}
125
125
  gather_selections(owner_object, owner_type, selections, selections_by_name)
126
126
  selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
@@ -159,8 +159,7 @@ module GraphQL
159
159
  # to propagate `null`
160
160
  set_type_at_path(next_path, return_type)
161
161
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
162
- set_interpreter_context(:current_path, next_path)
163
- set_interpreter_context(:current_field, field_defn)
162
+ set_all_interpreter_context(nil, field_defn, nil, next_path)
164
163
 
165
164
  context.scoped_context = scoped_context
166
165
  object = owner_object
@@ -171,44 +170,50 @@ module GraphQL
171
170
 
172
171
  begin
173
172
  kwarg_arguments = arguments(object, field_defn, ast_node)
174
- rescue GraphQL::ExecutionError => e
173
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => e
175
174
  continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
176
175
  next
177
176
  end
178
177
 
179
178
  after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
180
- if resolved_arguments.is_a? GraphQL::ExecutionError
179
+ case resolved_arguments
180
+ when GraphQL::ExecutionError, GraphQL::UnauthorizedError
181
181
  continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
182
182
  next
183
183
  end
184
184
 
185
- kwarg_arguments = resolved_arguments.keyword_arguments
186
-
187
- field_defn.extras.each do |extra|
188
- case extra
189
- when :ast_node
190
- kwarg_arguments[:ast_node] = ast_node
191
- when :execution_errors
192
- kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
193
- when :path
194
- kwarg_arguments[:path] = next_path
195
- when :lookahead
196
- if !field_ast_nodes
197
- field_ast_nodes = [ast_node]
185
+ if resolved_arguments.empty? && field_defn.extras.empty?
186
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
187
+ kwarg_arguments = NO_ARGS
188
+ else
189
+ kwarg_arguments = resolved_arguments.keyword_arguments
190
+
191
+ field_defn.extras.each do |extra|
192
+ case extra
193
+ when :ast_node
194
+ kwarg_arguments[:ast_node] = ast_node
195
+ when :execution_errors
196
+ kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
197
+ when :path
198
+ kwarg_arguments[:path] = next_path
199
+ when :lookahead
200
+ if !field_ast_nodes
201
+ field_ast_nodes = [ast_node]
202
+ end
203
+ kwarg_arguments[:lookahead] = Execution::Lookahead.new(
204
+ query: query,
205
+ ast_nodes: field_ast_nodes,
206
+ field: field_defn,
207
+ )
208
+ when :argument_details
209
+ kwarg_arguments[:argument_details] = resolved_arguments
210
+ else
211
+ kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
198
212
  end
199
- kwarg_arguments[:lookahead] = Execution::Lookahead.new(
200
- query: query,
201
- ast_nodes: field_ast_nodes,
202
- field: field_defn,
203
- )
204
- when :argument_details
205
- kwarg_arguments[:argument_details] = resolved_arguments
206
- else
207
- kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
208
213
  end
209
214
  end
210
215
 
211
- set_interpreter_context(:current_arguments, kwarg_arguments)
216
+ set_all_interpreter_context(nil, nil, kwarg_arguments, nil)
212
217
 
213
218
  # Optimize for the case that field is selected only once
214
219
  if field_ast_nodes.nil? || field_ast_nodes.size == 1
@@ -414,6 +419,21 @@ module GraphQL
414
419
  true
415
420
  end
416
421
 
422
+ def set_all_interpreter_context(object, field, arguments, path)
423
+ if object
424
+ @context[:current_object] = @interpreter_context[:current_object] = object
425
+ end
426
+ if field
427
+ @context[:current_field] = @interpreter_context[:current_field] = field
428
+ end
429
+ if arguments
430
+ @context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
431
+ end
432
+ if path
433
+ @context[:current_path] = @interpreter_context[:current_path] = path
434
+ end
435
+ end
436
+
417
437
  # @param obj [Object] Some user-returned value that may want to be batched
418
438
  # @param path [Array<String>]
419
439
  # @param field [GraphQL::Schema::Field]
@@ -421,16 +441,10 @@ module GraphQL
421
441
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
422
442
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
423
443
  def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block)
424
- set_interpreter_context(:current_object, owner_object)
425
- set_interpreter_context(:current_arguments, arguments)
426
- set_interpreter_context(:current_path, path)
427
- set_interpreter_context(:current_field, field)
444
+ set_all_interpreter_context(owner_object, field, arguments, path)
428
445
  if schema.lazy?(lazy_obj)
429
446
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
430
- set_interpreter_context(:current_path, path)
431
- set_interpreter_context(:current_field, field)
432
- set_interpreter_context(:current_object, owner_object)
433
- set_interpreter_context(:current_arguments, arguments)
447
+ set_all_interpreter_context(owner_object, field, arguments, path)
434
448
  context.scoped_context = scoped_context
435
449
  # Wrap the execution of _this_ method with tracing,
436
450
  # but don't wrap the continuation below
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ # This error is raised when `Types::Int` is given an input value outside of 32-bit integer range.
4
+ #
5
+ # For really big integer values, consider `GraphQL::Types::BigInt`
6
+ #
7
+ # @see GraphQL::Types::Int which raises this error
8
+ class IntegerDecodingError < GraphQL::RuntimeTypeError
9
+ # The value which couldn't be decoded
10
+ attr_reader :integer_value
11
+
12
+ def initialize(value)
13
+ @integer_value = value
14
+ super("Integer out of bounds: #{value}. \nConsider using GraphQL::Types::BigInt instead.")
15
+ end
16
+ end
17
+ end
@@ -39,7 +39,7 @@ module GraphQL
39
39
  end
40
40
 
41
41
  def inspect
42
- if name.nil? && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation)
42
+ if (name.nil? || parent_class.name.nil?) && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation)
43
43
  "#{mutation.inspect}::#{parent_class.graphql_name}::InvalidNullError"
44
44
  else
45
45
  super
@@ -63,11 +63,7 @@ module GraphQL
63
63
  all_wrappers
64
64
  end
65
65
 
66
- # Used by the runtime to wrap values in connection wrappers.
67
- # @api Private
68
- def wrap(field, parent, items, arguments, context, wrappers: all_wrappers)
69
- return items if GraphQL::Execution::Interpreter::RawValue === items
70
-
66
+ def wrapper_for(items, wrappers: all_wrappers)
71
67
  impl = nil
72
68
 
73
69
  items.class.ancestors.each { |cls|
@@ -75,6 +71,16 @@ module GraphQL
75
71
  break if impl
76
72
  }
77
73
 
74
+ impl
75
+ end
76
+
77
+ # Used by the runtime to wrap values in connection wrappers.
78
+ # @api Private
79
+ def wrap(field, parent, items, arguments, context, wrappers: all_wrappers)
80
+ return items if GraphQL::Execution::Interpreter::RawValue === items
81
+
82
+ impl = wrapper_for(items, wrappers: wrappers)
83
+
78
84
  if impl.nil?
79
85
  raise ImplementationMissingError, "Couldn't find a connection wrapper for #{items.class} during #{field.path} (#{items.inspect})"
80
86
  end
@@ -167,7 +167,10 @@ module GraphQL
167
167
  # @api private
168
168
  attr_accessor :scoped_context
169
169
 
170
- def_delegators :@provided_values, :[]=
170
+ def []=(key, value)
171
+ @provided_values[key] = value
172
+ end
173
+
171
174
  def_delegators :@query, :trace, :interpreter?
172
175
 
173
176
  # @!method []=(key, value)
@@ -72,7 +72,7 @@ module GraphQL
72
72
  elsif @operation_name_error
73
73
  @validation_errors << @operation_name_error
74
74
  else
75
- validation_result = @schema.static_validator.validate(@query, validate: @validate)
75
+ validation_result = @schema.static_validator.validate(@query, validate: @validate, timeout: @schema.validate_timeout)
76
76
  @validation_errors.concat(validation_result[:errors])
77
77
  @internal_representation = validation_result[:irep]
78
78
 
data/lib/graphql/query.rb CHANGED
@@ -88,6 +88,7 @@ module GraphQL
88
88
  schema = schema.graphql_definition
89
89
  end
90
90
  @schema = schema
91
+ @interpreter = @schema.interpreter?
91
92
  @filter = schema.default_filter.merge(except: except, only: only)
92
93
  @context = schema.context_class.new(query: self, object: root_value, values: context)
93
94
  @warden = warden
@@ -148,7 +149,9 @@ module GraphQL
148
149
  @query_string ||= (document ? document.to_query_string : nil)
149
150
  end
150
151
 
151
- def_delegators :@schema, :interpreter?
152
+ def interpreter?
153
+ @interpreter
154
+ end
152
155
 
153
156
  def subscription_update?
154
157
  @subscription_topic && subscription?
@@ -31,8 +31,6 @@ module GraphQL
31
31
  end
32
32
  end
33
33
 
34
- private
35
-
36
34
  def first
37
35
  @first ||= begin
38
36
  capped = limit_pagination_argument(arguments[:first], max_page_size)
@@ -47,6 +45,8 @@ module GraphQL
47
45
  @last ||= limit_pagination_argument(arguments[:last], max_page_size)
48
46
  end
49
47
 
48
+ private
49
+
50
50
  # apply first / last limit results
51
51
  def paged_nodes
52
52
  @paged_nodes ||= begin
@@ -33,12 +33,21 @@ module GraphQL
33
33
  # @param item [Object] The newly-added item (will be wrapped in `edge_class`)
34
34
  # @param parent [Object] The owner of `collection`, will be passed to the connection if provided
35
35
  # @param context [GraphQL::Query::Context] The surrounding `ctx`, will be passed to the connection if provided (this is required for cursor encoders)
36
- # @param edge_class [Class] The class to wrap `item` with
37
- def initialize(collection:, item:, parent: nil, context: nil, edge_class: Relay::Edge)
38
- connection_class = BaseConnection.connection_for_nodes(collection)
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
+ @edge = @connection.edge_class.new(item, @connection)
43
+ else
44
+ connection_class = BaseConnection.connection_for_nodes(collection)
45
+ @connection = connection_class.new(collection, {}, parent: parent, context: context)
46
+ edge_class ||= Relay::Edge
47
+ @edge = edge_class.new(item, @connection)
48
+ end
49
+
39
50
  @parent = parent
40
- @connection = connection_class.new(collection, {}, parent: parent, context: context)
41
- @edge = edge_class.new(item, @connection)
42
51
  end
43
52
  end
44
53
  end
@@ -197,23 +197,27 @@ module GraphQL
197
197
  end
198
198
 
199
199
  def build_scalar_type(scalar_type_definition, type_resolver, default_resolve:)
200
+ builder = self
200
201
  Class.new(GraphQL::Schema::Scalar) do
201
202
  graphql_name(scalar_type_definition.name)
202
203
  description(scalar_type_definition.description)
203
204
  ast_node(scalar_type_definition)
204
205
 
205
206
  if default_resolve.respond_to?(:coerce_input)
206
- def self.coerce_input(val, ctx)
207
- ctx.schema.definition_default_resolve.coerce_input(self, val, ctx)
208
- end
209
-
210
- def self.coerce_result(val, ctx)
211
- ctx.schema.definition_default_resolve.coerce_result(self, val, ctx)
212
- end
207
+ # Put these method definitions in another method to avoid retaining `type_resolve`
208
+ # from this method's bindiing
209
+ builder.build_scalar_type_coerce_method(self, :coerce_input, default_resolve)
210
+ builder.build_scalar_type_coerce_method(self, :coerce_result, default_resolve)
213
211
  end
214
212
  end
215
213
  end
216
214
 
215
+ def build_scalar_type_coerce_method(scalar_class, method_name, default_definition_resolve)
216
+ scalar_class.define_singleton_method(method_name) do |val, ctx|
217
+ default_definition_resolve.public_send(method_name, self, val, ctx)
218
+ end
219
+ end
220
+
217
221
  def build_union_type(union_type_definition, type_resolver)
218
222
  Class.new(GraphQL::Schema::Union) do
219
223
  graphql_name(union_type_definition.name)
@@ -8,6 +8,8 @@ module GraphQL
8
8
  ctx.errors << type_error
9
9
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
10
10
  raise type_error
11
+ when GraphQL::IntegerDecodingError
12
+ nil
11
13
  end
12
14
  end
13
15
  end
@@ -18,10 +18,11 @@ module GraphQL
18
18
  next_args.delete(:last)
19
19
  next_args.delete(:before)
20
20
  next_args.delete(:after)
21
- yield(object, next_args)
21
+ yield(object, next_args, arguments)
22
22
  end
23
23
 
24
24
  def after_resolve(value:, object:, arguments:, context:, memo:)
25
+ original_arguments = memo
25
26
  # rename some inputs to avoid conflicts inside the block
26
27
  maybe_lazy = value
27
28
  value = nil
@@ -37,10 +38,10 @@ module GraphQL
37
38
  # update the connection with some things that may not have been provided
38
39
  value.context ||= context
39
40
  value.parent ||= object.object
40
- value.first_value ||= arguments[:first]
41
- value.after_value ||= arguments[:after]
42
- value.last_value ||= arguments[:last]
43
- value.before_value ||= arguments[:before]
41
+ value.first_value ||= original_arguments[:first]
42
+ value.after_value ||= original_arguments[:after]
43
+ value.last_value ||= original_arguments[:last]
44
+ value.before_value ||= original_arguments[:before]
44
45
  if field.has_max_page_size? && !value.has_max_page_size_override?
45
46
  value.max_page_size = field.max_page_size
46
47
  end
@@ -50,7 +51,7 @@ module GraphQL
50
51
  value
51
52
  elsif context.schema.new_connections?
52
53
  wrappers = context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
53
- context.schema.connections.wrap(field, object.object, value, arguments, context, wrappers: wrappers)
54
+ context.schema.connections.wrap(field, object.object, value, original_arguments, context, wrappers: wrappers)
54
55
  else
55
56
  if object.is_a?(GraphQL::Schema::Object)
56
57
  object = object.object
@@ -58,7 +59,7 @@ module GraphQL
58
59
  connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(value)
59
60
  connection_class.new(
60
61
  value,
61
- arguments,
62
+ original_arguments,
62
63
  field: field,
63
64
  max_page_size: field.max_page_size,
64
65
  parent: object,
@@ -4,7 +4,7 @@ module GraphQL
4
4
  class Schema
5
5
  class Field
6
6
  class ScopeExtension < GraphQL::Schema::FieldExtension
7
- def after_resolve(value:, context:, **rest)
7
+ def after_resolve(object:, arguments:, context:, value:, memo:)
8
8
  if value.nil?
9
9
  value
10
10
  else
@@ -725,32 +725,42 @@ module GraphQL
725
725
  if @extensions.empty?
726
726
  yield(obj, args)
727
727
  else
728
- # Save these so that the originals can be re-given to `after_resolve` handlers.
729
- original_args = args
730
- original_obj = obj
731
-
732
- memos = []
733
- value = run_extensions_before_resolve(memos, obj, args, ctx) do |extended_obj, extended_args|
734
- yield(extended_obj, extended_args)
728
+ # This is a hack to get the _last_ value for extended obj and args,
729
+ # in case one of the extensions doesn't `yield`.
730
+ # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
731
+ extended = { args: args, obj: obj, memos: nil }
732
+ value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args|
733
+ yield(obj, args)
735
734
  end
736
735
 
736
+ extended_obj = extended[:obj]
737
+ extended_args = extended[:args]
738
+ memos = extended[:memos] || EMPTY_HASH
739
+
737
740
  ctx.schema.after_lazy(value) do |resolved_value|
738
- @extensions.each_with_index do |ext, idx|
741
+ idx = 0
742
+ @extensions.each do |ext|
739
743
  memo = memos[idx]
740
744
  # TODO after_lazy?
741
- resolved_value = ext.after_resolve(object: original_obj, arguments: original_args, context: ctx, value: resolved_value, memo: memo)
745
+ resolved_value = ext.after_resolve(object: extended_obj, arguments: extended_args, context: ctx, value: resolved_value, memo: memo)
746
+ idx += 1
742
747
  end
743
748
  resolved_value
744
749
  end
745
750
  end
746
751
  end
747
752
 
748
- def run_extensions_before_resolve(memos, obj, args, ctx, idx: 0)
753
+ def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
749
754
  extension = @extensions[idx]
750
755
  if extension
751
756
  extension.resolve(object: obj, arguments: args, context: ctx) do |extended_obj, extended_args, memo|
752
- memos << memo
753
- run_extensions_before_resolve(memos, extended_obj, extended_args, ctx, idx: idx + 1) { |o, a| yield(o, a) }
757
+ if memo
758
+ memos = extended[:memos] ||= {}
759
+ memos[idx] = memo
760
+ end
761
+ extended[:obj] = extended_obj
762
+ extended[:args] = extended_args
763
+ run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) }
754
764
  end
755
765
  else
756
766
  yield(obj, args)