graphql 1.12.5 → 1.12.10

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 (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
  }