graphql 1.12.11 → 1.12.15

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e46a535c108ff0ae582fcfa598b21cda8faad15142629b1cdae772f3d24cd7e
4
- data.tar.gz: 3185ec249d624c6b23920d9d9fb47167ac031b7645053ff705e3c04acc0fa571
3
+ metadata.gz: f858de6eaac686676c159e87ae2f200b6c69b9b6c1da8e7459595b49f4f44e7a
4
+ data.tar.gz: afd8686f7897896919bef91076285f0763e49076759036352bd212d41c1bb47f
5
5
  SHA512:
6
- metadata.gz: 989c1074a6de63a31002d2bd92da719e1b08682d3eefe361add671a75c1991173b13cc6516f8ede8ff5bcf7499935e514cd8d7d57a2940e85160bc427538e99c
7
- data.tar.gz: cb40ba585e721d28856951312755ef84bdd698a7299b1a383dd4f9db77d8b137e8d21ee9a8784b14780b4230a5a5325520d0ceadf90f6bd055781cc7159257b5
6
+ metadata.gz: 6774f423bdf61cf07d842ca00f517068139a86da2d6e0d6b7e15e7970aaf5ce1b07e58d78c0a13a1ccf0a8db87c48d4061e151ca42bc21731e26958ef96ad922
7
+ data.tar.gz: 31ca7b94bacf8df5a8c27c0e3c2f831362970e49c65c8b0a284095c1bd392af26347cecc78e60dc91ff4581a9a3d55dc08204eb0230ed641f2907e009a4f7f3e
@@ -7,6 +7,7 @@ module GraphQL
7
7
  super
8
8
  @used_fields = Set.new
9
9
  @used_deprecated_fields = Set.new
10
+ @used_deprecated_arguments = Set.new
10
11
  end
11
12
 
12
13
  def on_leave_field(node, parent, visitor)
@@ -14,14 +15,36 @@ module GraphQL
14
15
  field = "#{visitor.parent_type_definition.graphql_name}.#{field_defn.graphql_name}"
15
16
  @used_fields << field
16
17
  @used_deprecated_fields << field if field_defn.deprecation_reason
18
+
19
+ extract_deprecated_arguments(visitor.query.arguments_for(node, visitor.field_definition).argument_values)
17
20
  end
18
21
 
19
22
  def result
20
23
  {
21
24
  used_fields: @used_fields.to_a,
22
- used_deprecated_fields: @used_deprecated_fields.to_a
25
+ used_deprecated_fields: @used_deprecated_fields.to_a,
26
+ used_deprecated_arguments: @used_deprecated_arguments.to_a,
23
27
  }
24
28
  end
29
+
30
+ private
31
+
32
+ def extract_deprecated_arguments(argument_values)
33
+ argument_values.each_pair do |_argument_name, argument|
34
+ if argument.definition.deprecation_reason
35
+ @used_deprecated_arguments << argument.definition.path
36
+ end
37
+
38
+ if argument.definition.type.kind.input_object?
39
+ extract_deprecated_arguments(argument.value.arguments.argument_values)
40
+ elsif argument.definition.type.list?
41
+ argument
42
+ .value
43
+ .select { |value| value.respond_to?(:arguments) }
44
+ .each { |value| extract_deprecated_arguments(value.arguments.argument_values) }
45
+ end
46
+ end
47
+ end
25
48
  end
26
49
  end
27
50
  end
@@ -144,7 +144,7 @@ module GraphQL
144
144
  end
145
145
 
146
146
  def value_at(runtime, path)
147
- response = runtime.response
147
+ response = runtime.final_result
148
148
  path.each do |key|
149
149
  if response && (response = response[key])
150
150
  next
@@ -15,14 +15,17 @@ module GraphQL
15
15
  # No query context yet
16
16
  nil
17
17
  when "validate", "analyze_query", "execute_query", "execute_query_lazy"
18
- query = metadata[:query] || metadata[:queries].first
19
18
  push_key = []
20
- push_data = query
21
- multiplex = query.multiplex
19
+ if (query = metadata[:query]) || ((queries = metadata[:queries]) && (query = queries.first))
20
+ push_data = query
21
+ multiplex = query.multiplex
22
+ elsif (multiplex = metadata[:multiplex])
23
+ push_data = multiplex.queries.first
24
+ end
22
25
  when "execute_field", "execute_field_lazy"
23
26
  query = metadata[:query] || raise(ArgumentError, "Add `legacy: true` to use GraphQL::Backtrace without the interpreter runtime.")
24
27
  multiplex = query.multiplex
25
- push_key = metadata[:path].reject { |i| i.is_a?(Integer) }
28
+ push_key = metadata[:path]
26
29
  parent_frame = multiplex.context[:graphql_backtrace_contexts][push_key[0..-2]]
27
30
 
28
31
  if parent_frame.is_a?(GraphQL::Query)
@@ -10,6 +10,7 @@ module GraphQL
10
10
  # These are all no-ops because code was
11
11
  # executed sychronously.
12
12
  def run; end
13
+ def run_isolated; yield; end
13
14
  def yield; end
14
15
 
15
16
  def append_job
@@ -77,6 +77,21 @@ module GraphQL
77
77
  nil
78
78
  end
79
79
 
80
+ # Use a self-contained queue for the work in the block.
81
+ def run_isolated
82
+ prev_queue = @pending_jobs
83
+ @pending_jobs = []
84
+ res = nil
85
+ # Make sure the block is inside a Fiber, so it can `Fiber.yield`
86
+ append_job {
87
+ res = yield
88
+ }
89
+ run
90
+ res
91
+ ensure
92
+ @pending_jobs = prev_queue
93
+ end
94
+
80
95
  # @api private Move along, move along
81
96
  def run
82
97
  # At a high level, the algorithm is:
@@ -28,11 +28,12 @@ module GraphQL
28
28
  end
29
29
 
30
30
  def fetch(ast_node, argument_owner, parent_object)
31
- @storage[ast_node][argument_owner][parent_object]
32
31
  # If any jobs were enqueued, run them now,
33
32
  # since this might have been called outside of execution.
34
33
  # (The jobs are responsible for updating `result` in-place.)
35
- @dataloader.run
34
+ @dataloader.run_isolated do
35
+ @storage[ast_node][argument_owner][parent_object]
36
+ end
36
37
  # Ack, the _hash_ is updated, but the key is eventually
37
38
  # overridden with an immutable arguments instance.
38
39
  # The first call queues up the job,
@@ -34,7 +34,10 @@ module GraphQL
34
34
  next_results = []
35
35
  while results.any?
36
36
  result_value = results.shift
37
- if result_value.is_a?(Hash)
37
+ if result_value.is_a?(Runtime::GraphQLResultHash) || result_value.is_a?(Hash)
38
+ results.concat(result_value.values)
39
+ next
40
+ elsif result_value.is_a?(Runtime::GraphQLResultArray)
38
41
  results.concat(result_value.values)
39
42
  next
40
43
  elsif result_value.is_a?(Array)
@@ -46,7 +49,8 @@ module GraphQL
46
49
  # Since this field returned another lazy,
47
50
  # add it to the same queue
48
51
  results << loaded_value
49
- elsif loaded_value.is_a?(Hash) || loaded_value.is_a?(Array)
52
+ elsif loaded_value.is_a?(Runtime::GraphQLResultHash) || loaded_value.is_a?(Runtime::GraphQLResultArray) ||
53
+ loaded_value.is_a?(Hash) || loaded_value.is_a?(Array)
50
54
  # Add these values in wholesale --
51
55
  # they might be modified by later work in the dataloader.
52
56
  next_results << loaded_value
@@ -10,8 +10,6 @@ module GraphQL
10
10
  class Runtime
11
11
 
12
12
  module GraphQLResult
13
- # These methods are private concerns of GraphQL-Ruby,
14
- # they aren't guaranteed to continue working in the future.
15
13
  attr_accessor :graphql_dead, :graphql_parent, :graphql_result_name
16
14
  # Although these are used by only one of the Result classes,
17
15
  # it's handy to have the methods implemented on both (even though they just return `nil`)
@@ -20,14 +18,116 @@ module GraphQL
20
18
  attr_accessor :graphql_non_null_field_names
21
19
  # @return [nil, true]
22
20
  attr_accessor :graphql_non_null_list_items
21
+
22
+ # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
23
+ attr_accessor :graphql_result_data
23
24
  end
24
25
 
25
- class GraphQLResultHash < Hash
26
+ class GraphQLResultHash
27
+ def initialize
28
+ # Jump through some hoops to avoid creating this duplicate hash if at all possible.
29
+ @graphql_metadata = nil
30
+ @graphql_result_data = {}
31
+ end
32
+
26
33
  include GraphQLResult
34
+
35
+ attr_accessor :graphql_merged_into
36
+
37
+ def []=(key, value)
38
+ # This is a hack.
39
+ # Basically, this object is merged into the root-level result at some point.
40
+ # But the problem is, some lazies are created whose closures retain reference to _this_
41
+ # object. When those lazies are resolved, they cause an update to this object.
42
+ #
43
+ # In order to return a proper top-level result, we have to update that top-level result object.
44
+ # In order to return a proper partial result (eg, for a directive), we have to update this object, too.
45
+ # Yowza.
46
+ if (t = @graphql_merged_into)
47
+ t[key] = value
48
+ end
49
+
50
+ if value.respond_to?(:graphql_result_data)
51
+ @graphql_result_data[key] = value.graphql_result_data
52
+ # If we encounter some part of this response that requires metadata tracking,
53
+ # then create the metadata hash if necessary. It will be kept up-to-date after this.
54
+ (@graphql_metadata ||= @graphql_result_data.dup)[key] = value
55
+ else
56
+ @graphql_result_data[key] = value
57
+ # keep this up-to-date if it's been initialized
58
+ @graphql_metadata && @graphql_metadata[key] = value
59
+ end
60
+
61
+ value
62
+ end
63
+
64
+ def delete(key)
65
+ @graphql_metadata && @graphql_metadata.delete(key)
66
+ @graphql_result_data.delete(key)
67
+ end
68
+
69
+ def each
70
+ (@graphql_metadata || @graphql_result_data).each { |k, v| yield(k, v) }
71
+ end
72
+
73
+ def values
74
+ (@graphql_metadata || @graphql_result_data).values
75
+ end
76
+
77
+ def key?(k)
78
+ @graphql_result_data.key?(k)
79
+ end
80
+
81
+ def [](k)
82
+ (@graphql_metadata || @graphql_result_data)[k]
83
+ end
27
84
  end
28
85
 
29
- class GraphQLResultArray < Array
86
+ class GraphQLResultArray
30
87
  include GraphQLResult
88
+
89
+ def initialize
90
+ # Avoid this duplicate allocation if possible -
91
+ # but it will require some work to keep it up-to-date if it's created.
92
+ @graphql_metadata = nil
93
+ @graphql_result_data = []
94
+ end
95
+
96
+ def graphql_skip_at(index)
97
+ # Mark this index as dead. It's tricky because some indices may already be storing
98
+ # `Lazy`s. So the runtime is still holding indexes _before_ skipping,
99
+ # this object has to coordinate incoming writes to account for any already-skipped indices.
100
+ @skip_indices ||= []
101
+ @skip_indices << index
102
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < index}
103
+ delete_at_index = index - offset_by
104
+ @graphql_metadata && @graphql_metadata.delete_at(delete_at_index)
105
+ @graphql_result_data.delete_at(delete_at_index)
106
+ end
107
+
108
+ def []=(idx, value)
109
+ if @skip_indices
110
+ offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
111
+ idx -= offset_by
112
+ end
113
+ if value.respond_to?(:graphql_result_data)
114
+ @graphql_result_data[idx] = value.graphql_result_data
115
+ (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
116
+ else
117
+ @graphql_result_data[idx] = value
118
+ @graphql_metadata && @graphql_metadata[idx] = value
119
+ end
120
+
121
+ value
122
+ end
123
+
124
+ def values
125
+ (@graphql_metadata || @graphql_result_data)
126
+ end
127
+ end
128
+
129
+ class GraphQLSelectionSet < Hash
130
+ attr_accessor :graphql_directives
31
131
  end
32
132
 
33
133
  # @return [GraphQL::Query]
@@ -39,9 +139,6 @@ module GraphQL
39
139
  # @return [GraphQL::Query::Context]
40
140
  attr_reader :context
41
141
 
42
- # @return [Hash]
43
- attr_reader :response
44
-
45
142
  def initialize(query:)
46
143
  @query = query
47
144
  @dataloader = query.multiplex.dataloader
@@ -50,6 +147,14 @@ module GraphQL
50
147
  @multiplex_context = query.multiplex.context
51
148
  @interpreter_context = @context.namespace(:interpreter)
52
149
  @response = GraphQLResultHash.new
150
+ # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
151
+ @runtime_directive_names = []
152
+ noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
153
+ schema.directives.each do |name, dir_defn|
154
+ if dir_defn.method(:resolve).owner != noop_resolve_owner
155
+ @runtime_directive_names << name
156
+ end
157
+ end
53
158
  # A cache of { Class => { String => Schema::Field } }
54
159
  # Which assumes that MyObject.get_field("myField") will return the same field
55
160
  # during the lifetime of a query
@@ -58,10 +163,24 @@ module GraphQL
58
163
  @lazy_cache = {}
59
164
  end
60
165
 
166
+ def final_result
167
+ @response && @response.graphql_result_data
168
+ end
169
+
61
170
  def inspect
62
171
  "#<#{self.class.name} response=#{@response.inspect}>"
63
172
  end
64
173
 
174
+ def tap_or_each(obj_or_array)
175
+ if obj_or_array.is_a?(Array)
176
+ obj_or_array.each do |item|
177
+ yield(item, true)
178
+ end
179
+ else
180
+ yield(obj_or_array, false)
181
+ end
182
+ end
183
+
65
184
  # This _begins_ the execution. Some deferred work
66
185
  # might be stored up in lazies.
67
186
  # @return [void]
@@ -78,20 +197,40 @@ module GraphQL
78
197
  # Root .authorized? returned false.
79
198
  @response = nil
80
199
  else
81
- resolve_with_directives(object_proxy, root_operation) do # execute query level directives
200
+ resolve_with_directives(object_proxy, root_operation.directives) do # execute query level directives
82
201
  gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
83
- # Make the first fiber which will begin execution
84
- @dataloader.append_job {
85
- evaluate_selections(
86
- path,
87
- context.scoped_context,
88
- object_proxy,
89
- root_type,
90
- root_op_type == "mutation",
91
- gathered_selections,
92
- @response,
93
- )
94
- }
202
+ # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
203
+ # require isolation during execution (because of runtime directives). In that case,
204
+ # make a new, isolated result hash for writing the result into. (That isolated response
205
+ # is eventually merged back into the main response)
206
+ #
207
+ # Otherwise, `gathered_selections` is a hash of selections which can be
208
+ # directly evaluated and the results can be written right into the main response hash.
209
+ tap_or_each(gathered_selections) do |selections, is_selection_array|
210
+ if is_selection_array
211
+ selection_response = GraphQLResultHash.new
212
+ final_response = @response
213
+ else
214
+ selection_response = @response
215
+ final_response = nil
216
+ end
217
+
218
+ @dataloader.append_job {
219
+ set_all_interpreter_context(query.root_value, nil, nil, path)
220
+ resolve_with_directives(object_proxy, selections.graphql_directives) do
221
+ evaluate_selections(
222
+ path,
223
+ context.scoped_context,
224
+ object_proxy,
225
+ root_type,
226
+ root_op_type == "mutation",
227
+ selections,
228
+ selection_response,
229
+ final_response,
230
+ )
231
+ end
232
+ }
233
+ end
95
234
  end
96
235
  end
97
236
  delete_interpreter_context(:current_path)
@@ -101,15 +240,36 @@ module GraphQL
101
240
  nil
102
241
  end
103
242
 
104
- def gather_selections(owner_object, owner_type, selections, selections_by_name = {})
243
+ # @return [void]
244
+ def deep_merge_selection_result(from_result, into_result)
245
+ from_result.each do |key, value|
246
+ if !into_result.key?(key)
247
+ into_result[key] = value
248
+ else
249
+ case value
250
+ when GraphQLResultHash
251
+ deep_merge_selection_result(value, into_result[key])
252
+ else
253
+ # We have to assume that, since this passed the `fields_will_merge` selection,
254
+ # that the old and new values are the same.
255
+ # There's no special handling of arrays because currently, there's no way to split the execution
256
+ # of a list over several concurrent flows.
257
+ into_result[key] = value
258
+ end
259
+ end
260
+ end
261
+ from_result.graphql_merged_into = into_result
262
+ nil
263
+ end
264
+
265
+ def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
105
266
  selections.each do |node|
106
267
  # Skip gathering this if the directive says so
107
268
  if !directives_include?(node, owner_object, owner_type)
108
269
  next
109
270
  end
110
271
 
111
- case node
112
- when GraphQL::Language::Nodes::Field
272
+ if node.is_a?(GraphQL::Language::Nodes::Field)
113
273
  response_key = node.alias || node.name
114
274
  selections = selections_by_name[response_key]
115
275
  # if there was already a selection of this field,
@@ -125,52 +285,77 @@ module GraphQL
125
285
  # No selection was found for this field yet
126
286
  selections_by_name[response_key] = node
127
287
  end
128
- when GraphQL::Language::Nodes::InlineFragment
129
- if node.type
130
- type_defn = schema.get_type(node.type.name)
131
- # Faster than .map{}.include?()
132
- query.warden.possible_types(type_defn).each do |t|
288
+ else
289
+ # This is an InlineFragment or a FragmentSpread
290
+ if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
291
+ next_selections = GraphQLSelectionSet.new
292
+ next_selections.graphql_directives = node.directives
293
+ if selections_to_run
294
+ selections_to_run << next_selections
295
+ else
296
+ selections_to_run = []
297
+ selections_to_run << selections_by_name
298
+ selections_to_run << next_selections
299
+ end
300
+ else
301
+ next_selections = selections_by_name
302
+ end
303
+
304
+ case node
305
+ when GraphQL::Language::Nodes::InlineFragment
306
+ if node.type
307
+ type_defn = schema.get_type(node.type.name)
308
+
309
+ # Faster than .map{}.include?()
310
+ query.warden.possible_types(type_defn).each do |t|
311
+ if t == owner_type
312
+ gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
313
+ break
314
+ end
315
+ end
316
+ else
317
+ # it's an untyped fragment, definitely continue
318
+ gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
319
+ end
320
+ when GraphQL::Language::Nodes::FragmentSpread
321
+ fragment_def = query.fragments[node.name]
322
+ type_defn = schema.get_type(fragment_def.type.name)
323
+ possible_types = query.warden.possible_types(type_defn)
324
+ possible_types.each do |t|
133
325
  if t == owner_type
134
- gather_selections(owner_object, owner_type, node.selections, selections_by_name)
326
+ gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
135
327
  break
136
328
  end
137
329
  end
138
330
  else
139
- # it's an untyped fragment, definitely continue
140
- gather_selections(owner_object, owner_type, node.selections, selections_by_name)
331
+ raise "Invariant: unexpected selection class: #{node.class}"
141
332
  end
142
- when GraphQL::Language::Nodes::FragmentSpread
143
- fragment_def = query.fragments[node.name]
144
- type_defn = schema.get_type(fragment_def.type.name)
145
- possible_types = query.warden.possible_types(type_defn)
146
- possible_types.each do |t|
147
- if t == owner_type
148
- gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
149
- break
150
- end
151
- end
152
- else
153
- raise "Invariant: unexpected selection class: #{node.class}"
154
333
  end
155
334
  end
156
- selections_by_name
335
+ selections_to_run || selections_by_name
157
336
  end
158
337
 
159
338
  NO_ARGS = {}.freeze
160
339
 
161
340
  # @return [void]
162
- def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result)
341
+ def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result) # rubocop:disable Metrics/ParameterLists
163
342
  set_all_interpreter_context(owner_object, nil, nil, path)
164
343
 
344
+ finished_jobs = 0
345
+ enqueued_jobs = gathered_selections.size
165
346
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
166
347
  @dataloader.append_job {
167
348
  evaluate_selection(
168
349
  path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result
169
350
  )
351
+ finished_jobs += 1
352
+ if target_result && finished_jobs == enqueued_jobs
353
+ deep_merge_selection_result(selections_result, target_result)
354
+ end
170
355
  }
171
356
  end
172
357
 
173
- nil
358
+ selections_result
174
359
  end
175
360
 
176
361
  attr_reader :progress_path
@@ -292,12 +477,17 @@ module GraphQL
292
477
  # Optimize for the case that field is selected only once
293
478
  if field_ast_nodes.nil? || field_ast_nodes.size == 1
294
479
  next_selections = ast_node.selections
480
+ directives = ast_node.directives
295
481
  else
296
482
  next_selections = []
297
- field_ast_nodes.each { |f| next_selections.concat(f.selections) }
483
+ directives = []
484
+ field_ast_nodes.each { |f|
485
+ next_selections.concat(f.selections)
486
+ directives.concat(f.directives)
487
+ }
298
488
  end
299
489
 
300
- field_result = resolve_with_directives(object, ast_node) do
490
+ field_result = resolve_with_directives(object, directives) do
301
491
  # Actually call the field resolver and capture the result
302
492
  app_result = begin
303
493
  query.with_error_handling do
@@ -329,15 +519,7 @@ module GraphQL
329
519
  end
330
520
 
331
521
  def dead_result?(selection_result)
332
- r = selection_result
333
- while r
334
- if r.graphql_dead
335
- return true
336
- else
337
- r = r.graphql_parent
338
- end
339
- end
340
- false
522
+ selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
341
523
  end
342
524
 
343
525
  def set_result(selection_result, result_name, value)
@@ -361,9 +543,7 @@ module GraphQL
361
543
  @response = nil
362
544
  else
363
545
  set_result(parent, name_in_parent, nil)
364
- # This is odd, but it's how it used to work. Even if `parent` _would_ accept
365
- # a `nil`, it's marked dead. TODO: check the spec, is there a reason for this?
366
- parent.graphql_dead = true
546
+ set_graphql_dead(selection_result)
367
547
  end
368
548
  else
369
549
  selection_result[result_name] = value
@@ -371,6 +551,21 @@ module GraphQL
371
551
  end
372
552
  end
373
553
 
554
+ # Mark this node and any already-registered children as dead,
555
+ # so that it accepts no more writes.
556
+ def set_graphql_dead(selection_result)
557
+ case selection_result
558
+ when GraphQLResultArray
559
+ selection_result.graphql_dead = true
560
+ selection_result.values.each { |v| set_graphql_dead(v) }
561
+ when GraphQLResultHash
562
+ selection_result.graphql_dead = true
563
+ selection_result.each { |k, v| set_graphql_dead(v) }
564
+ else
565
+ # It's a scalar, no way to mark it dead.
566
+ end
567
+ end
568
+
374
569
  HALT = Object.new
375
570
  def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
376
571
  case value
@@ -390,11 +585,13 @@ module GraphQL
390
585
  # to avoid the overhead of checking three different classes
391
586
  # every time.
392
587
  if value.is_a?(GraphQL::ExecutionError)
393
- if !dead_result?(selection_result)
588
+ if selection_result.nil? || !dead_result?(selection_result)
394
589
  value.path ||= path
395
590
  value.ast_node ||= ast_node
396
591
  context.errors << value
397
- set_result(selection_result, result_name, nil)
592
+ if selection_result
593
+ set_result(selection_result, result_name, nil)
594
+ end
398
595
  end
399
596
  HALT
400
597
  elsif value.is_a?(GraphQL::UnauthorizedError)
@@ -407,6 +604,17 @@ module GraphQL
407
604
  end
408
605
  continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
409
606
  elsif GraphQL::Execution::Execute::SKIP == value
607
+ # It's possible a lazy was already written here
608
+ case selection_result
609
+ when GraphQLResultHash
610
+ selection_result.delete(result_name)
611
+ when GraphQLResultArray
612
+ selection_result.graphql_skip_at(result_name)
613
+ when nil
614
+ # this can happen with directives
615
+ else
616
+ raise "Invariant: unexpected result class #{selection_result.class} (#{selection_result.inspect})"
617
+ end
410
618
  HALT
411
619
  else
412
620
  # What could this actually _be_? Anyhow,
@@ -416,13 +624,15 @@ module GraphQL
416
624
  when Array
417
625
  # It's an array full of execution errors; add them all.
418
626
  if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
419
- if !dead_result?(selection_result)
627
+ if selection_result.nil? || !dead_result?(selection_result)
420
628
  value.each_with_index do |error, index|
421
629
  error.ast_node ||= ast_node
422
- error.path ||= path + (field.type.list? ? [index] : [])
630
+ error.path ||= path + ((field && field.type.list?) ? [index] : [])
423
631
  context.errors << error
424
632
  end
425
- set_result(selection_result, result_name, nil)
633
+ if selection_result
634
+ set_result(selection_result, result_name, nil)
635
+ end
426
636
  end
427
637
  HALT
428
638
  else
@@ -488,8 +698,39 @@ module GraphQL
488
698
  response_hash.graphql_result_name = result_name
489
699
  set_result(selection_result, result_name, response_hash)
490
700
  gathered_selections = gather_selections(continue_value, current_type, next_selections)
491
- evaluate_selections(path, context.scoped_context, continue_value, current_type, false, gathered_selections, response_hash)
492
- response_hash
701
+ # There are two possibilities for `gathered_selections`:
702
+ # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
703
+ # This case is handled below, and the result can be written right into the main `response_hash` above.
704
+ # In this case, `gathered_selections` is a hash of selections.
705
+ # 2. Some selections of this object have runtime directives that may or may not modify execution.
706
+ # That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
707
+ # eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
708
+ # (Technically, it's possible that one of those entries _doesn't_ require isolation.)
709
+ tap_or_each(gathered_selections) do |selections, is_selection_array|
710
+ if is_selection_array
711
+ this_result = GraphQLResultHash.new
712
+ this_result.graphql_parent = selection_result
713
+ this_result.graphql_result_name = result_name
714
+ final_result = response_hash
715
+ else
716
+ this_result = response_hash
717
+ final_result = nil
718
+ end
719
+ set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
720
+ resolve_with_directives(continue_value, selections.graphql_directives) do
721
+ evaluate_selections(
722
+ path,
723
+ context.scoped_context,
724
+ continue_value,
725
+ current_type,
726
+ false,
727
+ selections,
728
+ this_result,
729
+ final_result,
730
+ )
731
+ this_result
732
+ end
733
+ end
493
734
  end
494
735
  end
495
736
  when "LIST"
@@ -534,13 +775,13 @@ module GraphQL
534
775
  end
535
776
  end
536
777
 
537
- def resolve_with_directives(object, ast_node, &block)
538
- return yield if ast_node.directives.empty?
539
- run_directive(object, ast_node, 0, &block)
778
+ def resolve_with_directives(object, directives, &block)
779
+ return yield if directives.nil? || directives.empty?
780
+ run_directive(object, directives, 0, &block)
540
781
  end
541
782
 
542
- def run_directive(object, ast_node, idx, &block)
543
- dir_node = ast_node.directives[idx]
783
+ def run_directive(object, directives, idx, &block)
784
+ dir_node = directives[idx]
544
785
  if !dir_node
545
786
  yield
546
787
  else
@@ -548,9 +789,24 @@ module GraphQL
548
789
  if !dir_defn.is_a?(Class)
549
790
  dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
550
791
  end
551
- dir_args = arguments(nil, dir_defn, dir_node).keyword_arguments
552
- dir_defn.resolve(object, dir_args, context) do
553
- run_directive(object, ast_node, idx + 1, &block)
792
+ raw_dir_args = arguments(nil, dir_defn, dir_node)
793
+ dir_args = continue_value(
794
+ @context[:current_path], # path
795
+ raw_dir_args, # value
796
+ dir_defn, # parent_type
797
+ nil, # field
798
+ false, # is_non_null
799
+ dir_node, # ast_node
800
+ nil, # result_name
801
+ nil, # selection_result
802
+ )
803
+
804
+ if dir_args == HALT
805
+ nil
806
+ else
807
+ dir_defn.resolve(object, dir_args, context) do
808
+ run_directive(object, directives, idx + 1, &block)
809
+ end
554
810
  end
555
811
  end
556
812
  end
@@ -559,7 +815,7 @@ module GraphQL
559
815
  def directives_include?(node, graphql_object, parent_type)
560
816
  node.directives.each do |dir_node|
561
817
  dir_defn = schema.directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
562
- args = arguments(graphql_object, dir_defn, dir_node).keyword_arguments
818
+ args = arguments(graphql_object, dir_defn, dir_node)
563
819
  if !dir_defn.include?(graphql_object, args, context)
564
820
  return false
565
821
  end
@@ -18,7 +18,7 @@ module GraphQL
18
18
  def execute(_operation, _root_type, query)
19
19
  runtime = evaluate(query)
20
20
  sync_lazies(query: query)
21
- runtime.response
21
+ runtime.final_result
22
22
  end
23
23
 
24
24
  def self.use(schema_class)
@@ -56,7 +56,7 @@ module GraphQL
56
56
 
57
57
  def self.finish_query(query, _multiplex)
58
58
  {
59
- "data" => query.context.namespace(:interpreter)[:runtime].response
59
+ "data" => query.context.namespace(:interpreter)[:runtime].final_result
60
60
  }
61
61
  end
62
62
 
@@ -87,7 +87,7 @@ module GraphQL
87
87
  final_values = queries.map do |query|
88
88
  runtime = query.context.namespace(:interpreter)[:runtime]
89
89
  # it might not be present if the query has an error
90
- runtime ? runtime.response : nil
90
+ runtime ? runtime.final_result : nil
91
91
  end
92
92
  final_values.compact!
93
93
  tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
@@ -48,7 +48,11 @@ module GraphQL
48
48
  end
49
49
  end
50
50
 
51
- if @value.is_a?(StandardError)
51
+ # `SKIP` was made into a subclass of `GraphQL::Error` to improve runtime performance
52
+ # (fewer clauses in a hot `case` block), but now it requires special handling here.
53
+ # I think it's still worth it for the performance win, but if the number of special
54
+ # cases grows, then maybe it's worth rethinking somehow.
55
+ if @value.is_a?(StandardError) && @value != GraphQL::Execution::Execute::SKIP
52
56
  raise @value
53
57
  else
54
58
  @value
data/lib/graphql/query.rb CHANGED
@@ -270,7 +270,7 @@ module GraphQL
270
270
  # @return [String, nil] Returns nil if the query is invalid.
271
271
  def sanitized_query_string(inline_variables: true)
272
272
  with_prepared_ast {
273
- GraphQL::Language::SanitizedPrinter.new(self, inline_variables: inline_variables).sanitized_query_string
273
+ schema.sanitized_printer.new(self, inline_variables: inline_variables).sanitized_query_string
274
274
  }
275
275
  end
276
276
 
@@ -47,10 +47,16 @@ module GraphQL
47
47
  # _while_ building the schema.
48
48
  # It will dig for a type if it encounters a custom type. This could be a problem if there are cycles.
49
49
  directive_type_resolver = nil
50
- directive_type_resolver = build_resolve_type(GraphQL::Schema::BUILT_IN_TYPES, directives, ->(type_name) {
50
+ directive_type_resolver = build_resolve_type(types, directives, ->(type_name) {
51
51
  types[type_name] ||= begin
52
52
  defn = document.definitions.find { |d| d.respond_to?(:name) && d.name == type_name }
53
- build_definition_from_node(defn, directive_type_resolver, default_resolve)
53
+ if defn
54
+ build_definition_from_node(defn, directive_type_resolver, default_resolve)
55
+ elsif (built_in_defn = GraphQL::Schema::BUILT_IN_TYPES[type_name])
56
+ built_in_defn
57
+ else
58
+ raise "No definition for #{type_name.inspect} found in schema document or built-in types. Add a definition for it or remove it."
59
+ end
54
60
  end
55
61
  })
56
62
 
@@ -39,7 +39,7 @@ module GraphQL
39
39
  transform_name = arguments[:by]
40
40
  if TRANSFORMS.include?(transform_name) && return_value.respond_to?(transform_name)
41
41
  return_value = return_value.public_send(transform_name)
42
- response = context.namespace(:interpreter)[:runtime].response
42
+ response = context.namespace(:interpreter)[:runtime].final_result
43
43
  *keys, last = path
44
44
  keys.each do |key|
45
45
  if response && (response = response[key])
@@ -24,6 +24,15 @@ module GraphQL
24
24
  extend GraphQL::Schema::Member::ValidatesInput
25
25
 
26
26
  class UnresolvedValueError < GraphQL::EnumType::UnresolvedValueError
27
+ def initialize(value:, enum:, context:)
28
+ fix_message = ", but this isn't a valid value for `#{enum.graphql_name}`. Update the field or resolver to return one of `#{enum.graphql_name}`'s values instead."
29
+ message = if (cp = context[:current_path]) && (cf = context[:current_field])
30
+ "`#{cf.path}` returned `#{value.inspect}` at `#{cp.join(".")}`#{fix_message}"
31
+ else
32
+ "`#{value.inspect}` was returned for `#{enum.graphql_name}`#{fix_message}"
33
+ end
34
+ super(message)
35
+ end
27
36
  end
28
37
 
29
38
  class << self
@@ -100,7 +109,7 @@ module GraphQL
100
109
  if enum_value
101
110
  enum_value.graphql_name
102
111
  else
103
- raise(self::UnresolvedValueError, "Can't resolve enum #{graphql_name} for #{value.inspect}")
112
+ raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx)
104
113
  end
105
114
  end
106
115
 
@@ -11,6 +11,14 @@ module GraphQL
11
11
 
12
12
  include GraphQL::Dig
13
13
 
14
+ # @return [GraphQL::Query::Context] The context for this query
15
+ attr_reader :context
16
+ # @return [GraphQL::Query::Arguments, GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance
17
+ attr_reader :arguments
18
+
19
+ # Ruby-like hash behaviors, read-only
20
+ def_delegators :@ruby_style_hash, :keys, :values, :each, :map, :any?, :empty?
21
+
14
22
  def initialize(arguments = nil, ruby_kwargs: nil, context:, defaults_used:)
15
23
  @context = context
16
24
  if ruby_kwargs
@@ -54,19 +62,8 @@ module GraphQL
54
62
  @maybe_lazies = maybe_lazies
55
63
  end
56
64
 
57
- # @return [GraphQL::Query::Context] The context for this query
58
- attr_reader :context
59
-
60
- # @return [GraphQL::Query::Arguments, GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance
61
- attr_reader :arguments
62
-
63
- # Ruby-like hash behaviors, read-only
64
- def_delegators :@ruby_style_hash, :keys, :values, :each, :map, :any?, :empty?
65
-
66
65
  def to_h
67
- @ruby_style_hash.inject({}) do |h, (key, value)|
68
- h.merge(key => unwrap_value(value))
69
- end
66
+ unwrap_value(@ruby_style_hash)
70
67
  end
71
68
 
72
69
  def to_hash
@@ -91,8 +88,8 @@ module GraphQL
91
88
  when Array
92
89
  value.map { |item| unwrap_value(item) }
93
90
  when Hash
94
- value.inject({}) do |h, (key, value)|
95
- h.merge(key => unwrap_value(value))
91
+ value.reduce({}) do |h, (key, value)|
92
+ h.merge!(key => unwrap_value(value))
96
93
  end
97
94
  when InputObject
98
95
  value.to_h
@@ -162,7 +159,6 @@ module GraphQL
162
159
  # @api private
163
160
  INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object responding to `to_h` or `to_unsafe_h`."
164
161
 
165
-
166
162
  def validate_non_null_input(input, ctx)
167
163
  result = GraphQL::Query::InputValidationResult.new
168
164
 
@@ -124,6 +124,7 @@ module GraphQL
124
124
  end
125
125
 
126
126
  def camelize(string)
127
+ return string if string == '_'
127
128
  return string unless string.include?("_")
128
129
  camelized = string.split('_').map(&:capitalize).join
129
130
  camelized[0] = camelized[0].downcase
@@ -4,37 +4,32 @@ module GraphQL
4
4
  # Used to convert your {GraphQL::Schema} to a GraphQL schema string
5
5
  #
6
6
  # @example print your schema to standard output (via helper)
7
- # MySchema = GraphQL::Schema.define(query: QueryType)
8
7
  # puts GraphQL::Schema::Printer.print_schema(MySchema)
9
8
  #
10
9
  # @example print your schema to standard output
11
- # MySchema = GraphQL::Schema.define(query: QueryType)
12
10
  # puts GraphQL::Schema::Printer.new(MySchema).print_schema
13
11
  #
14
12
  # @example print a single type to standard output
15
- # query_root = GraphQL::ObjectType.define do
16
- # name "Query"
13
+ # class Types::Query < GraphQL::Schema::Object
17
14
  # description "The query root of this schema"
18
15
  #
19
- # field :post do
20
- # type post_type
21
- # resolve ->(obj, args, ctx) { Post.find(args["id"]) }
22
- # end
16
+ # field :post, Types::Post, null: true
23
17
  # end
24
18
  #
25
- # post_type = GraphQL::ObjectType.define do
26
- # name "Post"
19
+ # class Types::Post < GraphQL::Schema::Object
27
20
  # description "A blog post"
28
21
  #
29
- # field :id, !types.ID
30
- # field :title, !types.String
31
- # field :body, !types.String
22
+ # field :id, ID, null: false
23
+ # field :title, String, null: false
24
+ # field :body, String, null: false
32
25
  # end
33
26
  #
34
- # MySchema = GraphQL::Schema.define(query: query_root)
27
+ # class MySchema < GraphQL::Schema
28
+ # query(Types::Query)
29
+ # end
35
30
  #
36
31
  # printer = GraphQL::Schema::Printer.new(MySchema)
37
- # puts printer.print_type(post_type)
32
+ # puts printer.print_type(Types::Post)
38
33
  #
39
34
  class Printer < GraphQL::Language::Printer
40
35
  attr_reader :schema, :warden
@@ -87,7 +82,7 @@ module GraphQL
87
82
 
88
83
  # Return a GraphQL schema string for the defined types in the schema
89
84
  def print_schema
90
- print(@document)
85
+ print(@document) + "\n"
91
86
  end
92
87
 
93
88
  def print_type(type)
@@ -307,10 +307,15 @@ module GraphQL
307
307
  arguments: arguments,
308
308
  null: null,
309
309
  complexity: complexity,
310
- extensions: extensions,
311
310
  broadcastable: broadcastable?,
312
311
  }
313
312
 
313
+ # If there aren't any, then the returned array is `[].freeze`,
314
+ # but passing that along breaks some user code.
315
+ if (exts = extensions).any?
316
+ field_opts[:extensions] = exts
317
+ end
318
+
314
319
  if has_max_page_size?
315
320
  field_opts[:max_page_size] = max_page_size
316
321
  end
@@ -1631,6 +1631,14 @@ module GraphQL
1631
1631
  find_inherited_value(:multiplex_analyzers, EMPTY_ARRAY) + own_multiplex_analyzers
1632
1632
  end
1633
1633
 
1634
+ def sanitized_printer(new_sanitized_printer = nil)
1635
+ if new_sanitized_printer
1636
+ @own_sanitized_printer = new_sanitized_printer
1637
+ else
1638
+ @own_sanitized_printer || GraphQL::Language::SanitizedPrinter
1639
+ end
1640
+ end
1641
+
1634
1642
  # Execute a query on itself.
1635
1643
  # @see {Query#initialize} for arguments.
1636
1644
  # @return [Hash] query result, ready to be serialized as JSON
@@ -95,6 +95,14 @@ module GraphQL
95
95
  @action_cable = action_cable
96
96
  @action_cable_coder = action_cable_coder
97
97
  @serializer = serializer
98
+ @serialize_with_context = case @serializer.method(:load).arity
99
+ when 1
100
+ false
101
+ when 2
102
+ true
103
+ else
104
+ raise ArgumentError, "#{@serializer} must repond to `.load` accepting one or two arguments"
105
+ end
98
106
  @transmit_ns = namespace
99
107
  super
100
108
  end
@@ -154,7 +162,7 @@ module GraphQL
154
162
  # so just run it once, then deliver the result to every subscriber
155
163
  first_event = events.first
156
164
  first_subscription_id = first_event.context.fetch(:subscription_id)
157
- object ||= @serializer.load(message)
165
+ object ||= load_action_cable_message(message, first_event.context)
158
166
  result = execute_update(first_subscription_id, first_event, object)
159
167
  # Having calculated the result _once_, send the same payload to all subscribers
160
168
  events.each do |event|
@@ -167,6 +175,18 @@ module GraphQL
167
175
  end
168
176
  end
169
177
 
178
+ # This is called to turn an ActionCable-broadcasted string (JSON)
179
+ # into a query-ready application object.
180
+ # @param message [String] n ActionCable-broadcasted string (JSON)
181
+ # @param context [GraphQL::Query::Context] the context of the first event for a given subscription fingerprint
182
+ def load_action_cable_message(message, context)
183
+ if @serialize_with_context
184
+ @serializer.load(message, context)
185
+ else
186
+ @serializer.load(message)
187
+ end
188
+ end
189
+
170
190
  # Return the query from "storage" (in memory)
171
191
  def read_subscription(subscription_id)
172
192
  query = @subscriptions[subscription_id]
@@ -3,6 +3,7 @@
3
3
  module GraphQL
4
4
  module Types
5
5
  module Relay
6
+ # Include this module to your root Query type to get a Relay-compliant `node(id: ID!): Node` field that uses the schema's `object_from_id` hook.
6
7
  module HasNodeField
7
8
  def self.included(child_class)
8
9
  child_class.field(**field_options, &field_block)
@@ -12,7 +13,6 @@ module GraphQL
12
13
  def field_options
13
14
  {
14
15
  name: "node",
15
- owner: nil,
16
16
  type: GraphQL::Types::Relay::Node,
17
17
  null: true,
18
18
  description: "Fetches an object given its ID.",
@@ -3,6 +3,7 @@
3
3
  module GraphQL
4
4
  module Types
5
5
  module Relay
6
+ # Include this module to your root Query type to get a Relay-style `nodes(id: ID!): [Node]` field that uses the schema's `object_from_id` hook.
6
7
  module HasNodesField
7
8
  def self.included(child_class)
8
9
  child_class.field(**field_options, &field_block)
@@ -12,7 +13,6 @@ module GraphQL
12
13
  def field_options
13
14
  {
14
15
  name: "nodes",
15
- owner: nil,
16
16
  type: [GraphQL::Types::Relay::Node, null: true],
17
17
  null: false,
18
18
  description: "Fetches a list of objects given a list of IDs.",
@@ -6,7 +6,7 @@ module GraphQL
6
6
  # or use it for inspiration for your own field definition.
7
7
  #
8
8
  # @example Adding this field directly
9
- # add_field(GraphQL::Types::Relay::NodeField)
9
+ # include GraphQL::Types::Relay::HasNodeField
10
10
  #
11
11
  # @example Implementing a similar field in your own Query root
12
12
  #
@@ -19,7 +19,7 @@ module GraphQL
19
19
  # context.schema.object_from_id(id, context)
20
20
  # end
21
21
  #
22
- NodeField = GraphQL::Schema::Field.new(**HasNodeField.field_options, &HasNodeField.field_block)
22
+ NodeField = GraphQL::Schema::Field.new(owner: nil, **HasNodeField.field_options, &HasNodeField.field_block)
23
23
  end
24
24
  end
25
25
  end
@@ -6,7 +6,7 @@ module GraphQL
6
6
  # or use it for inspiration for your own field definition.
7
7
  #
8
8
  # @example Adding this field directly
9
- # add_field(GraphQL::Types::Relay::NodesField)
9
+ # include GraphQL::Types::Relay::HasNodesField
10
10
  #
11
11
  # @example Implementing a similar field in your own Query root
12
12
  #
@@ -21,7 +21,7 @@ module GraphQL
21
21
  # end
22
22
  # end
23
23
  #
24
- NodesField = GraphQL::Schema::Field.new(**HasNodesField.field_options, &HasNodesField.field_block)
24
+ NodesField = GraphQL::Schema::Field.new(owner: nil, **HasNodesField.field_options, &HasNodesField.field_block)
25
25
  end
26
26
  end
27
27
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.12.11"
3
+ VERSION = "1.12.15"
4
4
  end
data/readme.md CHANGED
@@ -2,9 +2,6 @@
2
2
 
3
3
  [![CI Suite](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml/badge.svg)](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml)
4
4
  [![Gem Version](https://badge.fury.io/rb/graphql.svg)](https://rubygems.org/gems/graphql)
5
- [![Code Climate](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/gpa.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
6
- [![Test Coverage](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/coverage.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
7
- [![built with love](https://cloud.githubusercontent.com/assets/2231765/6766607/d07992c6-cfc9-11e4-813f-d9240714dd50.png)](https://rmosolgo.github.io/react-badges/)
8
5
 
9
6
  A Ruby implementation of [GraphQL](https://graphql.org/).
10
7
 
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: 1.12.11
4
+ version: 1.12.15
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: 2021-05-28 00:00:00.000000000 Z
11
+ date: 2021-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: codeclimate-test-reporter
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '0.4'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '0.4'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: concurrent-ruby
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -699,7 +685,7 @@ metadata:
699
685
  source_code_uri: https://github.com/rmosolgo/graphql-ruby
700
686
  bug_tracker_uri: https://github.com/rmosolgo/graphql-ruby/issues
701
687
  mailing_list_uri: https://tinyletter.com/graphql-ruby
702
- post_install_message:
688
+ post_install_message:
703
689
  rdoc_options: []
704
690
  require_paths:
705
691
  - lib
@@ -714,8 +700,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
714
700
  - !ruby/object:Gem::Version
715
701
  version: '0'
716
702
  requirements: []
717
- rubygems_version: 3.1.4
718
- signing_key:
703
+ rubygems_version: 3.2.15
704
+ signing_key:
719
705
  specification_version: 4
720
706
  summary: A GraphQL language and runtime for Ruby
721
707
  test_files: []