graphql 1.12.5 → 1.12.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +1 -1
  3. data/lib/generators/graphql/templates/graphql_controller.erb +2 -2
  4. data/lib/graphql.rb +13 -11
  5. data/lib/graphql/dataloader.rb +36 -5
  6. data/lib/graphql/execution/errors.rb +109 -11
  7. data/lib/graphql/execution/interpreter/runtime.rb +32 -36
  8. data/lib/graphql/introspection.rb +1 -1
  9. data/lib/graphql/introspection/directive_type.rb +7 -3
  10. data/lib/graphql/language.rb +1 -0
  11. data/lib/graphql/language/cache.rb +37 -0
  12. data/lib/graphql/language/parser.rb +15 -5
  13. data/lib/graphql/language/parser.y +15 -5
  14. data/lib/graphql/pagination/active_record_relation_connection.rb +7 -0
  15. data/lib/graphql/pagination/connection.rb +6 -1
  16. data/lib/graphql/pagination/connections.rb +2 -1
  17. data/lib/graphql/pagination/relation_connection.rb +12 -1
  18. data/lib/graphql/query.rb +1 -3
  19. data/lib/graphql/query/null_context.rb +7 -1
  20. data/lib/graphql/query/validation_pipeline.rb +1 -1
  21. data/lib/graphql/railtie.rb +9 -1
  22. data/lib/graphql/rake_task.rb +3 -0
  23. data/lib/graphql/schema.rb +23 -37
  24. data/lib/graphql/schema/argument.rb +3 -1
  25. data/lib/graphql/schema/field/connection_extension.rb +1 -0
  26. data/lib/graphql/schema/input_object.rb +2 -2
  27. data/lib/graphql/schema/loader.rb +8 -0
  28. data/lib/graphql/schema/member/base_dsl_methods.rb +3 -15
  29. data/lib/graphql/schema/object.rb +19 -5
  30. data/lib/graphql/schema/resolver.rb +24 -22
  31. data/lib/graphql/schema/scalar.rb +3 -1
  32. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +3 -1
  33. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +6 -2
  34. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  35. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  36. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  37. data/lib/graphql/static_validation/rules/fields_will_merge.rb +17 -8
  38. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  39. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  40. data/lib/graphql/static_validation/validator.rb +5 -0
  41. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +4 -3
  42. data/lib/graphql/subscriptions/broadcast_analyzer.rb +0 -3
  43. data/lib/graphql/subscriptions/serialize.rb +11 -1
  44. data/lib/graphql/tracing/active_support_notifications_tracing.rb +2 -1
  45. data/lib/graphql/types/relay/base_connection.rb +4 -0
  46. data/lib/graphql/types/relay/connection_behaviors.rb +38 -5
  47. data/lib/graphql/types/relay/edge_behaviors.rb +12 -1
  48. data/lib/graphql/version.rb +1 -1
  49. metadata +7 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ec7a782dba5df306d3e0590820d75d520b86651b9293417b115070057c45b8f
4
- data.tar.gz: 731f02ddfda3690ecd54cba1b1f373f9c8a53b65d422f853d44ddc9072fa85d7
3
+ metadata.gz: 9d314c3a4dbbf6bded442bea6c2fcdd68b0cd7730ebb5e807d3677ffdc838268
4
+ data.tar.gz: 60a24771181bb8a479f768a4b17f3a474ff9bbc339027b94e18bf92554df296c
5
5
  SHA512:
6
- metadata.gz: e0783dea9b65037ea2d92b9ab88fc98153a541b0821f7b5d38bb53de3d33024a85d626302c8c92ddc9971606806c1a7896fb7a98da0c8c2261f9a034d87624e2
7
- data.tar.gz: c4b6035220bf578fc974a3fa437914dd5e652018b651b0c5af57f355e5d08b113a2faa2a6824242b71d01bab844767592f4d6ea5323e66e2ef05bcd74ad62df3
6
+ metadata.gz: 25f941249c01ce4dcfe3522dfc7b045b6f2bd3888e1f21b2e25a3ae5de74f0de5f9721c998d6ddd33eb681339c3e883edcdc9d76a196c148b688a1264b1761fa
7
+ data.tar.gz: 91bde8d0102ae6874511656b6e11140b511aa1813d19b70b91a168cf58cbfc95eb83a6244137fc06abeae19f15694ca005c94c6e728085d0b8be3ad3bb964ebc
@@ -122,7 +122,7 @@ module Graphql
122
122
  if options.api?
123
123
  say("Skipped graphiql, as this rails project is API only")
124
124
  say(" You may wish to use GraphiQL.app for development: https://github.com/skevy/graphiql-app")
125
- elsif !options[:skip_graphiql]
125
+ elsif !options[:skip_graphiql] && !File.read(Rails.root.join("Gemfile")).include?("graphiql-rails")
126
126
  gem("graphiql-rails", group: :development)
127
127
 
128
128
  # This is a little cheat just to get cleaner shell output:
@@ -15,9 +15,9 @@ class GraphqlController < ApplicationController
15
15
  }
16
16
  result = <%= schema_name %>.execute(query, variables: variables, context: context, operation_name: operation_name)
17
17
  render json: result
18
- rescue => e
18
+ rescue StandardError => e
19
19
  raise e unless Rails.env.development?
20
- handle_error_in_development e
20
+ handle_error_in_development(e)
21
21
  end
22
22
 
23
23
  private
data/lib/graphql.rb CHANGED
@@ -4,7 +4,6 @@ require "json"
4
4
  require "set"
5
5
  require "singleton"
6
6
  require "forwardable"
7
- require_relative "./graphql/railtie" if defined? Rails::Railtie
8
7
 
9
8
  module GraphQL
10
9
  # forwards-compat for argument handling
@@ -82,10 +81,19 @@ end
82
81
  # Order matters for these:
83
82
 
84
83
  require "graphql/execution_error"
84
+ require "graphql/runtime_type_error"
85
+ require "graphql/unresolved_type_error"
86
+ require "graphql/invalid_null_error"
87
+ require "graphql/analysis_error"
88
+ require "graphql/coercion_error"
89
+ require "graphql/invalid_name_error"
90
+ require "graphql/integer_decoding_error"
91
+ require "graphql/integer_encoding_error"
92
+ require "graphql/string_encoding_error"
93
+
85
94
  require "graphql/define"
86
95
  require "graphql/base_type"
87
96
  require "graphql/object_type"
88
-
89
97
  require "graphql/enum_type"
90
98
  require "graphql/input_object_type"
91
99
  require "graphql/interface_type"
@@ -103,13 +111,13 @@ require "graphql/scalar_type"
103
111
  require "graphql/name_validator"
104
112
 
105
113
  require "graphql/language"
114
+
115
+ require_relative "./graphql/railtie" if defined? Rails::Railtie
116
+
106
117
  require "graphql/analysis"
107
118
  require "graphql/tracing"
108
119
  require "graphql/dig"
109
120
  require "graphql/execution"
110
- require "graphql/runtime_type_error"
111
- require "graphql/unresolved_type_error"
112
- require "graphql/invalid_null_error"
113
121
  require "graphql/pagination"
114
122
  require "graphql/schema"
115
123
  require "graphql/query"
@@ -131,12 +139,6 @@ require "graphql/static_validation"
131
139
  require "graphql/dataloader"
132
140
  require "graphql/introspection"
133
141
 
134
- require "graphql/analysis_error"
135
- require "graphql/coercion_error"
136
- require "graphql/invalid_name_error"
137
- require "graphql/integer_decoding_error"
138
- require "graphql/integer_encoding_error"
139
- require "graphql/string_encoding_error"
140
142
  require "graphql/version"
141
143
  require "graphql/compatibility"
142
144
  require "graphql/function"
@@ -29,7 +29,12 @@ module GraphQL
29
29
 
30
30
  def initialize
31
31
  @source_cache = Hash.new { |h, source_class| h[source_class] = Hash.new { |h2, batch_parameters|
32
- source = source_class.new(*batch_parameters)
32
+ source = if RUBY_VERSION < "3"
33
+ source_class.new(*batch_parameters)
34
+ else
35
+ batch_args, batch_kwargs = batch_parameters
36
+ source_class.new(*batch_args, **batch_kwargs)
37
+ end
33
38
  source.setup(self)
34
39
  h2[batch_parameters] = source
35
40
  }
@@ -43,8 +48,15 @@ module GraphQL
43
48
  # @param batch_parameters [Array<Object>]
44
49
  # @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
45
50
  # and cached for the lifetime of this {Multiplex}.
46
- def with(source_class, *batch_parameters)
47
- @source_cache[source_class][batch_parameters]
51
+ if RUBY_VERSION < "3"
52
+ def with(source_class, *batch_parameters)
53
+ @source_cache[source_class][batch_parameters]
54
+ end
55
+ else
56
+ def with(source_class, *batch_args, **batch_kwargs)
57
+ batch_parameters = [batch_args, batch_kwargs]
58
+ @source_cache[source_class][batch_parameters]
59
+ end
48
60
  end
49
61
 
50
62
  # Tell the dataloader that this fiber is waiting for data.
@@ -104,7 +116,7 @@ module GraphQL
104
116
  while @pending_jobs.any?
105
117
  # Create a Fiber to consume jobs until one of the jobs yields
106
118
  # or jobs run out
107
- f = Fiber.new {
119
+ f = spawn_fiber {
108
120
  while (job = @pending_jobs.shift)
109
121
  job.call
110
122
  end
@@ -191,7 +203,7 @@ module GraphQL
191
203
  #
192
204
  # This design could probably be improved by maintaining a `@pending_sources` queue which is shared by the fibers,
193
205
  # similar to `@pending_jobs`. That way, when a fiber is resumed, it would never pick up work that was finished by a different fiber.
194
- source_fiber = Fiber.new do
206
+ source_fiber = spawn_fiber do
195
207
  pending_sources.each(&:run_pending_keys)
196
208
  end
197
209
  end
@@ -204,5 +216,24 @@ module GraphQL
204
216
  rescue UncaughtThrowError => e
205
217
  throw e.tag, e.value
206
218
  end
219
+
220
+ # Copies the thread local vars into the fiber thread local vars. Many
221
+ # gems (such as RequestStore, MiniRacer, etc.) rely on thread local vars
222
+ # to keep track of execution context, and without this they do not
223
+ # behave as expected.
224
+ #
225
+ # @see https://github.com/rmosolgo/graphql-ruby/issues/3449
226
+ def spawn_fiber
227
+ fiber_locals = {}
228
+
229
+ Thread.current.keys.each do |fiber_var_key|
230
+ fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
231
+ end
232
+
233
+ Fiber.new do
234
+ fiber_locals.each { |k, v| Thread.current[k] = v }
235
+ yield
236
+ end
237
+ end
207
238
  end
208
239
  end
@@ -18,21 +18,83 @@ module GraphQL
18
18
  #
19
19
  class Errors
20
20
  def self.use(schema)
21
- if schema.plugins.any? { |(plugin, kwargs)| plugin == self }
22
- definition_line = caller(2, 1).first
23
- GraphQL::Deprecation.warn("GraphQL::Execution::Errors is now installed by default, remove `use GraphQL::Execution::Errors` from #{definition_line}")
24
- end
25
- schema.error_handler = self.new(schema)
21
+ definition_line = caller(2, 1).first
22
+ GraphQL::Deprecation.warn("GraphQL::Execution::Errors is now installed by default, remove `use GraphQL::Execution::Errors` from #{definition_line}")
26
23
  end
27
24
 
25
+ NEW_HANDLER_HASH = ->(h, k) {
26
+ h[k] = {
27
+ class: k,
28
+ handler: nil,
29
+ subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
30
+ }
31
+ }
32
+
28
33
  def initialize(schema)
29
34
  @schema = schema
35
+ @handlers = {
36
+ class: nil,
37
+ handler: nil,
38
+ subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
39
+ }
40
+ end
41
+
42
+ # @api private
43
+ def each_rescue
44
+ handlers = @handlers.values
45
+ while (handler = handlers.shift) do
46
+ yield(handler[:class], handler[:handler])
47
+ handlers.concat(handler[:subclass_handlers].values)
48
+ end
30
49
  end
31
50
 
32
- class NullErrorHandler
33
- def self.with_error_handling(_ctx)
34
- yield
51
+ # Register this handler, updating the
52
+ # internal handler index to maintain least-to-most specific.
53
+ #
54
+ # @param error_class [Class<Exception>]
55
+ # @param error_handler [Proc]
56
+ # @return [void]
57
+ def rescue_from(error_class, error_handler)
58
+ subclasses_handlers = {}
59
+ this_level_subclasses = []
60
+ # During this traversal, do two things:
61
+ # - Identify any already-registered subclasses of this error class
62
+ # and gather them up to be inserted _under_ this class
63
+ # - Find the point in the index where this handler should be inserted
64
+ # (That is, _under_ any superclasses, or at top-level, if there are no superclasses registered)
65
+ handlers = @handlers[:subclass_handlers]
66
+ while (handlers) do
67
+ this_level_subclasses.clear
68
+ # First, identify already-loaded handlers that belong
69
+ # _under_ this one. (That is, they're handlers
70
+ # for subclasses of `error_class`.)
71
+ handlers.each do |err_class, handler|
72
+ if err_class < error_class
73
+ subclasses_handlers[err_class] = handler
74
+ this_level_subclasses << err_class
75
+ end
76
+ end
77
+ # Any handlers that we'll be moving, delete them from this point in the index
78
+ this_level_subclasses.each do |err_class|
79
+ handlers.delete(err_class)
80
+ end
81
+
82
+ # See if any keys in this hash are superclasses of this new class:
83
+ next_index_point = handlers.find { |err_class, handler| error_class < err_class }
84
+ if next_index_point
85
+ handlers = next_index_point[1][:subclass_handlers]
86
+ else
87
+ # this new handler doesn't belong to any sub-handlers,
88
+ # so insert it in the current set of `handlers`
89
+ break
90
+ end
35
91
  end
92
+ # Having found the point at which to insert this handler,
93
+ # register it and merge any subclass handlers back in at this point.
94
+ this_class_handlers = handlers[error_class]
95
+ this_class_handlers[:handler] = error_handler
96
+ this_class_handlers[:subclass_handlers].merge!(subclasses_handlers)
97
+ nil
36
98
  end
37
99
 
38
100
  # Call the given block with the schema's configured error handlers.
@@ -44,8 +106,7 @@ module GraphQL
44
106
  def with_error_handling(ctx)
45
107
  yield
46
108
  rescue StandardError => err
47
- rescues = ctx.schema.rescues
48
- _err_class, handler = rescues.find { |err_class, handler| err.is_a?(err_class) }
109
+ handler = find_handler_for(err.class)
49
110
  if handler
50
111
  runtime_info = ctx.namespace(:interpreter) || {}
51
112
  obj = runtime_info[:current_object]
@@ -54,11 +115,48 @@ module GraphQL
54
115
  if obj.is_a?(GraphQL::Schema::Object)
55
116
  obj = obj.object
56
117
  end
57
- handler.call(err, obj, args, ctx, field)
118
+ handler[:handler].call(err, obj, args, ctx, field)
58
119
  else
59
120
  raise err
60
121
  end
61
122
  end
123
+
124
+ # @return [Proc, nil] The handler for `error_class`, if one was registered on this schema or inherited
125
+ def find_handler_for(error_class)
126
+ handlers = @handlers[:subclass_handlers]
127
+ handler = nil
128
+ while (handlers) do
129
+ _err_class, next_handler = handlers.find { |err_class, handler| error_class <= err_class }
130
+ if next_handler
131
+ handlers = next_handler[:subclass_handlers]
132
+ handler = next_handler
133
+ else
134
+ # Don't reassign `handler` --
135
+ # let the previous assignment carry over outside this block.
136
+ break
137
+ end
138
+ end
139
+
140
+ # check for a handler from a parent class:
141
+ if @schema.superclass.respond_to?(:error_handler) && (parent_errors = @schema.superclass.error_handler)
142
+ parent_handler = parent_errors.find_handler_for(error_class)
143
+ end
144
+
145
+ # If the inherited handler is more specific than the one defined here,
146
+ # use it.
147
+ # If it's a tie (or there is no parent handler), use the one defined here.
148
+ # If there's an inherited one, but not one defined here, use the inherited one.
149
+ # Otherwise, there's no handler for this error, return `nil`.
150
+ if parent_handler && handler && parent_handler[:class] < handler[:class]
151
+ parent_handler
152
+ elsif handler
153
+ handler
154
+ elsif parent_handler
155
+ parent_handler
156
+ else
157
+ nil
158
+ end
159
+ end
62
160
  end
63
161
  end
64
162
  end
@@ -50,24 +50,27 @@ module GraphQL
50
50
  root_type = schema.root_type_for_operation(root_op_type)
51
51
  path = []
52
52
  set_all_interpreter_context(query.root_value, nil, nil, path)
53
- object_proxy = authorized_new(root_type, query.root_value, context, path)
53
+ object_proxy = authorized_new(root_type, query.root_value, context)
54
54
  object_proxy = schema.sync_lazy(object_proxy)
55
+
55
56
  if object_proxy.nil?
56
57
  # Root .authorized? returned false.
57
58
  write_in_response(path, nil)
58
59
  else
59
- gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
60
- # Make the first fiber which will begin execution
61
- @dataloader.append_job {
62
- evaluate_selections(
63
- path,
64
- context.scoped_context,
65
- object_proxy,
66
- root_type,
67
- root_op_type == "mutation",
68
- gathered_selections,
69
- )
70
- }
60
+ resolve_with_directives(object_proxy, root_operation) do # execute query level directives
61
+ gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
62
+ # Make the first fiber which will begin execution
63
+ @dataloader.append_job {
64
+ evaluate_selections(
65
+ path,
66
+ context.scoped_context,
67
+ object_proxy,
68
+ root_type,
69
+ root_op_type == "mutation",
70
+ gathered_selections,
71
+ )
72
+ }
73
+ end
71
74
  end
72
75
  delete_interpreter_context(:current_path)
73
76
  delete_interpreter_context(:current_field)
@@ -193,7 +196,7 @@ module GraphQL
193
196
  object = owner_object
194
197
 
195
198
  if is_introspection
196
- object = authorized_new(field_defn.owner, object, context, next_path)
199
+ object = authorized_new(field_defn.owner, object, context)
197
200
  end
198
201
 
199
202
  total_args_count = field_defn.arguments.size
@@ -246,11 +249,17 @@ module GraphQL
246
249
  # Use this flag to tell Interpreter::Arguments to add itself
247
250
  # to the keyword args hash _before_ freezing everything.
248
251
  extra_args[:argument_details] = :__arguments_add_self
252
+ when :irep_node
253
+ # This is used by `__typename` in order to support the legacy runtime,
254
+ # but it has no use here (and it's always `nil`).
255
+ # Stop adding it here to avoid the overhead of `.merge_extras` below.
249
256
  else
250
257
  extra_args[extra] = field_defn.fetch_extra(extra, context)
251
258
  end
252
259
  end
253
- resolved_arguments = resolved_arguments.merge_extras(extra_args)
260
+ if extra_args.any?
261
+ resolved_arguments = resolved_arguments.merge_extras(extra_args)
262
+ end
254
263
  resolved_arguments.keyword_arguments
255
264
  end
256
265
 
@@ -277,10 +286,7 @@ module GraphQL
277
286
  end
278
287
  after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
279
288
  continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
280
- if RawValue === continue_value
281
- # Write raw value directly to the response without resolving nested objects
282
- write_in_response(next_path, continue_value.resolve)
283
- elsif HALT != continue_value
289
+ if HALT != continue_value
284
290
  continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
285
291
  end
286
292
  end
@@ -332,6 +338,10 @@ module GraphQL
332
338
  continue_value(path, next_value, parent_type, field, is_non_null, ast_node)
333
339
  elsif GraphQL::Execution::Execute::SKIP == value
334
340
  HALT
341
+ elsif value.is_a?(GraphQL::Execution::Interpreter::RawValue)
342
+ # Write raw value directly to the response without resolving nested objects
343
+ write_in_response(path, value.resolve)
344
+ HALT
335
345
  else
336
346
  value
337
347
  end
@@ -371,7 +381,7 @@ module GraphQL
371
381
  end
372
382
  when "OBJECT"
373
383
  object_proxy = begin
374
- authorized_new(current_type, value, context, path)
384
+ authorized_new(current_type, value, context)
375
385
  rescue GraphQL::ExecutionError => err
376
386
  err
377
387
  end
@@ -634,22 +644,8 @@ module GraphQL
634
644
  end
635
645
  end
636
646
 
637
- def authorized_new(type, value, context, path)
638
- trace_payload = { context: context, type: type, object: value, path: path }
639
-
640
- auth_val = context.query.trace("authorized", trace_payload) do
641
- type.authorized_new(value, context)
642
- end
643
-
644
- if context.schema.lazy?(auth_val)
645
- GraphQL::Execution::Lazy.new do
646
- context.query.trace("authorized_lazy", trace_payload) do
647
- context.schema.sync_lazy(auth_val)
648
- end
649
- end
650
- else
651
- auth_val
652
- end
647
+ def authorized_new(type, value, context)
648
+ type.authorized_new(value, context)
653
649
  end
654
650
  end
655
651
  end
@@ -17,7 +17,7 @@ query IntrospectionQuery {
17
17
  name
18
18
  description
19
19
  locations
20
- args {
20
+ args#{include_deprecated_args ? '(includeDeprecated: true)' : ''} {
21
21
  ...InputValue
22
22
  }
23
23
  }