graphql 2.5.19 → 2.5.21

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/dashboard/application_controller.rb +41 -0
  3. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  4. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  5. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  6. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +3 -3
  7. data/lib/graphql/dashboard.rb +9 -74
  8. data/lib/graphql/dataloader/null_dataloader.rb +7 -3
  9. data/lib/graphql/execution/multiplex.rb +1 -1
  10. data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
  11. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  12. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  13. data/lib/graphql/execution/next/runner.rb +389 -0
  14. data/lib/graphql/execution/next/selections_step.rb +37 -0
  15. data/lib/graphql/execution/next.rb +69 -0
  16. data/lib/graphql/execution.rb +1 -0
  17. data/lib/graphql/execution_error.rb +13 -10
  18. data/lib/graphql/introspection/directive_type.rb +7 -3
  19. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  20. data/lib/graphql/introspection/entry_points.rb +11 -3
  21. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  22. data/lib/graphql/introspection/field_type.rb +13 -5
  23. data/lib/graphql/introspection/input_value_type.rb +21 -13
  24. data/lib/graphql/introspection/type_type.rb +64 -28
  25. data/lib/graphql/invalid_null_error.rb +11 -5
  26. data/lib/graphql/pagination/connection.rb +2 -0
  27. data/lib/graphql/pagination/connections.rb +32 -0
  28. data/lib/graphql/query/context.rb +3 -2
  29. data/lib/graphql/query/null_context.rb +9 -3
  30. data/lib/graphql/schema/argument.rb +5 -0
  31. data/lib/graphql/schema/build_from_definition.rb +7 -0
  32. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  33. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  34. data/lib/graphql/schema/field.rb +70 -1
  35. data/lib/graphql/schema/field_extension.rb +33 -0
  36. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  37. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  38. data/lib/graphql/schema/member/has_dataloader.rb +17 -0
  39. data/lib/graphql/schema/member/has_fields.rb +5 -1
  40. data/lib/graphql/schema/member.rb +5 -0
  41. data/lib/graphql/schema/object.rb +1 -0
  42. data/lib/graphql/schema/resolver.rb +45 -1
  43. data/lib/graphql/schema/visibility.rb +1 -1
  44. data/lib/graphql/schema.rb +33 -7
  45. data/lib/graphql/subscriptions.rb +1 -1
  46. data/lib/graphql/tracing/perfetto_trace.rb +1 -1
  47. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  48. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  49. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  50. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  51. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  52. data/lib/graphql/unauthorized_error.rb +5 -1
  53. data/lib/graphql/version.rb +1 -1
  54. data/lib/graphql.rb +3 -0
  55. metadata +12 -2
@@ -5,24 +5,33 @@ module GraphQL
5
5
  class Field
6
6
  class ScopeExtension < GraphQL::Schema::FieldExtension
7
7
  def after_resolve(object:, arguments:, context:, value:, memo:)
8
- if value.nil?
9
- value
10
- else
11
- ret_type = @field.type.unwrap
12
- if ret_type.respond_to?(:scope_items)
13
- scoped_items = ret_type.scope_items(value, context)
14
- if !scoped_items.equal?(value) && !ret_type.reauthorize_scoped_objects
15
- if (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
16
- (query_runtime_state = current_runtime_state[context.query])
17
- query_runtime_state.was_authorized_by_scope_items = true
8
+ if object.is_a?(GraphQL::Schema::Object)
9
+ if value.nil?
10
+ value
11
+ else
12
+ return_type = field.type.unwrap
13
+ if return_type.respond_to?(:scope_items)
14
+ scoped_items = return_type.scope_items(value, context)
15
+ if !scoped_items.equal?(value) && !return_type.reauthorize_scoped_objects
16
+ if (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
17
+ (query_runtime_state = current_runtime_state[context.query])
18
+ query_runtime_state.was_authorized_by_scope_items = true
19
+ end
18
20
  end
21
+ scoped_items
22
+ else
23
+ value
19
24
  end
20
- scoped_items
21
- else
22
- value
23
25
  end
26
+ else
27
+ # TODO skip this entirely?
28
+ value
24
29
  end
25
30
  end
31
+
32
+ def after_resolve_next(**kwargs)
33
+ raise "This should never be called -- it's hardcoded in execution instead."
34
+ end
26
35
  end
27
36
  end
28
37
  end
@@ -8,6 +8,7 @@ module GraphQL
8
8
  include GraphQL::Schema::Member::HasArguments
9
9
  include GraphQL::Schema::Member::HasArguments::FieldConfigured
10
10
  include GraphQL::Schema::Member::HasAstNode
11
+ include GraphQL::Schema::Member::HasAuthorization
11
12
  include GraphQL::Schema::Member::HasPath
12
13
  include GraphQL::Schema::Member::HasValidators
13
14
  extend GraphQL::Schema::FindInheritedValue
@@ -192,6 +193,10 @@ module GraphQL
192
193
  # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
193
194
  # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
194
195
  # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
196
+ # @param resolve_static [Symbol, true, nil] Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
197
+ # @param resolve_batch [Symbol, true, nil] Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
198
+ # @param resolve_each [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
199
+ # @param resolve_legacy_instance_method [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class.
195
200
  # @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
196
201
  # @param default_page_size [Integer, nil] For connections, the default number of items to return from this field, or `nil` to return unlimited results.
197
202
  # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
@@ -214,7 +219,7 @@ module GraphQL
214
219
  # @param relay_nodes_field [Boolean] (Private, used by GraphQL-Ruby)
215
220
  # @param extras [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] Extra arguments to be injected into the resolver for this field
216
221
  # @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}.
217
- def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
222
+ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, resolve_legacy_instance_method: nil, resolve_static: nil, resolve_each: nil, resolve_batch: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
218
223
  if name.nil?
219
224
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
220
225
  end
@@ -262,6 +267,33 @@ module GraphQL
262
267
  @method_str = -method_name.to_s
263
268
  @method_sym = method_name.to_sym
264
269
  @resolver_method = (resolver_method || name_s).to_sym
270
+
271
+ if resolve_static
272
+ @execution_next_mode = :resolve_static
273
+ @execution_next_mode_key = resolve_static == true ? @method_sym : resolve_static
274
+ elsif resolve_batch
275
+ @execution_next_mode = :resolve_batch
276
+ @execution_next_mode_key = resolve_batch == true ? @method_sym : resolve_batch
277
+ elsif resolve_each
278
+ @execution_next_mode = :resolve_each
279
+ @execution_next_mode_key = resolve_each == true ? @method_sym : resolve_each
280
+ elsif hash_key
281
+ @execution_next_mode = :hash_key
282
+ @execution_next_mode_key = hash_key
283
+ elsif dig
284
+ @execution_next_mode = :dig
285
+ @execution_next_mode_key = dig
286
+ elsif resolver_class
287
+ @execution_next_mode = :resolver_class
288
+ @execution_next_mode_key = resolver_class
289
+ elsif resolve_legacy_instance_method
290
+ @execution_next_mode = :resolve_legacy_instance_method
291
+ @execution_next_mode_key = resolve_legacy_instance_method == true ? @method_sym : resolve_legacy_instance_method
292
+ else
293
+ @execution_next_mode = :direct_send
294
+ @execution_next_mode_key = @method_sym
295
+ end
296
+
265
297
  @complexity = complexity
266
298
  @dynamic_introspection = dynamic_introspection
267
299
  @return_type_expr = type
@@ -332,6 +364,9 @@ module GraphQL
332
364
  end
333
365
  end
334
366
 
367
+ # @api private
368
+ attr_reader :execution_next_mode_key, :execution_next_mode
369
+
335
370
  # Calls the definition block, if one was given.
336
371
  # This is deferred so that references to the return type
337
372
  # can be lazily evaluated, reducing Rails boot time.
@@ -625,6 +660,13 @@ module GraphQL
625
660
  end
626
661
  end
627
662
 
663
+ def authorizes?(context)
664
+ method(:authorized?).owner != GraphQL::Schema::Field || (
665
+ (args = context.types.arguments(self)) &&
666
+ (args.any? { |a| a.authorizes?(context) })
667
+ )
668
+ end
669
+
628
670
  def authorized?(object, args, context)
629
671
  if @resolver_class
630
672
  # The resolver _instance_ will check itself during `resolve()`
@@ -879,6 +921,33 @@ ERR
879
921
  end
880
922
  end
881
923
 
924
+ public
925
+
926
+ def run_next_extensions_before_resolve(objs, args, ctx, extended, idx: 0, &block)
927
+ extension = @extensions[idx]
928
+ if extension
929
+ extension.resolve_next(objects: objs, arguments: args, context: ctx) do |extended_objs, extended_args, memo|
930
+ if memo
931
+ memos = extended.memos ||= {}
932
+ memos[idx] = memo
933
+ end
934
+
935
+ if (extras = extension.added_extras)
936
+ ae = extended.added_extras ||= []
937
+ ae.concat(extras)
938
+ end
939
+
940
+ extended.object = extended_objs
941
+ extended.arguments = extended_args
942
+ run_next_extensions_before_resolve(extended_objs, extended_args, ctx, extended, idx: idx + 1, &block)
943
+ end
944
+ else
945
+ yield(objs, args)
946
+ end
947
+ end
948
+
949
+ private
950
+
882
951
  def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
883
952
  extension = @extensions[idx]
884
953
  if extension
@@ -134,6 +134,24 @@ module GraphQL
134
134
  yield(object, arguments, nil)
135
135
  end
136
136
 
137
+ # Called before batch-resolving {#field}. It should either:
138
+ #
139
+ # - `yield` values to continue execution; OR
140
+ # - return something else to shortcut field execution.
141
+ #
142
+ # Whatever this method returns will be used for execution.
143
+ #
144
+ # @param objects [Array<Object>] The objects the field is being resolved on
145
+ # @param arguments [Hash] Ruby keyword arguments for resolving this field
146
+ # @param context [Query::Context] the context for this query
147
+ # @yieldparam objects [Array<Object>] The objects to continue resolving the field on. Length must be the same as passed-in `objects:`
148
+ # @yieldparam arguments [Hash] The keyword arguments to continue resolving with
149
+ # @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later
150
+ # @return [Array<Object>] The return value for this field, length matching passed-in `objects:`.
151
+ def resolve_next(objects:, arguments:, context:)
152
+ yield(objects, arguments, nil)
153
+ end
154
+
137
155
  # Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced,
138
156
  # but before the value was added to the GraphQL response.
139
157
  #
@@ -148,6 +166,21 @@ module GraphQL
148
166
  def after_resolve(object:, arguments:, context:, value:, memo:)
149
167
  value
150
168
  end
169
+
170
+ # Called after {#field} was batch-resolved, and after any lazy values (like `Promise`s) were synced,
171
+ # but before the value was added to the GraphQL response.
172
+ #
173
+ # Whatever this hook returns will be used as the return value.
174
+ #
175
+ # @param objects [Array<Object>] The objects the field is being resolved on
176
+ # @param arguments [Hash] Ruby keyword arguments for resolving this field
177
+ # @param context [Query::Context] the context for this query
178
+ # @param values [Array<Object>] Whatever the field returned, one for each of `objects`
179
+ # @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one
180
+ # @return [Array<Object>] The return values for this field, length matching `objects:`.
181
+ def after_resolve_next(objects:, arguments:, context:, values:, memo:)
182
+ values
183
+ end
151
184
  end
152
185
  end
153
186
  end
@@ -130,7 +130,7 @@ module GraphQL
130
130
  true
131
131
  end
132
132
 
133
- def default_relay
133
+ def default_relay?
134
134
  false
135
135
  end
136
136
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Member
5
+ module HasAuthorization
6
+ def self.included(child_class)
7
+ child_class.include(InstanceConfigured)
8
+ end
9
+
10
+ def self.extended(child_class)
11
+ child_class.extend(ClassConfigured)
12
+ child_class.class_exec do
13
+ @authorizes = false
14
+ end
15
+ end
16
+
17
+ def authorized?(object, context)
18
+ true
19
+ end
20
+
21
+ module InstanceConfigured
22
+ def authorizes?(context)
23
+ raise RequiredImplementationMissingError, "#{self.class} must implement #authorizes?(context)"
24
+ end
25
+ end
26
+
27
+ module ClassConfigured
28
+ def authorizes?(context)
29
+ method(:authorized?).owner != GraphQL::Schema::Member::HasAuthorization
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -20,6 +20,23 @@ module GraphQL
20
20
  dataloader.with(source_class, *source_args).load(load_key)
21
21
  end
22
22
 
23
+ # A shortcut method for loading many keys from a source.
24
+ # Identical to `dataloader.with(source_class, *source_args).load_all(load_keys)`
25
+ #
26
+ # @example
27
+ # field :score, Integer, resolve_batch: true
28
+ #
29
+ # def self.score(posts)
30
+ # dataload_all(PostScoreSource, posts.map(&:id))
31
+ # end
32
+ #
33
+ # @param source_class [Class<GraphQL::Dataloader::Source>]
34
+ # @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
35
+ # @param load_keys [Array<Object>] The keys to look up using `def fetch`
36
+ def dataload_all(source_class, *source_args, load_keys)
37
+ dataloader.with(source_class, *source_args).load_all(load_keys)
38
+ end
39
+
23
40
  # Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}.
24
41
  # @param model [Class<ActiveRecord::Base>]
25
42
  # @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
@@ -19,7 +19,11 @@ module GraphQL
19
19
  # @option kwargs [Symbol] :method The method to call on the underlying object to resolve this field (defaults to `name`)
20
20
  # @option kwargs [String, Symbol] :hash_key The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
21
21
  # @option kwargs [Array<String, Symbol>] :dig The nested hash keys to lookup on the underlying hash to resolve this field using dig
22
- # @option kwargs [Symbol] :resolver_method The method on the type to call to resolve this field (defaults to `name`)
22
+ # @option kwargs [Symbol, true] :resolver_method The method on the type to call to resolve this field (defaults to `name`)
23
+ # @option kwargs [Symbol, true] :resolve_static Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
24
+ # @option kwargs [Symbol, true] :resolve_batch Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
25
+ # @option kwargs [Symbol, true] :resolve_each Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
26
+ # @option kwargs [Symbol, true] :resolve_legacy_instance_method Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class.
23
27
  # @option kwargs [Boolean] :connection `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
24
28
  # @option kwargs [Class] :connection_extension The extension to add, to implement connections. If `nil`, no extension is added.
25
29
  # @option kwargs [Integer, nil] :max_page_size For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
@@ -2,6 +2,7 @@
2
2
  require 'graphql/schema/member/base_dsl_methods'
3
3
  require 'graphql/schema/member/graphql_type_names'
4
4
  require 'graphql/schema/member/has_ast_node'
5
+ require 'graphql/schema/member/has_authorization'
5
6
  require 'graphql/schema/member/has_dataloader'
6
7
  require 'graphql/schema/member/has_directives'
7
8
  require 'graphql/schema/member/has_deprecation_reason'
@@ -31,6 +32,10 @@ module GraphQL
31
32
  extend HasPath
32
33
  extend HasAstNode
33
34
  extend HasDirectives
35
+
36
+ def self.authorizes?(_ctx)
37
+ false
38
+ end
34
39
  end
35
40
  end
36
41
  end
@@ -5,6 +5,7 @@ require "graphql/query/null_context"
5
5
  module GraphQL
6
6
  class Schema
7
7
  class Object < GraphQL::Schema::Member
8
+ extend GraphQL::Schema::Member::HasAuthorization
8
9
  extend GraphQL::Schema::Member::HasFields
9
10
  extend GraphQL::Schema::Member::HasInterfaces
10
11
  include Member::HasDataloader
@@ -23,6 +23,7 @@ module GraphQL
23
23
  # Really we only need description & comment from here, but:
24
24
  extend Schema::Member::BaseDSLMethods
25
25
  extend GraphQL::Schema::Member::HasArguments
26
+ extend GraphQL::Schema::Member::HasAuthorization
26
27
  extend GraphQL::Schema::Member::HasValidators
27
28
  include Schema::Member::HasPath
28
29
  extend Schema::Member::HasPath
@@ -45,8 +46,10 @@ module GraphQL
45
46
  @prepared_arguments = nil
46
47
  end
47
48
 
49
+ attr_accessor :exec_result, :exec_index, :field_resolve_step
50
+
48
51
  # @return [Object] The application object this field is being resolved on
49
- attr_reader :object
52
+ attr_accessor :object
50
53
 
51
54
  # @return [GraphQL::Query::Context]
52
55
  attr_reader :context
@@ -54,6 +57,47 @@ module GraphQL
54
57
  # @return [GraphQL::Schema::Field]
55
58
  attr_reader :field
56
59
 
60
+ attr_writer :prepared_arguments
61
+
62
+ def call
63
+ if self.class < Schema::HasSingleInputArgument
64
+ @prepared_arguments = @prepared_arguments[:input]
65
+ end
66
+ q = context.query
67
+ trace_objs = [object]
68
+ q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q)
69
+ is_authed, new_return_value = authorized?(**@prepared_arguments)
70
+
71
+ if (runner = @field_resolve_step.runner).resolves_lazies && runner.schema.lazy?(is_authed)
72
+ is_authed, new_return_value = runner.schema.sync_lazy(is_authed)
73
+ end
74
+
75
+ result = if is_authed
76
+ Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field)
77
+ call_resolve(@prepared_arguments)
78
+ else
79
+ new_return_value
80
+ end
81
+ q = context.query
82
+ q.current_trace.end_execute_field(field, @prepared_arguments, trace_objs, q, [result])
83
+
84
+ exec_result[exec_index] = result
85
+ rescue RuntimeError => err
86
+ exec_result[exec_index] = err
87
+ rescue StandardError => stderr
88
+ exec_result[exec_index] = begin
89
+ context.query.handle_or_reraise(stderr)
90
+ rescue GraphQL::ExecutionError => ex_err
91
+ ex_err
92
+ end
93
+ ensure
94
+ field_pending_steps = field_resolve_step.pending_steps
95
+ field_pending_steps.delete(self)
96
+ if field_pending_steps.size == 0 && field_resolve_step.field_results
97
+ field_resolve_step.runner.add_step(field_resolve_step)
98
+ end
99
+ end
100
+
57
101
  def arguments
58
102
  @prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
59
103
  end
@@ -191,7 +191,7 @@ module GraphQL
191
191
  if refresh
192
192
  @top_level_profile = nil
193
193
  end
194
- @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema, visibility: self)
194
+ @top_level_profile ||= @schema.visibility_profile_class.new(context: @schema.null_context, schema: @schema, visibility: self)
195
195
  end
196
196
 
197
197
  private
@@ -330,10 +330,16 @@ module GraphQL
330
330
  find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
331
331
  end
332
332
 
333
+ attr_writer :null_context
334
+
335
+ def null_context
336
+ @null_context || GraphQL::Query::NullContext.instance
337
+ end
338
+
333
339
  # Build a map of `{ name => type }` and return it
334
340
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
335
341
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
336
- def types(context = GraphQL::Query::NullContext.instance)
342
+ def types(context = null_context)
337
343
  if use_visibility_profile?
338
344
  types = Visibility::Profile.from_context(context, self)
339
345
  return types.all_types_h
@@ -366,7 +372,7 @@ module GraphQL
366
372
  # @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
367
373
  # @param use_visibility_profile Private, for migration to {Schema::Visibility}
368
374
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
369
- def get_type(type_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
375
+ def get_type(type_name, context = null_context, use_visibility_profile = use_visibility_profile?)
370
376
  if use_visibility_profile
371
377
  profile = Visibility::Profile.from_context(context, self)
372
378
  return profile.type(type_name)
@@ -617,7 +623,7 @@ module GraphQL
617
623
  # @param use_visibility_profile Private, for migration to {Schema::Visibility}
618
624
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
619
625
  # @return [Array<Module>] Possible types for `type`, if it's given.
620
- def possible_types(type = nil, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
626
+ def possible_types(type = nil, context = null_context, use_visibility_profile = use_visibility_profile?)
621
627
  if use_visibility_profile
622
628
  if type
623
629
  return Visibility::Profile.from_context(context, self).possible_types(type)
@@ -701,7 +707,7 @@ module GraphQL
701
707
  GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
702
708
  end
703
709
 
704
- def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
710
+ def get_field(type_or_name, field_name, context = null_context, use_visibility_profile = use_visibility_profile?)
705
711
  if use_visibility_profile
706
712
  profile = Visibility::Profile.from_context(context, self)
707
713
  parent_type = case type_or_name
@@ -738,7 +744,7 @@ module GraphQL
738
744
  end
739
745
  end
740
746
 
741
- def get_fields(type, context = GraphQL::Query::NullContext.instance)
747
+ def get_fields(type, context = null_context)
742
748
  type.fields(context)
743
749
  end
744
750
 
@@ -1228,6 +1234,7 @@ module GraphQL
1228
1234
  vis = self.visibility
1229
1235
  child_class.visibility = vis.dup_for(child_class)
1230
1236
  end
1237
+ child_class.null_context = Query::NullContext.new(schema: child_class)
1231
1238
  super
1232
1239
  end
1233
1240
 
@@ -1329,10 +1336,11 @@ module GraphQL
1329
1336
  def type_error(type_error, context)
1330
1337
  case type_error
1331
1338
  when GraphQL::InvalidNullError
1332
- execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
1333
- execution_error.path = context[:current_path]
1339
+ execution_error = GraphQL::ExecutionError.new(type_error.message, ast_nodes: type_error.ast_nodes)
1340
+ execution_error.path = type_error.path || context[:current_path]
1334
1341
 
1335
1342
  context.errors << execution_error
1343
+ execution_error
1336
1344
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
1337
1345
  raise type_error
1338
1346
  when GraphQL::IntegerDecodingError
@@ -1354,6 +1362,24 @@ module GraphQL
1354
1362
  lazy_methods.set(lazy_class, value_method)
1355
1363
  end
1356
1364
 
1365
+ def uses_raw_value?
1366
+ !!@uses_raw_value
1367
+ end
1368
+
1369
+ def uses_raw_value(new_val)
1370
+ @uses_raw_value = new_val
1371
+ end
1372
+
1373
+ def resolves_lazies?
1374
+ lazy_method_count = 0
1375
+ lazy_methods.each do |k, v|
1376
+ if !v.nil?
1377
+ lazy_method_count += 1
1378
+ end
1379
+ end
1380
+ lazy_method_count > 2
1381
+ end
1382
+
1357
1383
  def instrument(instrument_step, instrumenter, options = {})
1358
1384
  warn <<~WARN
1359
1385
  Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html"
@@ -80,7 +80,7 @@ module GraphQL
80
80
 
81
81
  # Normalize symbol-keyed args to strings, try camelizing them
82
82
  # Should this accept a real context somehow?
83
- normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext.instance)
83
+ normalized_args = normalize_arguments(normalized_event_name, field, args, @schema.null_context)
84
84
 
85
85
  event = Subscriptions::Event.new(
86
86
  name: normalized_event_name,
@@ -682,7 +682,7 @@ module GraphQL
682
682
  when GraphQL::Schema::InputObject
683
683
  payload_to_debug(k, v.to_h, iid: iid, intern_value: intern_value)
684
684
  else
685
- class_name_iid = @interned_da_string_values[v.class.name]
685
+ class_name_iid = @interned_da_string_values[(v.class.name || "(anonymous)")]
686
686
  da = [
687
687
  debug_annotation(DA_DEBUG_INSPECT_CLASS_IID, :string_value_iid, class_name_iid),
688
688
  ]
@@ -196,18 +196,20 @@ module GraphQL
196
196
  def edges
197
197
  # Assume that whatever authorization needed to happen
198
198
  # already happened at the connection level.
199
- current_runtime_state = Fiber[:__graphql_runtime_info]
200
- query_runtime_state = current_runtime_state[context.query]
201
- query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
199
+ if (current_runtime_state = Fiber[:__graphql_runtime_info])
200
+ query_runtime_state = current_runtime_state[context.query]
201
+ query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
202
+ end
202
203
  @object.edges
203
204
  end
204
205
 
205
206
  def nodes
206
207
  # Assume that whatever authorization needed to happen
207
208
  # already happened at the connection level.
208
- current_runtime_state = Fiber[:__graphql_runtime_info]
209
- query_runtime_state = current_runtime_state[context.query]
210
- query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
209
+ if (current_runtime_state = Fiber[:__graphql_runtime_info])
210
+ query_runtime_state = current_runtime_state[context.query]
211
+ query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
212
+ end
211
213
  @object.nodes
212
214
  end
213
215
  end
@@ -14,9 +14,10 @@ module GraphQL
14
14
  end
15
15
 
16
16
  def node
17
- current_runtime_state = Fiber[:__graphql_runtime_info]
18
- query_runtime_state = current_runtime_state[context.query]
19
- query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
17
+ if (current_runtime_state = Fiber[:__graphql_runtime_info])
18
+ query_runtime_state = current_runtime_state[context.query]
19
+ query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
20
+ end
20
21
  @object.node
21
22
  end
22
23
 
@@ -7,6 +7,17 @@ module GraphQL
7
7
  module HasNodeField
8
8
  def self.included(child_class)
9
9
  child_class.field(**field_options, &field_block)
10
+ child_class.extend(ExecutionMethods)
11
+ end
12
+
13
+ module ExecutionMethods
14
+ def get_relay_node(context, id:)
15
+ context.schema.object_from_id(id, context)
16
+ end
17
+ end
18
+
19
+ def get_relay_node(id:)
20
+ self.class.get_relay_node(context, id: id)
10
21
  end
11
22
 
12
23
  class << self
@@ -17,6 +28,8 @@ module GraphQL
17
28
  null: true,
18
29
  description: "Fetches an object given its ID.",
19
30
  relay_node_field: true,
31
+ resolver_method: :get_relay_node,
32
+ resolve_static: :get_relay_node,
20
33
  }
21
34
  end
22
35
 
@@ -24,14 +37,6 @@ module GraphQL
24
37
  Proc.new {
25
38
  argument :id, "ID!",
26
39
  description: "ID of the object."
27
-
28
- def resolve(obj, args, ctx)
29
- ctx.schema.object_from_id(args[:id], ctx)
30
- end
31
-
32
- def resolve_field(obj, args, ctx)
33
- resolve(obj, args, ctx)
34
- end
35
40
  }
36
41
  end
37
42
  end
@@ -7,6 +7,17 @@ module GraphQL
7
7
  module HasNodesField
8
8
  def self.included(child_class)
9
9
  child_class.field(**field_options, &field_block)
10
+ child_class.extend(ExecutionMethods)
11
+ end
12
+
13
+ module ExecutionMethods
14
+ def get_relay_nodes(context, ids:)
15
+ ids.map { |id| context.schema.object_from_id(id, context) }
16
+ end
17
+ end
18
+
19
+ def get_relay_nodes(ids:)
20
+ self.class.get_relay_nodes(context, ids: ids)
10
21
  end
11
22
 
12
23
  class << self
@@ -17,6 +28,8 @@ module GraphQL
17
28
  null: false,
18
29
  description: "Fetches a list of objects given a list of IDs.",
19
30
  relay_nodes_field: true,
31
+ resolver_method: :get_relay_nodes,
32
+ resolve_static: :get_relay_nodes
20
33
  }
21
34
  end
22
35
 
@@ -24,14 +37,6 @@ module GraphQL
24
37
  Proc.new {
25
38
  argument :ids, "[ID!]!",
26
39
  description: "IDs of the objects."
27
-
28
- def resolve(obj, args, ctx)
29
- args[:ids].map { |id| ctx.schema.object_from_id(id, ctx) }
30
- end
31
-
32
- def resolve_field(obj, args, ctx)
33
- resolve(obj, args, ctx)
34
- end
35
40
  }
36
41
  end
37
42
  end