graphql 1.12.3 → 1.12.4

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