graphql 1.11.5 → 1.11.9

Sign up to get free protection for your applications and to get access to all the features.
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)