graphql 1.12.3 → 1.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +3 -1
  3. data/lib/generators/graphql/relay.rb +55 -0
  4. data/lib/generators/graphql/relay_generator.rb +3 -46
  5. data/lib/graphql.rb +1 -1
  6. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  7. data/lib/graphql/backtrace/table.rb +0 -1
  8. data/lib/graphql/backtrace/traced_error.rb +0 -1
  9. data/lib/graphql/backtrace/tracer.rb +2 -6
  10. data/lib/graphql/dataloader.rb +102 -92
  11. data/lib/graphql/dataloader/null_dataloader.rb +5 -5
  12. data/lib/graphql/dataloader/request.rb +1 -6
  13. data/lib/graphql/dataloader/request_all.rb +1 -4
  14. data/lib/graphql/dataloader/source.rb +20 -6
  15. data/lib/graphql/execution/interpreter.rb +1 -1
  16. data/lib/graphql/execution/interpreter/arguments_cache.rb +37 -14
  17. data/lib/graphql/execution/interpreter/resolve.rb +33 -25
  18. data/lib/graphql/execution/interpreter/runtime.rb +36 -74
  19. data/lib/graphql/execution/multiplex.rb +21 -22
  20. data/lib/graphql/object_type.rb +0 -2
  21. data/lib/graphql/parse_error.rb +0 -1
  22. data/lib/graphql/query.rb +8 -2
  23. data/lib/graphql/query/arguments_cache.rb +0 -1
  24. data/lib/graphql/query/context.rb +1 -3
  25. data/lib/graphql/query/executor.rb +0 -1
  26. data/lib/graphql/query/null_context.rb +3 -2
  27. data/lib/graphql/query/variable_validation_error.rb +1 -1
  28. data/lib/graphql/schema/argument.rb +61 -0
  29. data/lib/graphql/schema/field.rb +10 -5
  30. data/lib/graphql/schema/find_inherited_value.rb +3 -1
  31. data/lib/graphql/schema/input_object.rb +6 -2
  32. data/lib/graphql/schema/member/has_arguments.rb +43 -56
  33. data/lib/graphql/schema/member/has_fields.rb +1 -4
  34. data/lib/graphql/schema/member/instrumentation.rb +0 -1
  35. data/lib/graphql/subscriptions/event.rb +0 -1
  36. data/lib/graphql/subscriptions/instrumentation.rb +0 -1
  37. data/lib/graphql/subscriptions/serialize.rb +0 -1
  38. data/lib/graphql/version.rb +1 -1
  39. metadata +7 -90
@@ -35,7 +35,7 @@ module GraphQL
35
35
  @queries = queries
36
36
  @queries.each { |q| q.multiplex = self }
37
37
  @context = context
38
- @context[:dataloader] = @dataloader = @schema.dataloader_class.new(context)
38
+ @context[:dataloader] = @dataloader = @schema.dataloader_class.new
39
39
  @tracers = schema.tracers + (context[:tracers] || [])
40
40
  # Support `context: {backtrace: true}`
41
41
  if context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
@@ -74,6 +74,24 @@ module GraphQL
74
74
  end
75
75
  end
76
76
 
77
+ # @param query [GraphQL::Query]
78
+ def begin_query(results, idx, query, multiplex)
79
+ operation = query.selected_operation
80
+ result = if operation.nil? || !query.valid? || query.context.errors.any?
81
+ NO_OPERATION
82
+ else
83
+ begin
84
+ # These were checked to be the same in `#supports_multiplexing?`
85
+ query.schema.query_execution_strategy.begin_query(query, multiplex)
86
+ rescue GraphQL::ExecutionError => err
87
+ query.context.errors << err
88
+ NO_OPERATION
89
+ end
90
+ end
91
+ results[idx] = result
92
+ nil
93
+ end
94
+
77
95
  private
78
96
 
79
97
  def run_as_multiplex(multiplex)
@@ -83,15 +101,13 @@ module GraphQL
83
101
  # Do as much eager evaluation of the query as possible
84
102
  results = []
85
103
  queries.each_with_index do |query, idx|
86
- multiplex.dataloader.enqueue {
87
- results[idx] = begin_query(query, multiplex)
88
- }
104
+ multiplex.dataloader.append_job { begin_query(results, idx, query, multiplex) }
89
105
  end
90
106
 
91
107
  multiplex.dataloader.run
92
108
 
93
109
  # Then, work through lazy results in a breadth-first way
94
- multiplex.dataloader.enqueue {
110
+ multiplex.dataloader.append_job {
95
111
  multiplex.schema.query_execution_strategy.finish_multiplex(results, multiplex)
96
112
  }
97
113
  multiplex.dataloader.run
@@ -112,23 +128,6 @@ module GraphQL
112
128
  raise
113
129
  end
114
130
 
115
- # @param query [GraphQL::Query]
116
- # @return [Hash] The initial result (may not be finished if there are lazy values)
117
- def begin_query(query, multiplex)
118
- operation = query.selected_operation
119
- if operation.nil? || !query.valid? || query.context.errors.any?
120
- NO_OPERATION
121
- else
122
- begin
123
- # These were checked to be the same in `#supports_multiplexing?`
124
- query.schema.query_execution_strategy.begin_query(query, multiplex)
125
- rescue GraphQL::ExecutionError => err
126
- query.context.errors << err
127
- NO_OPERATION
128
- end
129
- end
130
- end
131
-
132
131
  # @param data_result [Hash] The result for the "data" key, if any
133
132
  # @param query [GraphQL::Query] The query which was run
134
133
  # @return [Hash] final result of this query, including all values and errors
@@ -121,8 +121,6 @@ module GraphQL
121
121
  iface = GraphQL::BaseType.resolve_related_type(type_membership.abstract_type)
122
122
  if iface.is_a?(GraphQL::InterfaceType)
123
123
  @clean_inherited_fields.merge!(iface.fields)
124
- else
125
- pp iface
126
124
  end
127
125
  end
128
126
  @clean_inherited_fields
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- # test_via: language/parser.rb
3
2
  module GraphQL
4
3
  class ParseError < GraphQL::Error
5
4
  attr_reader :line, :col, :query
data/lib/graphql/query.rb CHANGED
@@ -251,12 +251,18 @@ module GraphQL
251
251
  # @param parent_object [GraphQL::Schema::Object]
252
252
  # @return Hash{Symbol => Object}
253
253
  def arguments_for(ast_node, definition, parent_object: nil)
254
+ if interpreter?
255
+ arguments_cache.fetch(ast_node, definition, parent_object)
256
+ else
257
+ arguments_cache[ast_node][definition]
258
+ end
259
+ end
260
+
261
+ def arguments_cache
254
262
  if interpreter?
255
263
  @arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self)
256
- @arguments_cache.fetch(ast_node, definition, parent_object)
257
264
  else
258
265
  @arguments_cache ||= ArgumentsCache.build(self)
259
- @arguments_cache[ast_node][definition]
260
266
  end
261
267
  end
262
268
 
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- # test_via: ../query.rb
3
2
  module GraphQL
4
3
  class Query
5
4
  module ArgumentsCache
@@ -1,6 +1,4 @@
1
1
  # frozen_string_literal: true
2
- # test_via: ../execution/execute.rb
3
- # test_via: ../execution/lazy.rb
4
2
  module GraphQL
5
3
  class Query
6
4
  # Expose some query-specific info to field resolve functions.
@@ -159,7 +157,7 @@ module GraphQL
159
157
  end
160
158
 
161
159
  def dataloader
162
- @dataloader ||= query.multiplex.dataloader
160
+ @dataloader ||= query.multiplex ? query.multiplex.dataloader : schema.dataloader_class.new
163
161
  end
164
162
 
165
163
  # @api private
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- # test_via: ../query.rb
3
2
  module GraphQL
4
3
  class Query
5
4
  class Executor
@@ -9,10 +9,11 @@ module GraphQL
9
9
  def visible_type?(t); true; end
10
10
  end
11
11
 
12
- attr_reader :schema, :query, :warden
12
+ attr_reader :schema, :query, :warden, :dataloader
13
13
 
14
14
  def initialize
15
15
  @query = nil
16
+ @dataloader = GraphQL::Dataloader::NullDataloader.new
16
17
  @schema = GraphQL::Schema.new
17
18
  @warden = NullWarden.new(
18
19
  GraphQL::Filter.new,
@@ -36,7 +37,7 @@ module GraphQL
36
37
  @instance = self.new
37
38
  end
38
39
 
39
- def_delegators :instance, :query, :schema, :warden, :interpreter?
40
+ def_delegators :instance, :query, :schema, :warden, :interpreter?, :dataloader
40
41
  end
41
42
  end
42
43
  end
@@ -23,7 +23,7 @@ module GraphQL
23
23
  # a one level deep merge explicitly. However beyond that only show the
24
24
  # latest value and problems.
25
25
  super.merge({ "extensions" => { "value" => value, "problems" => validation_result.problems }}) do |key, oldValue, newValue|
26
- if oldValue.respond_to? merge
26
+ if oldValue.respond_to?(:merge)
27
27
  oldValue.merge(newValue)
28
28
  else
29
29
  newValue
@@ -236,6 +236,67 @@ module GraphQL
236
236
  end
237
237
  end
238
238
 
239
+ # @api private
240
+ def coerce_into_values(parent_object, values, context, argument_values)
241
+ arg_name = graphql_name
242
+ arg_key = keyword
243
+ has_value = false
244
+ default_used = false
245
+ if values.key?(arg_name)
246
+ has_value = true
247
+ value = values[arg_name]
248
+ elsif values.key?(arg_key)
249
+ has_value = true
250
+ value = values[arg_key]
251
+ elsif default_value?
252
+ has_value = true
253
+ value = default_value
254
+ default_used = true
255
+ end
256
+
257
+ if has_value
258
+ loaded_value = nil
259
+ coerced_value = context.schema.error_handler.with_error_handling(context) do
260
+ type.coerce_input(value, context)
261
+ end
262
+
263
+ # TODO this should probably be inside after_lazy
264
+ if loads && !from_resolver?
265
+ loaded_value = if type.list?
266
+ loaded_values = coerced_value.map { |val| owner.load_application_object(self, loads, val, context) }
267
+ context.schema.after_any_lazies(loaded_values) { |result| result }
268
+ else
269
+ owner.load_application_object(self, loads, coerced_value, context)
270
+ end
271
+ end
272
+
273
+ coerced_value = if loaded_value
274
+ loaded_value
275
+ else
276
+ coerced_value
277
+ end
278
+
279
+ # If this isn't lazy, then the block returns eagerly and assigns the result here
280
+ # If it _is_ lazy, then we write the lazy to the hash, then update it later
281
+ argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |coerced_value|
282
+ owner.validate_directive_argument(self, coerced_value)
283
+ prepared_value = context.schema.error_handler.with_error_handling(context) do
284
+ prepare_value(parent_object, coerced_value, context: context)
285
+ end
286
+
287
+ # TODO code smell to access such a deeply-nested constant in a distant module
288
+ argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
289
+ value: prepared_value,
290
+ definition: self,
291
+ default_used: default_used,
292
+ )
293
+ end
294
+ else
295
+ # has_value is false
296
+ owner.validate_directive_argument(self, nil)
297
+ end
298
+ end
299
+
239
300
  private
240
301
 
241
302
  def validate_input_type(input_type)
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- # test_via: ../object.rb
3
2
  require "graphql/schema/field/connection_extension"
4
3
  require "graphql/schema/field/scope_extension"
5
4
 
@@ -61,6 +60,10 @@ module GraphQL
61
60
  @introspection
62
61
  end
63
62
 
63
+ def inspect
64
+ "#<#{self.class} #{path}#{arguments.any? ? "(...)" : ""}: #{type.to_type_signature}>"
65
+ end
66
+
64
67
  alias :mutation :resolver
65
68
 
66
69
  # @return [Boolean] Apply tracing to this field? (Default: skip scalars, this is the override value)
@@ -285,22 +288,24 @@ module GraphQL
285
288
  @owner = owner
286
289
  @subscription_scope = subscription_scope
287
290
 
288
- # Do this last so we have as much context as possible when initializing them:
289
291
  @extensions = EMPTY_ARRAY
290
- if extensions.any?
291
- self.extensions(extensions)
292
- end
293
292
  # This should run before connection extension,
294
293
  # but should it run after the definition block?
295
294
  if scoped?
296
295
  self.extension(ScopeExtension)
297
296
  end
297
+
298
298
  # The problem with putting this after the definition_block
299
299
  # is that it would override arguments
300
300
  if connection? && connection_extension
301
301
  self.extension(connection_extension)
302
302
  end
303
303
 
304
+ # Do this last so we have as much context as possible when initializing them:
305
+ if extensions.any?
306
+ self.extensions(extensions)
307
+ end
308
+
304
309
  if directives.any?
305
310
  directives.each do |(dir_class, options)|
306
311
  self.directive(dir_class, **options)
@@ -20,7 +20,9 @@ module GraphQL
20
20
  if self.is_a?(Class)
21
21
  superclass.respond_to?(method_name, true) ? superclass.send(method_name) : default_value
22
22
  else
23
- ancestors[1..-1].each do |ancestor|
23
+ ancestors_except_self = ancestors
24
+ ancestors_except_self.delete(self)
25
+ ancestors_except_self.each do |ancestor|
24
26
  if ancestor.respond_to?(method_name, true)
25
27
  return ancestor.send(method_name)
26
28
  end
@@ -214,8 +214,12 @@ module GraphQL
214
214
  arguments = coerce_arguments(nil, value, ctx)
215
215
 
216
216
  ctx.schema.after_lazy(arguments) do |resolved_arguments|
217
- input_obj_instance = self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil)
218
- input_obj_instance.prepare
217
+ if resolved_arguments.is_a?(GraphQL::Error)
218
+ raise resolved_arguments
219
+ else
220
+ input_obj_instance = self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil)
221
+ input_obj_instance.prepare
222
+ end
219
223
  end
220
224
  end
221
225
 
@@ -81,79 +81,66 @@ module GraphQL
81
81
  end
82
82
 
83
83
  # @api private
84
+ # If given a block, it will eventually yield the loaded args to the block.
85
+ #
86
+ # If no block is given, it will immediately dataload (but might return a Lazy).
87
+ #
84
88
  # @param values [Hash<String, Object>]
85
89
  # @param context [GraphQL::Query::Context]
86
- # @return [Hash<Symbol, Object>, Execution::Lazy<Hash>]
87
- def coerce_arguments(parent_object, values, context)
90
+ # @yield [Interpreter::Arguments, Execution::Lazy<Interpeter::Arguments>]
91
+ # @return [Interpreter::Arguments, Execution::Lazy<Interpeter::Arguments>]
92
+ def coerce_arguments(parent_object, values, context, &block)
88
93
  # Cache this hash to avoid re-merging it
89
94
  arg_defns = self.arguments
95
+ total_args_count = arg_defns.size
90
96
 
91
- if arg_defns.empty?
92
- GraphQL::Execution::Interpreter::Arguments::EMPTY
97
+ if total_args_count == 0
98
+ final_args = GraphQL::Execution::Interpreter::Arguments::EMPTY
99
+ if block_given?
100
+ block.call(final_args)
101
+ nil
102
+ else
103
+ final_args
104
+ end
93
105
  else
106
+ finished_args = nil
94
107
  argument_values = {}
95
- arg_lazies = arg_defns.map do |arg_name, arg_defn|
96
- arg_key = arg_defn.keyword
97
- has_value = false
98
- default_used = false
99
- if values.key?(arg_name)
100
- has_value = true
101
- value = values[arg_name]
102
- elsif values.key?(arg_key)
103
- has_value = true
104
- value = values[arg_key]
105
- elsif arg_defn.default_value?
106
- has_value = true
107
- value = arg_defn.default_value
108
- default_used = true
109
- end
110
-
111
- if has_value
112
- loads = arg_defn.loads
113
- loaded_value = nil
114
- coerced_value = context.schema.error_handler.with_error_handling(context) do
115
- arg_defn.type.coerce_input(value, context)
116
- end
117
-
118
- # TODO this should probably be inside after_lazy
119
- if loads && !arg_defn.from_resolver?
120
- loaded_value = if arg_defn.type.list?
121
- loaded_values = coerced_value.map { |val| load_application_object(arg_defn, loads, val, context) }
122
- context.schema.after_any_lazies(loaded_values) { |result| result }
108
+ resolved_args_count = 0
109
+ raised_error = false
110
+ arg_defns.each do |arg_name, arg_defn|
111
+ context.dataloader.append_job do
112
+ begin
113
+ arg_defn.coerce_into_values(parent_object, values, context, argument_values)
114
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
115
+ raised_error = true
116
+ if block_given?
117
+ block.call(err)
123
118
  else
124
- load_application_object(arg_defn, loads, coerced_value, context)
119
+ finished_args = err
125
120
  end
126
121
  end
127
122
 
128
- coerced_value = if loaded_value
129
- loaded_value
130
- else
131
- coerced_value
132
- end
123
+ resolved_args_count += 1
124
+ if resolved_args_count == total_args_count && !raised_error
125
+ finished_args = context.schema.after_any_lazies(argument_values.values) {
126
+ GraphQL::Execution::Interpreter::Arguments.new(
127
+ argument_values: argument_values,
128
+ )
129
+ }
133
130
 
134
- context.schema.after_lazy(coerced_value) do |coerced_value|
135
- validate_directive_argument(arg_defn, coerced_value)
136
- prepared_value = context.schema.error_handler.with_error_handling(context) do
137
- arg_defn.prepare_value(parent_object, coerced_value, context: context)
131
+ if block_given?
132
+ block.call(finished_args)
138
133
  end
139
-
140
- # TODO code smell to access such a deeply-nested constant in a distant module
141
- argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
142
- value: prepared_value,
143
- definition: arg_defn,
144
- default_used: default_used,
145
- )
146
134
  end
147
- else
148
- # has_value is false
149
- validate_directive_argument(arg_defn, nil)
150
135
  end
151
136
  end
152
137
 
153
- context.schema.after_any_lazies(arg_lazies) do
154
- GraphQL::Execution::Interpreter::Arguments.new(
155
- argument_values: argument_values,
156
- )
138
+ if block_given?
139
+ nil
140
+ else
141
+ # This API returns eagerly, gotta run it now
142
+ context.dataloader.run
143
+ finished_args
157
144
  end
158
145
  end
159
146
  end
@@ -74,11 +74,8 @@ module GraphQL
74
74
  @field_class = new_field_class
75
75
  elsif defined?(@field_class) && @field_class
76
76
  @field_class
77
- elsif self.is_a?(Class)
78
- superclass.respond_to?(:field_class) ? superclass.field_class : GraphQL::Schema::Field
79
77
  else
80
- ancestor = ancestors[1..-1].find { |a| a.respond_to?(:field_class) && a.field_class }
81
- ancestor ? ancestor.field_class : GraphQL::Schema::Field
78
+ find_inherited_value(:field_class, GraphQL::Schema::Field)
82
79
  end
83
80
  end
84
81