graphql 2.4.5 → 2.5.0

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/visitor.rb +37 -40
  4. data/lib/graphql/analysis.rb +12 -9
  5. data/lib/graphql/autoload.rb +1 -0
  6. data/lib/graphql/backtrace/table.rb +118 -55
  7. data/lib/graphql/backtrace.rb +1 -19
  8. data/lib/graphql/current.rb +5 -0
  9. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  10. data/lib/graphql/dashboard/installable.rb +22 -0
  11. data/lib/graphql/dashboard/limiters.rb +93 -0
  12. data/lib/graphql/dashboard/operation_store.rb +199 -0
  13. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  14. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  15. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  16. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  17. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  18. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  19. data/lib/graphql/dashboard/statics/icon.png +0 -0
  20. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  21. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  24. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  25. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  26. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  36. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  37. data/lib/graphql/dashboard.rb +158 -0
  38. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  39. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  40. data/lib/graphql/dataloader/async_dataloader.rb +17 -5
  41. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  42. data/lib/graphql/dataloader/source.rb +2 -2
  43. data/lib/graphql/dataloader.rb +37 -5
  44. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -4
  45. data/lib/graphql/execution/interpreter/runtime.rb +71 -34
  46. data/lib/graphql/execution/interpreter.rb +12 -3
  47. data/lib/graphql/execution/multiplex.rb +1 -5
  48. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  49. data/lib/graphql/invalid_name_error.rb +1 -1
  50. data/lib/graphql/invalid_null_error.rb +5 -15
  51. data/lib/graphql/language/lexer.rb +7 -3
  52. data/lib/graphql/language/nodes.rb +3 -0
  53. data/lib/graphql/language/parser.rb +15 -8
  54. data/lib/graphql/language/static_visitor.rb +37 -33
  55. data/lib/graphql/language/visitor.rb +59 -55
  56. data/lib/graphql/query.rb +7 -13
  57. data/lib/graphql/railtie.rb +1 -1
  58. data/lib/graphql/schema/argument.rb +7 -8
  59. data/lib/graphql/schema/build_from_definition.rb +99 -53
  60. data/lib/graphql/schema/directive.rb +1 -1
  61. data/lib/graphql/schema/enum.rb +36 -1
  62. data/lib/graphql/schema/enum_value.rb +1 -1
  63. data/lib/graphql/schema/field.rb +2 -2
  64. data/lib/graphql/schema/input_object.rb +16 -16
  65. data/lib/graphql/schema/interface.rb +2 -1
  66. data/lib/graphql/schema/member/has_arguments.rb +13 -5
  67. data/lib/graphql/schema/member/has_dataloader.rb +60 -0
  68. data/lib/graphql/schema/member/has_directives.rb +1 -1
  69. data/lib/graphql/schema/member/has_fields.rb +1 -1
  70. data/lib/graphql/schema/member/has_interfaces.rb +1 -1
  71. data/lib/graphql/schema/member/scoped.rb +1 -1
  72. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  73. data/lib/graphql/schema/member.rb +1 -0
  74. data/lib/graphql/schema/object.rb +17 -8
  75. data/lib/graphql/schema/resolver.rb +6 -5
  76. data/lib/graphql/schema/subscription.rb +50 -4
  77. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  78. data/lib/graphql/schema/visibility/migration.rb +1 -0
  79. data/lib/graphql/schema/visibility/profile.rb +11 -5
  80. data/lib/graphql/schema/visibility.rb +14 -9
  81. data/lib/graphql/schema/warden.rb +14 -1
  82. data/lib/graphql/schema.rb +63 -35
  83. data/lib/graphql/static_validation/all_rules.rb +1 -1
  84. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  85. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  86. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  87. data/lib/graphql/static_validation/validator.rb +6 -1
  88. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  89. data/lib/graphql/subscriptions/event.rb +12 -1
  90. data/lib/graphql/subscriptions/serialize.rb +1 -1
  91. data/lib/graphql/testing/helpers.rb +5 -2
  92. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  93. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  94. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  95. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  96. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  97. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  98. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  99. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  100. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  101. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  102. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  103. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  104. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  105. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  106. data/lib/graphql/tracing/monitor_trace.rb +285 -0
  107. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  108. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  109. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  110. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  111. data/lib/graphql/tracing/null_trace.rb +9 -0
  112. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  113. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  114. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  115. data/lib/graphql/tracing/platform_trace.rb +5 -0
  116. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  117. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  118. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  119. data/lib/graphql/tracing/scout_trace.rb +32 -55
  120. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  121. data/lib/graphql/tracing/sentry_trace.rb +63 -94
  122. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  123. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  124. data/lib/graphql/tracing/trace.rb +111 -1
  125. data/lib/graphql/tracing.rb +31 -30
  126. data/lib/graphql/types/relay/connection_behaviors.rb +1 -1
  127. data/lib/graphql/types/relay/edge_behaviors.rb +1 -1
  128. data/lib/graphql/version.rb +1 -1
  129. data/lib/graphql.rb +4 -29
  130. metadata +144 -11
  131. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  132. data/lib/graphql/backtrace/trace.rb +0 -93
  133. data/lib/graphql/backtrace/tracer.rb +0 -80
  134. data/lib/graphql/schema/null_mask.rb +0 -11
  135. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -61,12 +61,19 @@ module GraphQL
61
61
  # @option kwargs [String] :description, the GraphQL description for this value, present in documentation
62
62
  # @option kwargs [String] :comment, the GraphQL comment for this value, present in documentation
63
63
  # @option kwargs [::Object] :value the translated Ruby value for this object (defaults to `graphql_name`)
64
+ # @option kwargs [::Object] :value_method, the method name to fetch `graphql_name` (defaults to `graphql_name.downcase`)
64
65
  # @option kwargs [String] :deprecation_reason if this object is deprecated, include a message here
66
+ # @param value_method [Symbol, false] A method to generate for this value, or `false` to skip generation
65
67
  # @return [void]
66
68
  # @see {Schema::EnumValue} which handles these inputs by default
67
- def value(*args, **kwargs, &block)
69
+ def value(*args, value_method: nil, **kwargs, &block)
68
70
  kwargs[:owner] = self
69
71
  value = enum_value_class.new(*args, **kwargs, &block)
72
+
73
+ if value_method || (value_methods && value_method != false)
74
+ generate_value_method(value, value_method)
75
+ end
76
+
70
77
  key = value.graphql_name
71
78
  prev_value = own_values[key]
72
79
  case prev_value
@@ -154,6 +161,18 @@ module GraphQL
154
161
  end
155
162
  end
156
163
 
164
+ def value_methods(new_value = NOT_CONFIGURED)
165
+ if NOT_CONFIGURED.equal?(new_value)
166
+ if @value_methods != nil
167
+ @value_methods
168
+ else
169
+ find_inherited_value(:value_methods, false)
170
+ end
171
+ else
172
+ @value_methods = new_value
173
+ end
174
+ end
175
+
157
176
  def kind
158
177
  GraphQL::TypeKinds::ENUM
159
178
  end
@@ -215,6 +234,7 @@ module GraphQL
215
234
  # because they would end up with names like `#<Class0x1234>::UnresolvedValueError` which messes up bug trackers
216
235
  child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
217
236
  end
237
+ child_class.class_exec { @value_methods = nil }
218
238
  super
219
239
  end
220
240
 
@@ -223,6 +243,21 @@ module GraphQL
223
243
  def own_values
224
244
  @own_values ||= {}
225
245
  end
246
+
247
+ def generate_value_method(value, configured_value_method)
248
+ return if configured_value_method == false
249
+
250
+ value_method_name = configured_value_method || value.graphql_name.downcase
251
+
252
+ if respond_to?(value_method_name.to_sym)
253
+ warn "Failed to define value method for :#{value_method_name}, because " \
254
+ "#{value.owner.name || value.owner.graphql_name} already responds to that method. Use `value_method:` to override the method name " \
255
+ "or `value_method: false` to disable Enum value method generation."
256
+ return
257
+ end
258
+
259
+ define_singleton_method(value_method_name) { value.graphql_name }
260
+ end
226
261
  end
227
262
 
228
263
  enum_value_class(GraphQL::Schema::EnumValue)
@@ -48,7 +48,7 @@ module GraphQL
48
48
  end
49
49
 
50
50
  if block_given?
51
- instance_eval(&block)
51
+ instance_exec(self, &block)
52
52
  end
53
53
  end
54
54
 
@@ -255,7 +255,7 @@ module GraphQL
255
255
 
256
256
  @underscored_name = -Member::BuildType.underscore(name_s)
257
257
  @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
258
-
258
+ NameValidator.validate!(@name)
259
259
  @description = description
260
260
  @comment = comment
261
261
  @type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary
@@ -369,7 +369,7 @@ module GraphQL
369
369
  if @definition_block.arity == 1
370
370
  @definition_block.call(self)
371
371
  else
372
- instance_eval(&@definition_block)
372
+ instance_exec(self, &@definition_block)
373
373
  end
374
374
  self.extensions.each(&:after_define_apply)
375
375
  @call_after_define = true
@@ -59,19 +59,12 @@ module GraphQL
59
59
  else
60
60
  new_h = {}
61
61
  keys.each { |k| @ruby_style_hash.key?(k) && new_h[k] = @ruby_style_hash[k] }
62
- new_h
62
+ new_h
63
63
  end
64
64
  end
65
65
 
66
66
  def prepare
67
- if @context
68
- object = @context[:current_object]
69
- # Pass this object's class with `as` so that messages are rendered correctly from inherited validators
70
- Schema::Validator.validate!(self.class.validators, object, @context, @ruby_style_hash, as: self.class)
71
- self
72
- else
73
- self
74
- end
67
+ self
75
68
  end
76
69
 
77
70
  def unwrap_value(value)
@@ -111,6 +104,14 @@ module GraphQL
111
104
  @ruby_style_hash.dup
112
105
  end
113
106
 
107
+ # @api private
108
+ def validate_for(context)
109
+ object = context[:current_object]
110
+ # Pass this object's class with `as` so that messages are rendered correctly from inherited validators
111
+ Schema::Validator.validate!(self.class.validators, object, context, @ruby_style_hash, as: self.class)
112
+ nil
113
+ end
114
+
114
115
  class << self
115
116
  def authorized?(obj, value, ctx)
116
117
  # Authorize each argument (but this doesn't apply if `prepare` is implemented):
@@ -150,14 +151,8 @@ module GraphQL
150
151
  end
151
152
  end
152
153
  # Add a method access
153
- method_name = argument_defn.keyword
154
154
  suppress_redefinition_warning do
155
- class_eval <<-RUBY, __FILE__, __LINE__
156
- def #{method_name}
157
- self[#{method_name.inspect}]
158
- end
159
- alias_method :#{method_name}, :#{method_name}
160
- RUBY
155
+ define_accessor_method(argument_defn.keyword)
161
156
  end
162
157
  argument_defn
163
158
  end
@@ -293,6 +288,11 @@ module GraphQL
293
288
  ensure
294
289
  $VERBOSE = verbose
295
290
  end
291
+
292
+ def define_accessor_method(method_name)
293
+ define_method(method_name) { self[method_name] }
294
+ alias_method(method_name, method_name)
295
+ end
296
296
  end
297
297
 
298
298
  private
@@ -13,6 +13,7 @@ module GraphQL
13
13
  include GraphQL::Schema::Member::Scoped
14
14
  include GraphQL::Schema::Member::HasAstNode
15
15
  include GraphQL::Schema::Member::HasUnresolvedTypeError
16
+ include GraphQL::Schema::Member::HasDataloader
16
17
  include GraphQL::Schema::Member::HasDirectives
17
18
  include GraphQL::Schema::Member::HasInterfaces
18
19
 
@@ -29,7 +30,7 @@ module GraphQL
29
30
  const_set(:DefinitionMethods, defn_methods_module)
30
31
  extend(self::DefinitionMethods)
31
32
  end
32
- self::DefinitionMethods.module_eval(&block)
33
+ self::DefinitionMethods.module_exec(&block)
33
34
  end
34
35
 
35
36
  # @see {Schema::Warden} hides interfaces without visible implementations
@@ -359,7 +359,8 @@ module GraphQL
359
359
  if application_object.nil?
360
360
  nil
361
361
  else
362
- maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context)
362
+ arg_loads_type = argument.loads
363
+ maybe_lazy_resolve_type = context.schema.resolve_type(arg_loads_type, application_object, context)
363
364
  context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result|
364
365
  if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
365
366
  application_object_type, application_object = resolve_type_result
@@ -368,10 +369,17 @@ module GraphQL
368
369
  # application_object is already assigned
369
370
  end
370
371
 
371
- if !(
372
- context.types.possible_types(argument.loads).include?(application_object_type) ||
373
- context.types.loadable?(argument.loads, context)
374
- )
372
+ passes_possible_types_check = if context.types.loadable?(arg_loads_type, context)
373
+ if arg_loads_type.kind.union?
374
+ # This union is used in `loads:` but not otherwise visible to this query
375
+ context.types.loadable_possible_types(arg_loads_type, context).include?(application_object_type)
376
+ else
377
+ true
378
+ end
379
+ else
380
+ context.types.possible_types(arg_loads_type).include?(application_object_type)
381
+ end
382
+ if !passes_possible_types_check
375
383
  err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object)
376
384
  application_object = load_application_object_failed(err)
377
385
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module HasDataloader
7
+ # @return [GraphQL::Dataloader] The dataloader for the currently-running query
8
+ def dataloader
9
+ context.dataloader
10
+ end
11
+
12
+ # A shortcut method for loading a key from a source.
13
+ # Identical to `dataloader.with(source_class, *source_args).load(load_key)`
14
+ # @param source_class [Class<GraphQL::Dataloader::Source>]
15
+ # @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
16
+ # @param load_key [Object] The key to look up using `def fetch`
17
+ def dataload(source_class, *source_args, load_key)
18
+ dataloader.with(source_class, *source_args).load(load_key)
19
+ end
20
+
21
+ # Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}.
22
+ # @param model [Class<ActiveRecord::Base>]
23
+ # @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
24
+ # @param find_by [Symbol, String] A column name to look the record up by. (Defaults to the model's primary key.)
25
+ # @return [ActiveRecord::Base, nil]
26
+ # @example Finding a record by ID
27
+ # dataload_record(Post, 5) # Like `Post.find(5)`, but dataloaded
28
+ # @example Finding a record by another attribute
29
+ # dataload_record(User, "matz", find_by: :handle) # Like `User.find_by(handle: "matz")`, but dataloaded
30
+ def dataload_record(model, find_by_value, find_by: nil)
31
+ source = if find_by
32
+ dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by)
33
+ else
34
+ dataloader.with(Dataloader::ActiveRecordSource, model)
35
+ end
36
+
37
+ source.load(find_by_value)
38
+ end
39
+
40
+ # Look up an associated record using a Rails association.
41
+ # @param association_name [Symbol] A `belongs_to` or `has_one` association. (If a `has_many` association is named here, it will be selected without pagination.)
42
+ # @param record [ActiveRecord::Base] The object that the association belongs to.
43
+ # @param scope [ActiveRecord::Relation] A scope to look up the associated record in
44
+ # @return [ActiveRecord::Base, nil] The associated record, if there is one
45
+ # @example Looking up a belongs_to on the current object
46
+ # dataload_association(:parent) # Equivalent to `object.parent`, but dataloaded
47
+ # @example Looking up an associated record on some other object
48
+ # dataload_association(:post, comment) # Equivalent to `comment.post`, but dataloaded
49
+ def dataload_association(record = object, association_name, scope: nil)
50
+ source = if scope
51
+ dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope)
52
+ else
53
+ dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name)
54
+ end
55
+ source.load(record)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -6,7 +6,7 @@ module GraphQL
6
6
  module HasDirectives
7
7
  def self.extended(child_cls)
8
8
  super
9
- child_cls.module_eval { self.own_directives = nil }
9
+ child_cls.module_exec { self.own_directives = nil }
10
10
  end
11
11
 
12
12
  def inherited(child_cls)
@@ -202,7 +202,7 @@ module GraphQL
202
202
 
203
203
  def inherited(subclass)
204
204
  super
205
- subclass.class_eval do
205
+ subclass.class_exec do
206
206
  @own_fields ||= nil
207
207
  @field_class ||= nil
208
208
  @has_no_fields ||= false
@@ -133,7 +133,7 @@ module GraphQL
133
133
 
134
134
  def inherited(subclass)
135
135
  super
136
- subclass.class_eval do
136
+ subclass.class_exec do
137
137
  @own_interface_type_memberships ||= nil
138
138
  end
139
139
  end
@@ -30,7 +30,7 @@ module GraphQL
30
30
 
31
31
  def inherited(subclass)
32
32
  super
33
- subclass.class_eval do
33
+ subclass.class_exec do
34
34
  @reauthorize_scoped_objects = nil
35
35
  end
36
36
  end
@@ -42,7 +42,7 @@ module GraphQL
42
42
  private
43
43
 
44
44
  def inherited(subclass)
45
- subclass.class_eval do
45
+ subclass.class_exec do
46
46
  @to_non_null_type ||= nil
47
47
  @to_list_type ||= nil
48
48
  end
@@ -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_dataloader'
5
6
  require 'graphql/schema/member/has_directives'
6
7
  require 'graphql/schema/member/has_deprecation_reason'
7
8
  require 'graphql/schema/member/has_interfaces'
@@ -7,6 +7,7 @@ module GraphQL
7
7
  class Object < GraphQL::Schema::Member
8
8
  extend GraphQL::Schema::Member::HasFields
9
9
  extend GraphQL::Schema::Member::HasInterfaces
10
+ include Member::HasDataloader
10
11
 
11
12
  # Raised when an Object doesn't have any field defined and hasn't explicitly opted out of this requirement
12
13
  class FieldsAreRequiredError < GraphQL::Error
@@ -65,20 +66,28 @@ module GraphQL
65
66
  # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
66
67
  # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
67
68
  def authorized_new(object, context)
68
- maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
69
- begin
70
- authorized?(object, context)
71
- rescue GraphQL::UnauthorizedError => err
72
- context.schema.unauthorized_object(err)
73
- rescue StandardError => err
74
- context.query.handle_or_reraise(err)
69
+ context.query.current_trace.begin_authorized(self, object, context)
70
+ begin
71
+ maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
72
+ begin
73
+ authorized?(object, context)
74
+ rescue GraphQL::UnauthorizedError => err
75
+ context.schema.unauthorized_object(err)
76
+ rescue StandardError => err
77
+ context.query.handle_or_reraise(err)
78
+ end
75
79
  end
80
+ ensure
81
+ context.query.current_trace.end_authorized(self, object, context, maybe_lazy_auth_val)
76
82
  end
77
83
 
78
84
  auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
79
85
  GraphQL::Execution::Lazy.new do
86
+ context.query.current_trace.begin_authorized(self, object, context)
80
87
  context.query.current_trace.authorized_lazy(query: context.query, type: self, object: object) do
81
- context.schema.sync_lazy(maybe_lazy_auth_val)
88
+ res = context.schema.sync_lazy(maybe_lazy_auth_val)
89
+ context.query.current_trace.end_authorized(self, object, context, res)
90
+ res
82
91
  end
83
92
  end
84
93
  else
@@ -27,6 +27,7 @@ module GraphQL
27
27
  include Schema::Member::HasPath
28
28
  extend Schema::Member::HasPath
29
29
  extend Schema::Member::HasDirectives
30
+ include Schema::Member::HasDataloader
30
31
 
31
32
  # @param object [Object] The application object that this field is being resolved on
32
33
  # @param context [GraphQL::Query::Context]
@@ -49,11 +50,6 @@ module GraphQL
49
50
  # @return [GraphQL::Query::Context]
50
51
  attr_reader :context
51
52
 
52
- # @return [GraphQL::Dataloader]
53
- def dataloader
54
- context.dataloader
55
- end
56
-
57
53
  # @return [GraphQL::Schema::Field]
58
54
  attr_reader :field
59
55
 
@@ -407,6 +403,11 @@ module GraphQL
407
403
  end
408
404
  end
409
405
 
406
+ def inherited(child_class)
407
+ child_class.description(description)
408
+ super
409
+ end
410
+
410
411
  private
411
412
 
412
413
  attr_reader :own_extensions
@@ -19,13 +19,22 @@ module GraphQL
19
19
  # propagate null.
20
20
  null false
21
21
 
22
+ # @api private
22
23
  def initialize(object:, context:, field:)
23
24
  super
24
25
  # Figure out whether this is an update or an initial subscription
25
26
  @mode = context.query.subscription_update? ? :update : :subscribe
27
+ @subscription_written = false
28
+ @original_arguments = nil
29
+ if (subs_ns = context.namespace(:subscriptions)) &&
30
+ (sub_insts = subs_ns[:subscriptions])
31
+ sub_insts[context.current_path] = self
32
+ end
26
33
  end
27
34
 
35
+ # @api private
28
36
  def resolve_with_support(**args)
37
+ @original_arguments = args # before `loads:` have been run
29
38
  result = nil
30
39
  unsubscribed = true
31
40
  unsubscribed_result = catch :graphql_subscription_unsubscribed do
@@ -46,7 +55,9 @@ module GraphQL
46
55
  end
47
56
  end
48
57
 
49
- # Implement the {Resolve} API
58
+ # Implement the {Resolve} API.
59
+ # You can implement this if you want code to run for _both_ the initial subscription
60
+ # and for later updates. Or, implement {#subscribe} and {#update}
50
61
  def resolve(**args)
51
62
  # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever
52
63
  # have an unexpected `@mode`
@@ -54,6 +65,7 @@ module GraphQL
54
65
  end
55
66
 
56
67
  # Wrap the user-defined `#subscribe` hook
68
+ # @api private
57
69
  def resolve_subscribe(**args)
58
70
  ret_val = !args.empty? ? subscribe(**args) : subscribe
59
71
  if ret_val == :no_response
@@ -71,6 +83,7 @@ module GraphQL
71
83
  end
72
84
 
73
85
  # Wrap the user-provided `#update` hook
86
+ # @api private
74
87
  def resolve_update(**args)
75
88
  ret_val = !args.empty? ? update(**args) : update
76
89
  if ret_val == NO_UPDATE
@@ -106,14 +119,13 @@ module GraphQL
106
119
  throw :graphql_subscription_unsubscribed, update_value
107
120
  end
108
121
 
109
- READING_SCOPE = ::Object.new
110
122
  # Call this method to provide a new subscription_scope; OR
111
123
  # call it without an argument to get the subscription_scope
112
124
  # @param new_scope [Symbol]
113
125
  # @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
114
126
  # @return [Symbol]
115
- def self.subscription_scope(new_scope = READING_SCOPE, optional: false)
116
- if new_scope != READING_SCOPE
127
+ def self.subscription_scope(new_scope = NOT_CONFIGURED, optional: false)
128
+ if new_scope != NOT_CONFIGURED
117
129
  @subscription_scope = new_scope
118
130
  @subscription_scope_optional = optional
119
131
  elsif defined?(@subscription_scope)
@@ -150,6 +162,40 @@ module GraphQL
150
162
  def self.topic_for(arguments:, field:, scope:)
151
163
  Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments])
152
164
  end
165
+
166
+ # Calls through to `schema.subscriptions` to register this subscription with the backend.
167
+ # This is automatically called by GraphQL-Ruby after a query finishes successfully,
168
+ # but if you need to commit the subscription during `#subscribe`, you can call it there.
169
+ # (This method also sets a flag showing that this subscription was already written.)
170
+ #
171
+ # If you call this method yourself, you may also need to {#unsubscribe}
172
+ # or call `subscriptions.delete_subscription` to clean up the database if the query crashes with an error
173
+ # later in execution.
174
+ # @return [void]
175
+ def write_subscription
176
+ if subscription_written?
177
+ raise GraphQL::Error, "`write_subscription` was called but `#{self.class}#subscription_written?` is already true. Remove a call to `write subscription`."
178
+ else
179
+ @subscription_written = true
180
+ context.schema.subscriptions.write_subscription(context.query, [event])
181
+ end
182
+ nil
183
+ end
184
+
185
+ # @return [Boolean] `true` if {#write_subscription} was called already
186
+ def subscription_written?
187
+ @subscription_written
188
+ end
189
+
190
+ # @return [Subscriptions::Event] This object is used as a representation of this subscription for the backend
191
+ def event
192
+ @event ||= Subscriptions::Event.new(
193
+ name: field.name,
194
+ arguments: @original_arguments,
195
+ context: context,
196
+ field: field,
197
+ )
198
+ end
153
199
  end
154
200
  end
155
201
  end
@@ -51,19 +51,36 @@ module GraphQL
51
51
  end
52
52
 
53
53
  def validate(_object, context, value)
54
- matched_conditions = 0
54
+ fully_matched_conditions = 0
55
+ partially_matched_conditions = 0
55
56
 
56
57
  if !value.nil?
57
58
  @one_of.each do |one_of_condition|
58
59
  case one_of_condition
59
60
  when Symbol
60
61
  if value.key?(one_of_condition)
61
- matched_conditions += 1
62
+ fully_matched_conditions += 1
62
63
  end
63
64
  when Array
64
- if one_of_condition.all? { |k| value.key?(k) }
65
- matched_conditions += 1
66
- break
65
+ any_match = false
66
+ full_match = true
67
+
68
+ one_of_condition.each do |k|
69
+ if value.key?(k)
70
+ any_match = true
71
+ else
72
+ full_match = false
73
+ end
74
+ end
75
+
76
+ partial_match = !full_match && any_match
77
+
78
+ if full_match
79
+ fully_matched_conditions += 1
80
+ end
81
+
82
+ if partial_match
83
+ partially_matched_conditions += 1
67
84
  end
68
85
  else
69
86
  raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
@@ -71,7 +88,7 @@ module GraphQL
71
88
  end
72
89
  end
73
90
 
74
- if matched_conditions == 1
91
+ if fully_matched_conditions == 1 && partially_matched_conditions == 0
75
92
  nil # OK
76
93
  else
77
94
  @message || build_message(context)
@@ -112,6 +112,7 @@ module GraphQL
112
112
  :all_types_h,
113
113
  :fields,
114
114
  :loadable?,
115
+ :loadable_possible_types,
115
116
  :type,
116
117
  :arguments,
117
118
  :argument,
@@ -18,7 +18,7 @@ module GraphQL
18
18
  if ctx.respond_to?(:types) && (types = ctx.types).is_a?(self)
19
19
  types
20
20
  else
21
- schema.visibility.profile_for(ctx, nil)
21
+ schema.visibility.profile_for(ctx)
22
22
  end
23
23
  end
24
24
 
@@ -84,6 +84,8 @@ module GraphQL
84
84
  @cached_arguments = Hash.new do |h, owner|
85
85
  h[owner] = non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments)
86
86
  end.compare_by_identity
87
+
88
+ @loadable_possible_types = Hash.new { |h, union_type| h[union_type] = union_type.possible_types }.compare_by_identity
87
89
  end
88
90
 
89
91
  def field_on_visible_interface?(field, owner)
@@ -157,7 +159,7 @@ module GraphQL
157
159
  end
158
160
  end
159
161
  end
160
- visible_f.ensure_loaded
162
+ visible_f&.ensure_loaded
161
163
  elsif f && @cached_visible_fields[owner][f.ensure_loaded]
162
164
  f
163
165
  else
@@ -249,6 +251,10 @@ module GraphQL
249
251
  !@all_types[t.graphql_name] && @cached_visible[t]
250
252
  end
251
253
 
254
+ def loadable_possible_types(t, _ctx)
255
+ @loadable_possible_types[t]
256
+ end
257
+
252
258
  def loaded_types
253
259
  @all_types.values
254
260
  end
@@ -313,9 +319,9 @@ module GraphQL
313
319
  case type.kind.name
314
320
  when "INTERFACE"
315
321
  pts = []
316
- @schema.visibility.all_interface_type_memberships[type].each do |itm|
317
- if @cached_visible[itm] && (ot = itm.object_type) && @cached_visible[ot] && referenced?(ot)
318
- pts << ot
322
+ @schema.visibility.all_interface_type_memberships[type].each do |(itm, impl_type)|
323
+ if @cached_visible[itm] && @cached_visible[impl_type] && referenced?(impl_type)
324
+ pts << impl_type
319
325
  end
320
326
  end
321
327
  pts