graphql 2.5.23 → 2.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/query_complexity.rb +29 -13
  3. data/lib/graphql/analysis.rb +20 -13
  4. data/lib/graphql/backtrace/table.rb +10 -1
  5. data/lib/graphql/current.rb +7 -1
  6. data/lib/graphql/dataloader.rb +1 -1
  7. data/lib/graphql/execution/directive_checks.rb +2 -0
  8. data/lib/graphql/execution/field_resolve_step.rb +744 -0
  9. data/lib/graphql/execution/finalize.rb +230 -0
  10. data/lib/graphql/execution/input_values.rb +333 -0
  11. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
  12. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  13. data/lib/graphql/execution/interpreter/runtime.rb +36 -15
  14. data/lib/graphql/execution/load_argument_step.rb +102 -0
  15. data/lib/graphql/execution/next.rb +42 -16
  16. data/lib/graphql/execution/prepare_object_step.rb +147 -0
  17. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  18. data/lib/graphql/execution/runner.rb +445 -0
  19. data/lib/graphql/execution/selections_step.rb +91 -0
  20. data/lib/graphql/execution.rb +10 -3
  21. data/lib/graphql/execution_error.rb +7 -13
  22. data/lib/graphql/introspection/entry_points.rb +2 -2
  23. data/lib/graphql/introspection/schema_type.rb +6 -2
  24. data/lib/graphql/language/lexer.rb +12 -8
  25. data/lib/graphql/language/parser.rb +1 -1
  26. data/lib/graphql/language.rb +8 -2
  27. data/lib/graphql/pagination/connections.rb +1 -3
  28. data/lib/graphql/query/context.rb +6 -0
  29. data/lib/graphql/query/partial.rb +18 -3
  30. data/lib/graphql/query.rb +12 -3
  31. data/lib/graphql/runtime_error.rb +6 -0
  32. data/lib/graphql/schema/argument.rb +3 -3
  33. data/lib/graphql/schema/build_from_definition.rb +10 -0
  34. data/lib/graphql/schema/directive/feature.rb +4 -0
  35. data/lib/graphql/schema/directive/transform.rb +20 -0
  36. data/lib/graphql/schema/directive.rb +23 -9
  37. data/lib/graphql/schema/field/connection_extension.rb +2 -15
  38. data/lib/graphql/schema/field/scope_extension.rb +0 -4
  39. data/lib/graphql/schema/field.rb +20 -20
  40. data/lib/graphql/schema/field_extension.rb +11 -41
  41. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  42. data/lib/graphql/schema/input_object.rb +4 -0
  43. data/lib/graphql/schema/interface.rb +26 -0
  44. data/lib/graphql/schema/introspection_system.rb +6 -21
  45. data/lib/graphql/schema/list.rb +4 -0
  46. data/lib/graphql/schema/member/base_dsl_methods.rb +0 -10
  47. data/lib/graphql/schema/printer.rb +1 -1
  48. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  49. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  50. data/lib/graphql/schema/resolver.rb +30 -14
  51. data/lib/graphql/schema/subscription.rb +53 -8
  52. data/lib/graphql/schema/timeout.rb +2 -2
  53. data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
  54. data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
  55. data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
  56. data/lib/graphql/schema/validator/format_validator.rb +3 -3
  57. data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
  58. data/lib/graphql/schema/validator/length_validator.rb +6 -6
  59. data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
  60. data/lib/graphql/schema/validator/required_validator.rb +6 -4
  61. data/lib/graphql/schema/validator.rb +9 -0
  62. data/lib/graphql/schema/visibility/profile.rb +6 -4
  63. data/lib/graphql/schema/visibility/visit.rb +1 -1
  64. data/lib/graphql/schema/visibility.rb +30 -22
  65. data/lib/graphql/schema.rb +43 -20
  66. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +31 -25
  67. data/lib/graphql/subscriptions/event.rb +0 -1
  68. data/lib/graphql/subscriptions.rb +15 -0
  69. data/lib/graphql/tracing/perfetto_trace.rb +5 -3
  70. data/lib/graphql/tracing/trace.rb +6 -0
  71. data/lib/graphql/unauthorized_error.rb +1 -1
  72. data/lib/graphql/version.rb +1 -1
  73. data/lib/graphql.rb +1 -3
  74. metadata +11 -7
  75. data/lib/graphql/execution/next/field_resolve_step.rb +0 -743
  76. data/lib/graphql/execution/next/load_argument_step.rb +0 -64
  77. data/lib/graphql/execution/next/prepare_object_step.rb +0 -129
  78. data/lib/graphql/execution/next/runner.rb +0 -411
  79. data/lib/graphql/execution/next/selections_step.rb +0 -37
@@ -47,13 +47,19 @@ module GraphQL
47
47
  def self.escape_single_quoted_newlines(query_str)
48
48
  scanner = StringScanner.new(query_str)
49
49
  inside_single_quoted_string = false
50
+ inside_triple_quoted_string = false
50
51
  new_query_str = nil
51
52
  while !scanner.eos?
52
- if scanner.skip(/(?:\\"|[^"\n\r]|""")+/m)
53
+ if scanner.skip('"""')
54
+ inside_triple_quoted_string = !inside_triple_quoted_string
55
+ new_query_str && (new_query_str << scanner.matched)
56
+ elsif scanner.skip(/(?:\\"|[^"\n\r])+/m)
53
57
  new_query_str && (new_query_str << scanner.matched)
54
58
  elsif scanner.skip('"')
55
59
  new_query_str && (new_query_str << '"')
56
- inside_single_quoted_string = !inside_single_quoted_string
60
+ if !inside_triple_quoted_string
61
+ inside_single_quoted_string = !inside_single_quoted_string
62
+ end
57
63
  elsif scanner.skip("\n")
58
64
  if inside_single_quoted_string
59
65
  new_query_str ||= query_str[0, scanner.pos - 1]
@@ -85,9 +85,7 @@ module GraphQL
85
85
 
86
86
  def populate_connection(field, object, value, original_arguments, context)
87
87
  if value.is_a? GraphQL::ExecutionError
88
- # This isn't even going to work because context doesn't have ast_node anymore
89
- context.add_error(value)
90
- nil
88
+ raise value
91
89
  elsif value.nil?
92
90
  nil
93
91
  elsif value.is_a?(GraphQL::Pagination::Connection)
@@ -126,6 +126,12 @@ module GraphQL
126
126
  nil
127
127
  end
128
128
 
129
+ # @param value [Object] Any object to be inserted directly into the final response
130
+ # @return [GraphQL::Execution::Interpreter::RawValue] Return this from the field
131
+ def raw_value(value)
132
+ GraphQL::Execution::Interpreter::RawValue.new(value)
133
+ end
134
+
129
135
  # @example Print the GraphQL backtrace during field resolution
130
136
  # puts ctx.backtrace
131
137
  #
@@ -32,6 +32,7 @@ module GraphQL
32
32
  @multiplex = nil
33
33
  @result_values = nil
34
34
  @result = nil
35
+ @finalizers = @top_level_finalizers = nil
35
36
 
36
37
  if fragment_node
37
38
  @ast_nodes = [fragment_node]
@@ -51,6 +52,10 @@ module GraphQL
51
52
  @leaf
52
53
  end
53
54
 
55
+ def root_value
56
+ object
57
+ end
58
+
54
59
  attr_reader :context, :query, :ast_nodes, :root_type, :object, :field_definition, :path, :schema
55
60
 
56
61
  attr_accessor :multiplex, :result_values
@@ -90,10 +95,22 @@ module GraphQL
90
95
  @query.fragments
91
96
  end
92
97
 
98
+ def validate
99
+ @query.validate
100
+ end
101
+
93
102
  def valid?
94
103
  @query.valid?
95
104
  end
96
105
 
106
+ def query?
107
+ true
108
+ end
109
+
110
+ def run_partials(...)
111
+ @query.run_partials(...)
112
+ end
113
+
97
114
  def analyzers
98
115
  EmptyObjects::EMPTY_ARRAY
99
116
  end
@@ -107,7 +124,7 @@ module GraphQL
107
124
  end
108
125
 
109
126
  def selected_operation
110
- ast_nodes.first
127
+ Language::Nodes::OperationDefinition.new(selections: ast_nodes.flat_map(&:selections))
111
128
  end
112
129
 
113
130
  def static_errors
@@ -123,7 +140,6 @@ module GraphQL
123
140
  def set_type_info_from_path
124
141
  selections = [@query.selected_operation]
125
142
  type = @query.root_type
126
- parent_type = nil
127
143
  field_defn = nil
128
144
 
129
145
  @path.each do |name_in_doc|
@@ -162,7 +178,6 @@ module GraphQL
162
178
  end
163
179
  field_name = next_selections.first.name
164
180
  field_defn = @schema.get_field(type, field_name, @query.context) || raise("Invariant: no field called #{field_name} on #{type.graphql_name}")
165
- parent_type = type
166
181
  type = field_defn.type
167
182
  if type.non_null?
168
183
  type = type.of_type
data/lib/graphql/query.rb CHANGED
@@ -45,8 +45,8 @@ module GraphQL
45
45
  end
46
46
 
47
47
  # @api private
48
- def handle_or_reraise(err)
49
- @schema.handle_or_reraise(context, err)
48
+ def handle_or_reraise(err, **kwargs)
49
+ @schema.handle_or_reraise(context, err, **kwargs)
50
50
  end
51
51
  end
52
52
 
@@ -159,6 +159,7 @@ module GraphQL
159
159
  @root_value = root_value
160
160
  @fragments = nil
161
161
  @operations = nil
162
+ @finalizers = @top_level_finalizers = nil
162
163
  @validate = validate
163
164
  self.static_validator = static_validator if static_validator
164
165
  context_tracers = (context ? context.fetch(:tracers, []) : [])
@@ -262,6 +263,10 @@ module GraphQL
262
263
  with_prepared_ast { @operations }
263
264
  end
264
265
 
266
+ def path
267
+ EmptyObjects::EMPTY_ARRAY
268
+ end
269
+
265
270
  # Run subtree partials of this query and return their results.
266
271
  # Each partial is identified with a `path:` and `object:`
267
272
  # where the path references a field in the AST and the object will be treated
@@ -271,7 +276,11 @@ module GraphQL
271
276
  # @return [Array<GraphQL::Query::Result>]
272
277
  def run_partials(partials_hashes)
273
278
  partials = partials_hashes.map { |partial_options| Partial.new(query: self, **partial_options) }
274
- Execution::Interpreter.run_all(@schema, partials, context: @context)
279
+ if context[:__graphql_execute_next]
280
+ Execution::Next.run_all(@schema, partials, context: @context)
281
+ else
282
+ Execution::Interpreter.run_all(@schema, partials, context: @context)
283
+ end
275
284
  end
276
285
 
277
286
  # Get the result for this query, executing it once
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class RuntimeError < Error
4
+ include GraphQL::Execution::Finalizer
5
+ end
6
+ end
@@ -94,7 +94,7 @@ module GraphQL
94
94
  end
95
95
 
96
96
  if required == :nullable
97
- self.owner.validates(required: { argument: arg_name })
97
+ self.owner.validates(required: { argument: @keyword })
98
98
  end
99
99
 
100
100
  if definition_block
@@ -165,8 +165,8 @@ module GraphQL
165
165
  true
166
166
  end
167
167
 
168
- def authorizes?(_context)
169
- self.method(:authorized?).owner != GraphQL::Schema::Argument
168
+ def authorizes?(context)
169
+ self.method(:authorized?).owner != GraphQL::Schema::Argument || type.unwrap.authorizes?(context)
170
170
  end
171
171
 
172
172
  def authorized?(obj, value, ctx)
@@ -99,6 +99,16 @@ module GraphQL
99
99
  # It's possible that this was already loaded by the directives
100
100
  prev_type = types[definition.name]
101
101
  if prev_type.nil? || prev_type.is_a?(Schema::LateBoundType)
102
+ if definition.is_a?(GraphQL::Language::Nodes::ObjectTypeDefinition) || definition.is_a?(Language::Nodes::InterfaceTypeDefinition)
103
+ interface_names = definition.interfaces.map(&:name)
104
+ transitive_names = interface_names.map { |n| document.definitions.find { |d| d.respond_to?(:name) && d.name == n }&.interfaces&.map(&:name) }
105
+ transitive_names.flatten!
106
+ transitive_names.compact!
107
+ if !(missing_transitive_interfaces = transitive_names - interface_names).empty?
108
+ raise GraphQL::Schema::InvalidDocumentError, "type #{definition.name} is missing one or more transitive interface names: #{missing_transitive_interfaces.join(", ")}. Add them to the type's `implements` list and try again."
109
+ end
110
+ end
111
+
102
112
  types[definition.name] = build_definition_from_node(definition, type_resolver, default_resolve, base_types)
103
113
  end
104
114
  end
@@ -60,6 +60,10 @@ module GraphQL
60
60
  def self.enabled?(flag_name, object, context)
61
61
  raise GraphQL::RequiredImplementationMissingError, "Implement `.enabled?(flag_name, object, context)` to return true or false for the feature flag (#{flag_name.inspect})"
62
62
  end
63
+
64
+ def self.resolve_field(...); end
65
+ def self.resolve_fragment_spread(...); end
66
+ def self.resolve_inline_fragment(...); end
63
67
  end
64
68
  end
65
69
  end
@@ -54,6 +54,26 @@ module GraphQL
54
54
  nil
55
55
  end
56
56
  end
57
+
58
+ def self.resolve_field(ast_nodes, parent_type, field_defn, objects, arguments, context)
59
+ transform_name = arguments[:by]
60
+ if TRANSFORMS.include?(transform_name)
61
+ Transformer.new(transform_name)
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+ class Transformer
68
+ include Execution::PostProcessor
69
+ def initialize(transform)
70
+ @transform = transform
71
+ end
72
+ def after_resolve(field_results)
73
+ field_results.map! { |r| r.respond_to?(@transform) ? r.public_send(@transform) : r }
74
+ field_results
75
+ end
76
+ end
57
77
  end
58
78
  end
59
79
  end
@@ -31,17 +31,25 @@ module GraphQL
31
31
 
32
32
  def locations(*new_locations)
33
33
  if !new_locations.empty?
34
+ is_runtime = false
34
35
  new_locations.each do |new_loc|
35
- if !LOCATIONS.include?(new_loc.to_sym)
36
+ loc_sym = new_loc.to_sym
37
+ if !LOCATIONS.include?(loc_sym)
36
38
  raise ArgumentError, "#{self} (#{self.graphql_name}) has an invalid directive location: `locations #{new_loc}` "
37
39
  end
40
+ is_runtime ||= RUNTIME_LOCATIONS.include?(loc_sym)
38
41
  end
39
42
  @locations = new_locations
43
+ @is_runtime = is_runtime
40
44
  else
41
45
  @locations ||= (superclass.respond_to?(:locations) ? superclass.locations : [])
42
46
  end
43
47
  end
44
48
 
49
+ def runtime?
50
+ @is_runtime
51
+ end
52
+
45
53
  def default_directive(new_default_directive = nil)
46
54
  if new_default_directive != nil
47
55
  @default_directive = new_default_directive
@@ -104,8 +112,12 @@ module GraphQL
104
112
 
105
113
  def inherited(subclass)
106
114
  super
115
+ parent_class = self
107
116
  subclass.class_exec do
108
117
  @default_graphql_name ||= nil
118
+ @locations = parent_class.locations
119
+ @is_runtime = parent_class.runtime?
120
+ @repeatable = false
109
121
  end
110
122
  end
111
123
  end
@@ -177,13 +189,16 @@ module GraphQL
177
189
  end
178
190
 
179
191
  LOCATIONS = [
180
- QUERY = :QUERY,
181
- MUTATION = :MUTATION,
182
- SUBSCRIPTION = :SUBSCRIPTION,
183
- FIELD = :FIELD,
184
- FRAGMENT_DEFINITION = :FRAGMENT_DEFINITION,
185
- FRAGMENT_SPREAD = :FRAGMENT_SPREAD,
186
- INLINE_FRAGMENT = :INLINE_FRAGMENT,
192
+ *(RUNTIME_LOCATIONS = [
193
+ QUERY = :QUERY,
194
+ MUTATION = :MUTATION,
195
+ SUBSCRIPTION = :SUBSCRIPTION,
196
+ FIELD = :FIELD,
197
+ FRAGMENT_DEFINITION = :FRAGMENT_DEFINITION,
198
+ FRAGMENT_SPREAD = :FRAGMENT_SPREAD,
199
+ INLINE_FRAGMENT = :INLINE_FRAGMENT,
200
+ VARIABLE_DEFINITION = :VARIABLE_DEFINITION,
201
+ ]),
187
202
  SCHEMA = :SCHEMA,
188
203
  SCALAR = :SCALAR,
189
204
  OBJECT = :OBJECT,
@@ -195,7 +210,6 @@ module GraphQL
195
210
  ENUM_VALUE = :ENUM_VALUE,
196
211
  INPUT_OBJECT = :INPUT_OBJECT,
197
212
  INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION,
198
- VARIABLE_DEFINITION = :VARIABLE_DEFINITION,
199
213
  ]
200
214
 
201
215
  DEFAULT_DEPRECATION_REASON = 'No longer supported'
@@ -12,22 +12,13 @@ module GraphQL
12
12
  end
13
13
 
14
14
  # Remove pagination args before passing it to a user method
15
- def resolve(object:, arguments:, context:)
15
+ def resolve(object: nil, objects: nil, arguments:, context:)
16
16
  next_args = arguments.dup
17
17
  next_args.delete(:first)
18
18
  next_args.delete(:last)
19
19
  next_args.delete(:before)
20
20
  next_args.delete(:after)
21
- yield(object, next_args, arguments)
22
- end
23
-
24
- def resolve_next(objects:, arguments:, context:)
25
- next_args = arguments.dup
26
- next_args.delete(:first)
27
- next_args.delete(:last)
28
- next_args.delete(:before)
29
- next_args.delete(:after)
30
- yield(objects, next_args, arguments)
21
+ yield(object || objects, next_args, arguments)
31
22
  end
32
23
 
33
24
  def after_resolve(value:, object:, arguments:, context:, memo:)
@@ -36,10 +27,6 @@ module GraphQL
36
27
  context.schema.connections.populate_connection(field, object.object, resolved_value, original_arguments, context)
37
28
  end
38
29
  end
39
-
40
- def after_resolve_next(**kwargs)
41
- raise "This should never be called -- it's hardcoded in execution instead."
42
- end
43
30
  end
44
31
  end
45
32
  end
@@ -28,10 +28,6 @@ module GraphQL
28
28
  value
29
29
  end
30
30
  end
31
-
32
- def after_resolve_next(**kwargs)
33
- raise "This should never be called -- it's hardcoded in execution instead."
34
- end
35
31
  end
36
32
  end
37
33
  end
@@ -270,32 +270,32 @@ module GraphQL
270
270
  @resolver_method = (resolver_method || name_s).to_sym
271
271
 
272
272
  if resolve_static
273
- @execution_next_mode = :resolve_static
274
- @execution_next_mode_key = resolve_static == true ? @method_sym : resolve_static
273
+ @execution_mode = :resolve_static
274
+ @execution_mode_key = resolve_static == true ? @method_sym : resolve_static
275
275
  elsif resolve_batch
276
- @execution_next_mode = :resolve_batch
277
- @execution_next_mode_key = resolve_batch == true ? @method_sym : resolve_batch
276
+ @execution_mode = :resolve_batch
277
+ @execution_mode_key = resolve_batch == true ? @method_sym : resolve_batch
278
278
  elsif resolve_each
279
- @execution_next_mode = :resolve_each
280
- @execution_next_mode_key = resolve_each == true ? @method_sym : resolve_each
279
+ @execution_mode = :resolve_each
280
+ @execution_mode_key = resolve_each == true ? @method_sym : resolve_each
281
281
  elsif hash_key
282
- @execution_next_mode = :hash_key
283
- @execution_next_mode_key = hash_key
282
+ @execution_mode = :hash_key
283
+ @execution_mode_key = hash_key
284
284
  elsif dig
285
- @execution_next_mode = :dig
286
- @execution_next_mode_key = dig
285
+ @execution_mode = :dig
286
+ @execution_mode_key = dig
287
287
  elsif resolver_class
288
- @execution_next_mode = :resolver_class
289
- @execution_next_mode_key = resolver_class
288
+ @execution_mode = :resolver_class
289
+ @execution_mode_key = resolver_class
290
290
  elsif resolve_legacy_instance_method
291
- @execution_next_mode = :resolve_legacy_instance_method
292
- @execution_next_mode_key = resolve_legacy_instance_method == true ? @method_sym : resolve_legacy_instance_method
291
+ @execution_mode = :resolve_legacy_instance_method
292
+ @execution_mode_key = resolve_legacy_instance_method == true ? @method_sym : resolve_legacy_instance_method
293
293
  elsif dataload
294
- @execution_next_mode = :dataload
295
- @execution_next_mode_key = dataload
294
+ @execution_mode = :dataload
295
+ @execution_mode_key = dataload
296
296
  else
297
- @execution_next_mode = :direct_send
298
- @execution_next_mode_key = @method_sym
297
+ @execution_mode = :direct_send
298
+ @execution_mode_key = @method_sym
299
299
  end
300
300
 
301
301
  @complexity = complexity
@@ -369,7 +369,7 @@ module GraphQL
369
369
  end
370
370
 
371
371
  # @api private
372
- attr_reader :execution_next_mode_key, :execution_next_mode
372
+ attr_reader :execution_mode_key, :execution_mode
373
373
 
374
374
  # Calls the definition block, if one was given.
375
375
  # This is deferred so that references to the return type
@@ -929,7 +929,7 @@ ERR
929
929
  def run_next_extensions_before_resolve(objs, args, ctx, extended, idx: 0, &block)
930
930
  extension = @extensions[idx]
931
931
  if extension
932
- extension.resolve_next(objects: objs, arguments: args, context: ctx) do |extended_objs, extended_args, memo|
932
+ extension.resolve(objects: objs, arguments: args, context: ctx) do |extended_objs, extended_args, memo|
933
933
  if memo
934
934
  memos = extended.memos ||= {}
935
935
  memos[idx] = memo
@@ -123,33 +123,16 @@ module GraphQL
123
123
  #
124
124
  # Whatever this method returns will be used for execution.
125
125
  #
126
- # @param object [Object] The object the field is being resolved on
126
+ # @param object [Object] The object the field is being resolved on (not passed by new execution)
127
+ # @param objects [Array<Object>] The objects the field is being resolved on (passed by new execution)
127
128
  # @param arguments [Hash] Ruby keyword arguments for resolving this field
128
129
  # @param context [Query::Context] the context for this query
129
- # @yieldparam object [Object] The object to continue resolving the field on
130
+ # @yieldparam object_or_objects [Object, Array<Object>] The object or objects (new execution) to continue resolving the field on
130
131
  # @yieldparam arguments [Hash] The keyword arguments to continue resolving with
131
132
  # @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later
132
133
  # @return [Object] The return value for this field.
133
- def resolve(object:, arguments:, context:)
134
- yield(object, arguments, nil)
135
- end
136
-
137
- # Called before batch-resolving {#field}. It should either:
138
- #
139
- # - `yield` values to continue execution; OR
140
- # - return something else to shortcut field execution.
141
- #
142
- # Whatever this method returns will be used for execution.
143
- #
144
- # @param objects [Array<Object>] The objects the field is being resolved on
145
- # @param arguments [Hash] Ruby keyword arguments for resolving this field
146
- # @param context [Query::Context] the context for this query
147
- # @yieldparam objects [Array<Object>] The objects to continue resolving the field on. Length must be the same as passed-in `objects:`
148
- # @yieldparam arguments [Hash] The keyword arguments to continue resolving with
149
- # @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later
150
- # @return [Array<Object>] The return value for this field, length matching passed-in `objects:`.
151
- def resolve_next(objects:, arguments:, context:)
152
- yield(objects, arguments, nil)
134
+ def resolve(object: nil, objects: nil, arguments:, context:)
135
+ yield(object.nil? ? objects : object, arguments, nil)
153
136
  end
154
137
 
155
138
  # Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced,
@@ -157,29 +140,16 @@ module GraphQL
157
140
  #
158
141
  # Whatever this hook returns will be used as the return value.
159
142
  #
160
- # @param object [Object] The object the field is being resolved on
143
+ # @param object [Object] The object the field is being resolved on (not passed by new execution)
144
+ # @param objects [Array<Object>] The object the field is being resolved on (passed by new execution)
161
145
  # @param arguments [Hash] Ruby keyword arguments for resolving this field
162
146
  # @param context [Query::Context] the context for this query
163
- # @param value [Object] Whatever the field previously returned
147
+ # @param value [Object] Whatever the field previously returned (not passed by new execution)
148
+ # @param values [Array<Object>] Whatever the field previously returned (passed by new execution)
164
149
  # @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one
165
150
  # @return [Object] The return value for this field.
166
- def after_resolve(object:, arguments:, context:, value:, memo:)
167
- value
168
- end
169
-
170
- # Called after {#field} was batch-resolved, and after any lazy values (like `Promise`s) were synced,
171
- # but before the value was added to the GraphQL response.
172
- #
173
- # Whatever this hook returns will be used as the return value.
174
- #
175
- # @param objects [Array<Object>] The objects the field is being resolved on
176
- # @param arguments [Hash] Ruby keyword arguments for resolving this field
177
- # @param context [Query::Context] the context for this query
178
- # @param values [Array<Object>] Whatever the field returned, one for each of `objects`
179
- # @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one
180
- # @return [Array<Object>] The return values for this field, length matching `objects:`.
181
- def after_resolve_next(objects:, arguments:, context:, values:, memo:)
182
- values
151
+ def after_resolve(object: nil, objects: nil, arguments:, context:, values: nil, value: nil, memo:)
152
+ value.nil? ? values : value
183
153
  end
184
154
  end
185
155
  end
@@ -4,10 +4,30 @@ module GraphQL
4
4
  class Schema
5
5
  module HasSingleInputArgument
6
6
  def resolve_with_support(**inputs)
7
- if inputs[:input].is_a?(InputObject)
8
- input = inputs[:input].to_kwargs
7
+ input_kwargs = flatten_arguments(inputs)
8
+ if !input_kwargs.empty?
9
+ super(**input_kwargs)
10
+ else
11
+ super()
12
+ end
13
+ end
14
+
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ end
18
+
19
+ def call
20
+ @prepared_arguments = flatten_arguments(@prepared_arguments)
21
+ super
22
+ end
23
+
24
+ private
25
+
26
+ def flatten_arguments(inputs)
27
+ input = if inputs[:input].is_a?(InputObject)
28
+ inputs[:input].to_kwargs
9
29
  else
10
- input = inputs[:input]
30
+ inputs[:input]
11
31
  end
12
32
 
13
33
  new_extras = field ? field.extras : []
@@ -24,7 +44,6 @@ module GraphQL
24
44
  end
25
45
 
26
46
  if input
27
- # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
28
47
  input_kwargs = input.to_h
29
48
  else
30
49
  # Relay Classic Mutations with no `argument`s
@@ -32,15 +51,7 @@ module GraphQL
32
51
  input_kwargs = {}
33
52
  end
34
53
 
35
- if !input_kwargs.empty?
36
- super(**input_kwargs)
37
- else
38
- super()
39
- end
40
- end
41
-
42
- def self.included(base)
43
- base.extend(ClassMethods)
54
+ input_kwargs
44
55
  end
45
56
 
46
57
  module ClassMethods
@@ -113,6 +113,10 @@ module GraphQL
113
113
  end
114
114
 
115
115
  class << self
116
+ def authorizes?(ctx)
117
+ self.method(:authorized?).owner != GraphQL::Schema::InputObject
118
+ end
119
+
116
120
  def authorized?(obj, value, ctx)
117
121
  # Authorize each argument (but this doesn't apply if `prepare` is implemented):
118
122
  if value.respond_to?(:key?)
@@ -33,6 +33,26 @@ module GraphQL
33
33
  self::DefinitionMethods.module_exec(&block)
34
34
  end
35
35
 
36
+ # Instance methods defined in this block will become class methods on objects that implement this interface.
37
+ # Use it to implement `resolve_each:`, `resolve_batch:`, and `resolve_static:` fields.
38
+ # @example
39
+ # field :thing, String, resolve_static: true
40
+ #
41
+ # resolver_methods do
42
+ # def thing
43
+ # Somehow.get.thing
44
+ # end
45
+ # end
46
+ def resolver_methods(&block)
47
+ if !defined?(@_resolver_methods)
48
+ resolver_methods_module = Module.new
49
+ @_resolver_methods = resolver_methods_module
50
+ const_set(:ResolverMethods, resolver_methods_module)
51
+ extend(self::ResolverMethods)
52
+ end
53
+ self::ResolverMethods.module_exec(&block)
54
+ end
55
+
36
56
  # @see {Schema::Warden} hides interfaces without visible implementations
37
57
  def visible?(context)
38
58
  true
@@ -79,6 +99,12 @@ module GraphQL
79
99
  if !backtrace_line
80
100
  raise "Attach interfaces using `implements(#{self})`, not `include(#{self})`"
81
101
  end
102
+
103
+ child_class.ancestors.reverse_each do |ancestor|
104
+ if ancestor != child_class && ancestor <= GraphQL::Schema::Interface && ancestor.const_defined?(:ResolverMethods, false)
105
+ child_class.extend(ancestor::ResolverMethods)
106
+ end
107
+ end
82
108
  end
83
109
 
84
110
  super
@@ -105,11 +105,13 @@ module GraphQL
105
105
  end
106
106
 
107
107
  def load_constant(class_name)
108
- const = @custom_namespace.const_get(class_name)
108
+ const = begin
109
+ @custom_namespace.const_get(class_name)
110
+ rescue NameError
111
+ # Dup the built-in so that the cached fields aren't shared
112
+ @built_in_namespace.const_get(class_name)
113
+ end
109
114
  dup_type_class(const)
110
- rescue NameError
111
- # Dup the built-in so that the cached fields aren't shared
112
- dup_type_class(@built_in_namespace.const_get(class_name))
113
115
  end
114
116
 
115
117
  def get_fields_from_class(class_sym:)
@@ -133,23 +135,6 @@ module GraphQL
133
135
  end
134
136
  end
135
137
  end
136
-
137
- class PerFieldProxyResolve
138
- def initialize(object_class:, inner_resolve:)
139
- @object_class = object_class
140
- @inner_resolve = inner_resolve
141
- end
142
-
143
- def call(obj, args, ctx)
144
- query_ctx = ctx.query.context
145
- # Remove the QueryType wrapper
146
- if obj.is_a?(GraphQL::Schema::Object)
147
- obj = obj.object
148
- end
149
- wrapped_object = @object_class.wrap(obj, query_ctx)
150
- @inner_resolve.call(wrapped_object, args, ctx)
151
- end
152
- end
153
138
  end
154
139
  end
155
140
  end
@@ -22,6 +22,10 @@ module GraphQL
22
22
  @type_signature ||= -"[#{@of_type.to_type_signature}]"
23
23
  end
24
24
 
25
+ def authorizes?(ctx)
26
+ of_type.authorizes?(ctx)
27
+ end
28
+
25
29
  # This is for introspection, where it's expected the name will be `null`
26
30
  def graphql_name
27
31
  nil