graphql 2.4.3 → 2.4.13

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 (141) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/visitor.rb +38 -41
  4. data/lib/graphql/analysis.rb +15 -12
  5. data/lib/graphql/autoload.rb +38 -0
  6. data/lib/graphql/backtrace/table.rb +95 -55
  7. data/lib/graphql/backtrace.rb +1 -19
  8. data/lib/graphql/current.rb +6 -1
  9. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  10. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  11. data/lib/graphql/dashboard/statics/dashboard.css +3 -0
  12. data/lib/graphql/dashboard/statics/dashboard.js +78 -0
  13. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  14. data/lib/graphql/dashboard/statics/icon.png +0 -0
  15. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  16. data/lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb +63 -0
  17. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +60 -0
  18. data/lib/graphql/dashboard.rb +142 -0
  19. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  20. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  21. data/lib/graphql/dataloader/async_dataloader.rb +21 -9
  22. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  23. data/lib/graphql/dataloader/source.rb +3 -3
  24. data/lib/graphql/dataloader.rb +43 -14
  25. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  26. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -4
  27. data/lib/graphql/execution/interpreter/runtime.rb +67 -40
  28. data/lib/graphql/execution/interpreter.rb +16 -6
  29. data/lib/graphql/execution/multiplex.rb +0 -4
  30. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  31. data/lib/graphql/invalid_name_error.rb +1 -1
  32. data/lib/graphql/invalid_null_error.rb +5 -15
  33. data/lib/graphql/language/cache.rb +13 -0
  34. data/lib/graphql/language/document_from_schema_definition.rb +8 -7
  35. data/lib/graphql/language/lexer.rb +11 -4
  36. data/lib/graphql/language/nodes.rb +3 -0
  37. data/lib/graphql/language/parser.rb +2 -2
  38. data/lib/graphql/language/printer.rb +8 -8
  39. data/lib/graphql/language/static_visitor.rb +37 -33
  40. data/lib/graphql/language/visitor.rb +59 -55
  41. data/lib/graphql/pagination/connection.rb +1 -1
  42. data/lib/graphql/query/context/scoped_context.rb +1 -1
  43. data/lib/graphql/query/context.rb +6 -5
  44. data/lib/graphql/query/variable_validation_error.rb +1 -1
  45. data/lib/graphql/query.rb +20 -22
  46. data/lib/graphql/railtie.rb +7 -0
  47. data/lib/graphql/schema/addition.rb +1 -1
  48. data/lib/graphql/schema/argument.rb +3 -5
  49. data/lib/graphql/schema/build_from_definition.rb +8 -7
  50. data/lib/graphql/schema/directive/flagged.rb +1 -1
  51. data/lib/graphql/schema/directive.rb +2 -2
  52. data/lib/graphql/schema/enum.rb +36 -1
  53. data/lib/graphql/schema/enum_value.rb +1 -1
  54. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  55. data/lib/graphql/schema/field.rb +12 -12
  56. data/lib/graphql/schema/field_extension.rb +1 -1
  57. data/lib/graphql/schema/has_single_input_argument.rb +3 -1
  58. data/lib/graphql/schema/input_object.rb +70 -34
  59. data/lib/graphql/schema/interface.rb +3 -2
  60. data/lib/graphql/schema/loader.rb +1 -1
  61. data/lib/graphql/schema/member/has_arguments.rb +25 -17
  62. data/lib/graphql/schema/member/has_dataloader.rb +60 -0
  63. data/lib/graphql/schema/member/has_directives.rb +4 -4
  64. data/lib/graphql/schema/member/has_fields.rb +19 -1
  65. data/lib/graphql/schema/member/has_interfaces.rb +5 -5
  66. data/lib/graphql/schema/member/has_validators.rb +1 -1
  67. data/lib/graphql/schema/member/scoped.rb +1 -1
  68. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  69. data/lib/graphql/schema/member.rb +1 -0
  70. data/lib/graphql/schema/object.rb +25 -8
  71. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  72. data/lib/graphql/schema/resolver.rb +11 -10
  73. data/lib/graphql/schema/subscription.rb +52 -6
  74. data/lib/graphql/schema/union.rb +1 -1
  75. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  76. data/lib/graphql/schema/validator.rb +1 -1
  77. data/lib/graphql/schema/visibility/migration.rb +1 -0
  78. data/lib/graphql/schema/visibility/profile.rb +69 -237
  79. data/lib/graphql/schema/visibility/visit.rb +190 -0
  80. data/lib/graphql/schema/visibility.rb +169 -28
  81. data/lib/graphql/schema/warden.rb +18 -5
  82. data/lib/graphql/schema.rb +90 -43
  83. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  84. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +1 -1
  85. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  86. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  87. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  88. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  89. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  90. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  91. data/lib/graphql/static_validation/validation_context.rb +1 -0
  92. data/lib/graphql/static_validation/validator.rb +6 -1
  93. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  94. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  95. data/lib/graphql/subscriptions/event.rb +12 -1
  96. data/lib/graphql/subscriptions/serialize.rb +1 -1
  97. data/lib/graphql/subscriptions.rb +1 -1
  98. data/lib/graphql/testing/helpers.rb +2 -2
  99. data/lib/graphql/tracing/active_support_notifications_trace.rb +7 -3
  100. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  101. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  102. data/lib/graphql/tracing/appoptics_tracing.rb +2 -0
  103. data/lib/graphql/tracing/appsignal_trace.rb +12 -0
  104. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  105. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  106. data/lib/graphql/tracing/data_dog_trace.rb +11 -0
  107. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  108. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  109. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  110. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  111. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  112. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  113. data/lib/graphql/tracing/new_relic_trace.rb +164 -41
  114. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  115. data/lib/graphql/tracing/notifications_trace.rb +4 -0
  116. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  117. data/lib/graphql/tracing/null_trace.rb +9 -0
  118. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  119. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  120. data/lib/graphql/tracing/perfetto_trace.rb +737 -0
  121. data/lib/graphql/tracing/platform_trace.rb +5 -0
  122. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  123. data/lib/graphql/tracing/prometheus_trace.rb +31 -0
  124. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  125. data/lib/graphql/tracing/scout_trace.rb +11 -0
  126. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  127. data/lib/graphql/tracing/sentry_trace.rb +11 -0
  128. data/lib/graphql/tracing/statsd_trace.rb +15 -0
  129. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  130. data/lib/graphql/tracing/trace.rb +128 -1
  131. data/lib/graphql/tracing.rb +30 -30
  132. data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
  133. data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
  134. data/lib/graphql/types.rb +18 -11
  135. data/lib/graphql/version.rb +1 -1
  136. data/lib/graphql.rb +55 -47
  137. metadata +152 -10
  138. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  139. data/lib/graphql/backtrace/trace.rb +0 -93
  140. data/lib/graphql/backtrace/tracer.rb +0 -80
  141. data/lib/graphql/schema/null_mask.rb +0 -11
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/schema/visibility/profile"
3
3
  require "graphql/schema/visibility/migration"
4
+ require "graphql/schema/visibility/visit"
4
5
 
5
6
  module GraphQL
6
7
  class Schema
@@ -12,41 +13,80 @@ module GraphQL
12
13
  # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?`)
13
14
  # @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
14
15
  def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails) ? Rails.env.production? : nil), migration_errors: false)
16
+ profiles&.each { |name, ctx|
17
+ ctx[:visibility_profile] = name
18
+ ctx.freeze
19
+ }
15
20
  schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
21
+ if preload
22
+ schema.visibility.preload
23
+ end
16
24
  end
17
25
 
18
26
  def initialize(schema, dynamic:, preload:, profiles:, migration_errors:)
19
27
  @schema = schema
20
28
  schema.use_visibility_profile = true
21
- if migration_errors
22
- schema.visibility_profile_class = Migration
29
+ schema.visibility_profile_class = if migration_errors
30
+ Visibility::Migration
31
+ else
32
+ Visibility::Profile
23
33
  end
24
34
  @preload = preload
25
35
  @profiles = profiles
26
36
  @cached_profiles = {}
27
37
  @dynamic = dynamic
28
38
  @migration_errors = migration_errors
29
- if preload
30
- # Traverse the schema now (and in the *_configured hooks below)
31
- # To make sure things are loaded during boot
32
- @preloaded_types = Set.new
33
- types_to_visit = [
34
- @schema.query,
35
- @schema.mutation,
36
- @schema.subscription,
37
- *@schema.introspection_system.types.values,
38
- *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
39
- *@schema.orphan_types,
40
- ]
41
- # Root types may have been nil:
42
- types_to_visit.compact!
43
- ensure_all_loaded(types_to_visit)
44
-
45
- profiles.each do |profile_name, example_ctx|
46
- example_ctx[:visibility_profile] = profile_name
47
- prof = profile_for(example_ctx, profile_name)
48
- prof.all_types # force loading
49
- end
39
+ # Top-level type caches:
40
+ @visit = nil
41
+ @interface_type_memberships = nil
42
+ @directives = nil
43
+ @types = nil
44
+ @all_references = nil
45
+ @loaded_all = false
46
+ end
47
+
48
+ def all_directives
49
+ load_all
50
+ @directives
51
+ end
52
+
53
+ def all_interface_type_memberships
54
+ load_all
55
+ @interface_type_memberships
56
+ end
57
+
58
+ def all_references
59
+ load_all
60
+ @all_references
61
+ end
62
+
63
+ def get_type(type_name)
64
+ load_all
65
+ @types[type_name]
66
+ end
67
+
68
+ def preload?
69
+ @preload
70
+ end
71
+
72
+ def preload
73
+ # Traverse the schema now (and in the *_configured hooks below)
74
+ # To make sure things are loaded during boot
75
+ @preloaded_types = Set.new
76
+ types_to_visit = [
77
+ @schema.query,
78
+ @schema.mutation,
79
+ @schema.subscription,
80
+ *@schema.introspection_system.types.values,
81
+ *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
82
+ *@schema.orphan_types,
83
+ ]
84
+ # Root types may have been nil:
85
+ types_to_visit.compact!
86
+ ensure_all_loaded(types_to_visit)
87
+ @profiles.each do |profile_name, example_ctx|
88
+ prof = profile_for(example_ctx)
89
+ prof.all_types # force loading
50
90
  end
51
91
  end
52
92
 
@@ -108,8 +148,8 @@ module GraphQL
108
148
 
109
149
  attr_reader :cached_profiles
110
150
 
111
- def profile_for(context, visibility_profile)
112
- if @profiles.any?
151
+ def profile_for(context, visibility_profile = context[:visibility_profile])
152
+ if !@profiles.empty?
113
153
  if visibility_profile.nil?
114
154
  if @dynamic
115
155
  if context.is_a?(Query::NullContext)
@@ -117,13 +157,14 @@ module GraphQL
117
157
  else
118
158
  @schema.visibility_profile_class.new(context: context, schema: @schema)
119
159
  end
120
- elsif @profiles.any?
160
+ elsif !@profiles.empty?
121
161
  raise ArgumentError, "#{@schema} expects a visibility profile, but `visibility_profile:` wasn't passed. Provide a `visibility_profile:` value or add `dynamic: true` to your visibility configuration."
122
162
  end
123
163
  elsif !@profiles.include?(visibility_profile)
124
164
  raise ArgumentError, "`#{visibility_profile.inspect}` isn't allowed for `visibility_profile:` (must be one of #{@profiles.keys.map(&:inspect).join(", ")}). Or, add `#{visibility_profile.inspect}` to the list of profiles in the schema definition."
125
165
  else
126
- @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: context, schema: @schema)
166
+ profile_ctx = @profiles[visibility_profile]
167
+ @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: profile_ctx, schema: @schema)
127
168
  end
128
169
  elsif context.is_a?(Query::NullContext)
129
170
  top_level_profile
@@ -132,7 +173,10 @@ module GraphQL
132
173
  end
133
174
  end
134
175
 
135
- private
176
+ attr_reader :top_level
177
+
178
+ # @api private
179
+ attr_reader :unfiltered_interface_type_memberships
136
180
 
137
181
  def top_level_profile(refresh: false)
138
182
  if refresh
@@ -141,6 +185,8 @@ module GraphQL
141
185
  @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema)
142
186
  end
143
187
 
188
+ private
189
+
144
190
  def ensure_all_loaded(types_to_visit)
145
191
  while (type = types_to_visit.shift)
146
192
  if type.kind.fields? && @preloaded_types.add?(type)
@@ -153,6 +199,101 @@ module GraphQL
153
199
  top_level_profile(refresh: true)
154
200
  nil
155
201
  end
202
+
203
+ def load_all(types: nil)
204
+ if @visit.nil?
205
+ # Set up the visit system
206
+ @interface_type_memberships = Hash.new { |h, interface_type| h[interface_type] = [] }.compare_by_identity
207
+ @directives = []
208
+ @types = {} # String => Module
209
+ @all_references = Hash.new { |h, member| h[member] = Set.new.compare_by_identity }.compare_by_identity
210
+ @unions_for_references = Set.new
211
+ @visit = Visibility::Visit.new(@schema) do |member|
212
+ if member.is_a?(Module)
213
+ type_name = member.graphql_name
214
+ if (prev_t = @types[type_name])
215
+ if prev_t.is_a?(Array)
216
+ prev_t << member
217
+ else
218
+ @types[type_name] = [member, prev_t]
219
+ end
220
+ else
221
+ @types[member.graphql_name] = member
222
+ end
223
+ member.directives.each { |dir| @all_references[dir.class] << member }
224
+ if member < GraphQL::Schema::Directive
225
+ @directives << member
226
+ elsif member.respond_to?(:interface_type_memberships)
227
+ member.interface_type_memberships.each do |itm|
228
+ @all_references[itm.abstract_type] << member
229
+ # `itm.object_type` may not actually be `member` if this implementation
230
+ # is inherited from a superclass
231
+ @interface_type_memberships[itm.abstract_type] << [itm, member]
232
+ end
233
+ elsif member < GraphQL::Schema::Union
234
+ @unions_for_references << member
235
+ end
236
+ elsif member.is_a?(GraphQL::Schema::Argument)
237
+ member.validate_default_value
238
+ @all_references[member.type.unwrap] << member
239
+ if !(dirs = member.directives).empty?
240
+ dir_owner = member.owner
241
+ if dir_owner.respond_to?(:owner)
242
+ dir_owner = dir_owner.owner
243
+ end
244
+ dirs.each { |dir| @all_references[dir.class] << dir_owner }
245
+ end
246
+ elsif member.is_a?(GraphQL::Schema::Field)
247
+ @all_references[member.type.unwrap] << member
248
+ if !(dirs = member.directives).empty?
249
+ dir_owner = member.owner
250
+ dirs.each { |dir| @all_references[dir.class] << dir_owner }
251
+ end
252
+ elsif member.is_a?(GraphQL::Schema::EnumValue)
253
+ if !(dirs = member.directives).empty?
254
+ dir_owner = member.owner
255
+ dirs.each { |dir| @all_references[dir.class] << dir_owner }
256
+ end
257
+ end
258
+ true
259
+ end
260
+
261
+ @schema.root_types.each { |t| @all_references[t] << true }
262
+ @schema.introspection_system.types.each_value { |t| @all_references[t] << true }
263
+ @schema.directives.each_value { |dir_class| @all_references[dir_class] << true }
264
+
265
+ @visit.visit_each(types: []) # visit default directives
266
+ end
267
+
268
+ if types
269
+ @visit.visit_each(types: types, directives: [])
270
+ elsif @loaded_all == false
271
+ @loaded_all = true
272
+ @visit.visit_each
273
+ else
274
+ # already loaded all
275
+ return
276
+ end
277
+
278
+ # TODO: somehow don't iterate over all these,
279
+ # only the ones that may have been modified
280
+ @interface_type_memberships.each do |int_type, type_membership_pairs|
281
+ referers = @all_references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) }
282
+ if !referers.empty?
283
+ type_membership_pairs.each do |(type_membership, impl_type)|
284
+ # Add new items only:
285
+ @all_references[impl_type] |= referers
286
+ end
287
+ end
288
+ end
289
+
290
+ @unions_for_references.each do |union_type|
291
+ refs = @all_references[union_type]
292
+ union_type.all_possible_types.each do |object_type|
293
+ @all_references[object_type] |= refs # Add new items
294
+ end
295
+ end
296
+ end
156
297
  end
157
298
  end
158
299
  end
@@ -72,6 +72,7 @@ module GraphQL
72
72
  def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
73
73
  def arguments(owner, ctx); owner.arguments(ctx); end
74
74
  def loadable?(type, ctx); type.visible?(ctx); end
75
+ def loadable_possible_types(type, ctx); type.possible_types(ctx); end
75
76
  def visibility_profile
76
77
  @visibility_profile ||= Warden::VisibilityProfile.new(self)
77
78
  end
@@ -106,6 +107,7 @@ module GraphQL
106
107
  def get_field(parent_type, field_name); @schema.get_field(parent_type, field_name); end
107
108
  def reachable_type?(type_name); true; end
108
109
  def loadable?(type, _ctx); true; end
110
+ def loadable_possible_types(union_type, _ctx); union_type.possible_types; end
109
111
  def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop
110
112
  def possible_types(type_defn); @schema.possible_types(type_defn, Query::NullContext.instance, false); end
111
113
  def interfaces(obj_type); obj_type.interfaces; end
@@ -180,6 +182,10 @@ module GraphQL
180
182
  @warden.loadable?(t, ctx)
181
183
  end
182
184
 
185
+ def loadable_possible_types(t, ctx)
186
+ @warden.loadable_possible_types(t, ctx)
187
+ end
188
+
183
189
  def reachable_type?(type_name)
184
190
  !!@warden.reachable_type?(type_name)
185
191
  end
@@ -204,7 +210,7 @@ module GraphQL
204
210
  @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
205
211
  @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
206
212
  @visible_and_reachable_type = @unions = @unfiltered_interfaces =
207
- @reachable_type_set = @visibility_profile =
213
+ @reachable_type_set = @visibility_profile = @loadable_possible_types =
208
214
  nil
209
215
  @skip_warning = schema.plugins.any? { |(plugin, _opts)| plugin == GraphQL::Schema::Warden }
210
216
  end
@@ -229,6 +235,13 @@ module GraphQL
229
235
  !reachable_type_set.include?(type) && visible_type?(type)
230
236
  end
231
237
 
238
+ def loadable_possible_types(union_type, _ctx)
239
+ @loadable_possible_types ||= read_through do |t|
240
+ t.possible_types # unfiltered
241
+ end
242
+ @loadable_possible_types[union_type]
243
+ end
244
+
232
245
  # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
233
246
  def get_type(type_name)
234
247
  @visible_types ||= read_through do |name|
@@ -297,7 +310,7 @@ module GraphQL
297
310
  def arguments(argument_owner, ctx = nil)
298
311
  @visible_arguments ||= read_through { |o|
299
312
  args = o.arguments(@context)
300
- if args.any?
313
+ if !args.empty?
301
314
  args = args.values
302
315
  args.select! { |a| visible_argument?(a, @context) }
303
316
  args
@@ -329,7 +342,7 @@ module GraphQL
329
342
  def interfaces(obj_type)
330
343
  @visible_interfaces ||= read_through { |t|
331
344
  ints = t.interfaces(@context)
332
- if ints.any?
345
+ if !ints.empty?
333
346
  ints.select! { |i| visible_type?(i) }
334
347
  end
335
348
  ints
@@ -389,9 +402,9 @@ module GraphQL
389
402
  next true if root_type?(type_defn) || type_defn.introspection?
390
403
 
391
404
  if type_defn.kind.union?
392
- possible_types(type_defn).any? && (referenced?(type_defn) || orphan_type?(type_defn))
405
+ !possible_types(type_defn).empty? && (referenced?(type_defn) || orphan_type?(type_defn))
393
406
  elsif type_defn.kind.interface?
394
- if possible_types(type_defn).any?
407
+ if !possible_types(type_defn).empty?
395
408
  true
396
409
  else
397
410
  if @context.respond_to?(:logger) && (logger = @context.logger)
@@ -7,7 +7,6 @@ require "graphql/schema/find_inherited_value"
7
7
  require "graphql/schema/finder"
8
8
  require "graphql/schema/introspection_system"
9
9
  require "graphql/schema/late_bound_type"
10
- require "graphql/schema/null_mask"
11
10
  require "graphql/schema/timeout"
12
11
  require "graphql/schema/type_expression"
13
12
  require "graphql/schema/unique_within_type"
@@ -73,6 +72,9 @@ module GraphQL
73
72
  class Schema
74
73
  extend GraphQL::Schema::Member::HasAstNode
75
74
  extend GraphQL::Schema::FindInheritedValue
75
+ extend Autoload
76
+
77
+ autoload :BUILT_IN_TYPES, "graphql/schema/built_in_types"
76
78
 
77
79
  class DuplicateNamesError < GraphQL::Error
78
80
  attr_reader :duplicated_name
@@ -164,9 +166,6 @@ module GraphQL
164
166
  mods.each { |mod| new_class.include(mod) }
165
167
  new_class.include(DefaultTraceClass)
166
168
  trace_mode(:default, new_class)
167
- backtrace_class = Class.new(new_class)
168
- backtrace_class.include(GraphQL::Backtrace::Trace)
169
- trace_mode(:default_backtrace, backtrace_class)
170
169
  end
171
170
  trace_class_for(:default, build: true)
172
171
  end
@@ -213,11 +212,6 @@ module GraphQL
213
212
  const_set(:DefaultTrace, Class.new(base_class) do
214
213
  include DefaultTraceClass
215
214
  end)
216
- when :default_backtrace
217
- schema_base_class = trace_class_for(:default, build: true)
218
- const_set(:DefaultTraceBacktrace, Class.new(schema_base_class) do
219
- include(GraphQL::Backtrace::Trace)
220
- end)
221
215
  else
222
216
  # First, see if the superclass has a custom-defined class for this.
223
217
  # Then, if it doesn't, use this class's default trace
@@ -233,7 +227,7 @@ module GraphQL
233
227
  add_trace_options_for(mode, default_options)
234
228
 
235
229
  Class.new(base_class) do
236
- mods.any? && include(*mods)
230
+ !mods.empty? && include(*mods)
237
231
  end
238
232
  end
239
233
  end
@@ -321,7 +315,7 @@ module GraphQL
321
315
  # @param plugin [#use] A Schema plugin
322
316
  # @return void
323
317
  def use(plugin, **kwargs)
324
- if kwargs.any?
318
+ if !kwargs.empty?
325
319
  plugin.use(self, **kwargs)
326
320
  else
327
321
  plugin.use(self)
@@ -446,7 +440,12 @@ module GraphQL
446
440
  raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
447
441
  elsif use_visibility_profile?
448
442
  if block_given?
449
- @query_object = lazy_load_block
443
+ if visibility.preload?
444
+ @query_object = lazy_load_block.call
445
+ self.visibility.query_configured(@query_object)
446
+ else
447
+ @query_object = lazy_load_block
448
+ end
450
449
  else
451
450
  @query_object = new_query_object
452
451
  self.visibility.query_configured(@query_object)
@@ -480,7 +479,12 @@ module GraphQL
480
479
  raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
481
480
  elsif use_visibility_profile?
482
481
  if block_given?
483
- @mutation_object = lazy_load_block
482
+ if visibility.preload?
483
+ @mutation_object = lazy_load_block.call
484
+ self.visibility.mutation_configured(@mutation_object)
485
+ else
486
+ @mutation_object = lazy_load_block
487
+ end
484
488
  else
485
489
  @mutation_object = new_mutation_object
486
490
  self.visibility.mutation_configured(@mutation_object)
@@ -514,7 +518,12 @@ module GraphQL
514
518
  raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
515
519
  elsif use_visibility_profile?
516
520
  if block_given?
517
- @subscription_object = lazy_load_block
521
+ if visibility.preload?
522
+ @subscription_object = lazy_load_block.call
523
+ visibility.subscription_configured(@subscription_object)
524
+ else
525
+ @subscription_object = lazy_load_block
526
+ end
518
527
  else
519
528
  @subscription_object = new_subscription_object
520
529
  self.visibility.subscription_configured(@subscription_object)
@@ -676,7 +685,7 @@ module GraphQL
676
685
  # and generally speaking, we won't inherit any values.
677
686
  # So optimize the most common case -- don't create a duplicate Hash.
678
687
  inherited_value = find_inherited_value(:references_to, EMPTY_HASH)
679
- if inherited_value.any?
688
+ if !inherited_value.empty?
680
689
  inherited_value.merge(own_references_to)
681
690
  else
682
691
  own_references_to
@@ -812,13 +821,13 @@ module GraphQL
812
821
 
813
822
  attr_writer :validate_timeout
814
823
 
815
- def validate_timeout(new_validate_timeout = nil)
816
- if new_validate_timeout
824
+ def validate_timeout(new_validate_timeout = NOT_CONFIGURED)
825
+ if !NOT_CONFIGURED.equal?(new_validate_timeout)
817
826
  @validate_timeout = new_validate_timeout
818
827
  elsif defined?(@validate_timeout)
819
828
  @validate_timeout
820
829
  else
821
- find_inherited_value(:validate_timeout)
830
+ find_inherited_value(:validate_timeout) || 3
822
831
  end
823
832
  end
824
833
 
@@ -962,7 +971,7 @@ module GraphQL
962
971
  # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema
963
972
  # @return [Array<Module>] Type definitions added to this schema
964
973
  def extra_types(*new_extra_types)
965
- if new_extra_types.any?
974
+ if !new_extra_types.empty?
966
975
  new_extra_types = new_extra_types.flatten
967
976
  @own_extra_types ||= []
968
977
  @own_extra_types.concat(new_extra_types)
@@ -987,10 +996,10 @@ module GraphQL
987
996
  # @param new_orphan_types [Array<Class<GraphQL::Schema::Object>>] Object types to register as implementations of interfaces in the schema.
988
997
  # @return [Array<Class<GraphQL::Schema::Object>>] All previously-registered orphan types for this schema
989
998
  def orphan_types(*new_orphan_types)
990
- if new_orphan_types.any?
999
+ if !new_orphan_types.empty?
991
1000
  new_orphan_types = new_orphan_types.flatten
992
1001
  non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object }
993
- if non_object_types.any?
1002
+ if !non_object_types.empty?
994
1003
  raise ArgumentError, <<~ERR
995
1004
  Only object type classes should be added as `orphan_types(...)`.
996
1005
 
@@ -1007,7 +1016,7 @@ module GraphQL
1007
1016
 
1008
1017
  inherited_ot = find_inherited_value(:orphan_types, nil)
1009
1018
  if inherited_ot
1010
- if own_orphan_types.any?
1019
+ if !own_orphan_types.empty?
1011
1020
  inherited_ot + own_orphan_types
1012
1021
  else
1013
1022
  inherited_ot
@@ -1100,6 +1109,9 @@ module GraphQL
1100
1109
  }
1101
1110
  end
1102
1111
 
1112
+ # @api private
1113
+ attr_accessor :using_backtrace
1114
+
1103
1115
  # @api private
1104
1116
  def handle_or_reraise(context, err)
1105
1117
  handler = Execution::Errors.find_handler_for(self, err.class)
@@ -1113,6 +1125,10 @@ module GraphQL
1113
1125
  end
1114
1126
  handler[:handler].call(err, obj, args, context, field)
1115
1127
  else
1128
+ if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
1129
+ err = GraphQL::Backtrace::TracedError.new(err, context)
1130
+ end
1131
+
1116
1132
  raise err
1117
1133
  end
1118
1134
  end
@@ -1282,7 +1298,10 @@ module GraphQL
1282
1298
  def type_error(type_error, ctx)
1283
1299
  case type_error
1284
1300
  when GraphQL::InvalidNullError
1285
- ctx.errors << type_error
1301
+ execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
1302
+ execution_error.path = ctx[:current_path]
1303
+
1304
+ ctx.errors << execution_error
1286
1305
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
1287
1306
  raise type_error
1288
1307
  when GraphQL::IntegerDecodingError
@@ -1317,12 +1336,12 @@ module GraphQL
1317
1336
  # Add several directives at once
1318
1337
  # @param new_directives [Class]
1319
1338
  def directives(*new_directives)
1320
- if new_directives.any?
1339
+ if !new_directives.empty?
1321
1340
  new_directives.flatten.each { |d| directive(d) }
1322
1341
  end
1323
1342
 
1324
1343
  inherited_dirs = find_inherited_value(:directives, default_directives)
1325
- if own_directives.any?
1344
+ if !own_directives.empty?
1326
1345
  inherited_dirs.merge(own_directives)
1327
1346
  else
1328
1347
  inherited_dirs
@@ -1350,6 +1369,16 @@ module GraphQL
1350
1369
  }.freeze
1351
1370
  end
1352
1371
 
1372
+ # @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema
1373
+ attr_accessor :detailed_trace
1374
+
1375
+ # @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex})
1376
+ # @return [Boolean] When `true`, save a detailed trace for this query.
1377
+ # @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true
1378
+ def detailed_trace?(query)
1379
+ raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition."
1380
+ end
1381
+
1353
1382
  def tracer(new_tracer, silence_deprecation_warning: false)
1354
1383
  if !silence_deprecation_warning
1355
1384
  warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
@@ -1367,14 +1396,22 @@ module GraphQL
1367
1396
  find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
1368
1397
  end
1369
1398
 
1370
- # Mix `trace_mod` into this schema's `Trace` class so that its methods
1371
- # will be called at runtime.
1399
+ # Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
1400
+ #
1401
+ # You can attach a module to run in only _some_ circumstances by using `mode:`. When a module is added with `mode:`,
1402
+ # it will only run for queries with a matching `context[:trace_mode]`.
1403
+ #
1404
+ # Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration).
1405
+ #
1406
+ # @example Adding a trace in a special mode
1407
+ # # only runs when `query.context[:trace_mode]` is `:special`
1408
+ # trace_with SpecialTrace, mode: :special
1372
1409
  #
1373
1410
  # @param trace_mod [Module] A module that implements tracing methods
1374
1411
  # @param mode [Symbol] Trace module will only be used for this trade mode
1375
1412
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1376
1413
  # @return [void]
1377
- # @see GraphQL::Tracing::Trace for available tracing methods
1414
+ # @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
1378
1415
  def trace_with(trace_mod, mode: :default, **options)
1379
1416
  if mode.is_a?(Array)
1380
1417
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
@@ -1424,29 +1461,36 @@ module GraphQL
1424
1461
  #
1425
1462
  # If no `mode:` is given, then {default_trace_mode} will be used.
1426
1463
  #
1464
+ # If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then
1465
+ # DetailedTrace's mode will override the passed-in `mode`.
1466
+ #
1427
1467
  # @param mode [Symbol] Trace modules for this trade mode will be included
1428
1468
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1429
1469
  # @return [Tracing::Trace]
1430
1470
  def new_trace(mode: nil, **options)
1431
- target = options[:query] || options[:multiplex]
1432
- mode ||= target && target.context[:trace_mode]
1433
-
1434
- trace_mode = if mode
1435
- mode
1436
- elsif target && target.context[:backtrace]
1437
- if default_trace_mode != :default
1438
- raise ArgumentError, "Can't use `context[:backtrace]` with a custom default trace mode (`#{dm.inspect}`)"
1439
- else
1440
- own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
1441
- options_trace_mode = :default
1442
- :default_backtrace
1471
+ should_sample = if detailed_trace
1472
+ if (query = options[:query])
1473
+ detailed_trace?(query)
1474
+ elsif (multiplex = options[:multiplex])
1475
+ if multiplex.queries.length == 1
1476
+ detailed_trace?(multiplex.queries.first)
1477
+ else
1478
+ detailed_trace?(multiplex)
1479
+ end
1443
1480
  end
1444
1481
  else
1445
- default_trace_mode
1482
+ false
1483
+ end
1484
+
1485
+ if should_sample
1486
+ mode = detailed_trace.trace_mode
1487
+ else
1488
+ target = options[:query] || options[:multiplex]
1489
+ mode ||= target && target.context[:trace_mode]
1446
1490
  end
1447
1491
 
1448
- options_trace_mode ||= trace_mode
1449
- base_trace_options = trace_options_for(options_trace_mode)
1492
+ trace_mode = mode || default_trace_mode
1493
+ base_trace_options = trace_options_for(trace_mode)
1450
1494
  trace_options = base_trace_options.merge(options)
1451
1495
  trace_class_for_mode = trace_class_for(trace_mode, build: true)
1452
1496
  trace_class_for_mode.new(**trace_options)
@@ -1787,3 +1831,6 @@ module GraphQL
1787
1831
  end
1788
1832
  end
1789
1833
  end
1834
+
1835
+ require "graphql/schema/loader"
1836
+ require "graphql/schema/printer"
@@ -16,7 +16,7 @@ module GraphQL
16
16
 
17
17
  def validate_arguments(node)
18
18
  argument_defns = node.arguments
19
- if argument_defns.any?
19
+ if !argument_defns.empty?
20
20
  args_by_name = Hash.new { |h, k| h[k] = [] }
21
21
  argument_defns.each { |a| args_by_name[a.name] << a }
22
22
  args_by_name.each do |name, defns|
@@ -25,7 +25,7 @@ module GraphQL
25
25
  def validate_field_selections(ast_node, resolved_type)
26
26
  msg = if resolved_type.nil?
27
27
  nil
28
- elsif ast_node.selections.any? && resolved_type.kind.leaf?
28
+ elsif !ast_node.selections.empty? && resolved_type.kind.leaf?
29
29
  selection_strs = ast_node.selections.map do |n|
30
30
  case n
31
31
  when GraphQL::Language::Nodes::InlineFragment
@@ -345,7 +345,7 @@ module GraphQL
345
345
  fields << Field.new(node, definition, owner_type, parents)
346
346
  when GraphQL::Language::Nodes::InlineFragment
347
347
  fragment_type = node.type ? @types.type(node.type.name) : owner_type
348
- find_fields_and_fragments(node.selections, parents: [*parents, fragment_type], owner_type: owner_type, fields: fields, fragment_spreads: fragment_spreads) if fragment_type
348
+ find_fields_and_fragments(node.selections, parents: [*parents, fragment_type], owner_type: fragment_type, fields: fields, fragment_spreads: fragment_spreads) if fragment_type
349
349
  when GraphQL::Language::Nodes::FragmentSpread
350
350
  fragment_spreads << FragmentSpread.new(node.name, parents)
351
351
  end
@@ -32,7 +32,7 @@ module GraphQL
32
32
 
33
33
  def on_document(node, parent)
34
34
  super
35
- if @schema_definition_nodes.any?
35
+ if !@schema_definition_nodes.empty?
36
36
  add_error(GraphQL::StaticValidation::NoDefinitionsArePresentError.new(%|Query cannot contain schema definitions|, nodes: @schema_definition_nodes))
37
37
  end
38
38
  end