graphql 2.4.3 → 2.5.2

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 (166) 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 +118 -55
  7. data/lib/graphql/backtrace.rb +1 -19
  8. data/lib/graphql/current.rb +6 -1
  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 +21 -9
  41. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  42. data/lib/graphql/dataloader/source.rb +3 -3
  43. data/lib/graphql/dataloader.rb +43 -14
  44. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  45. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -4
  46. data/lib/graphql/execution/interpreter/runtime.rb +94 -51
  47. data/lib/graphql/execution/interpreter.rb +16 -7
  48. data/lib/graphql/execution/multiplex.rb +1 -5
  49. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  50. data/lib/graphql/invalid_name_error.rb +1 -1
  51. data/lib/graphql/invalid_null_error.rb +5 -15
  52. data/lib/graphql/language/cache.rb +13 -0
  53. data/lib/graphql/language/document_from_schema_definition.rb +8 -7
  54. data/lib/graphql/language/lexer.rb +11 -4
  55. data/lib/graphql/language/nodes.rb +3 -0
  56. data/lib/graphql/language/parser.rb +15 -8
  57. data/lib/graphql/language/printer.rb +8 -8
  58. data/lib/graphql/language/static_visitor.rb +37 -33
  59. data/lib/graphql/language/visitor.rb +59 -55
  60. data/lib/graphql/pagination/connection.rb +1 -1
  61. data/lib/graphql/query/context/scoped_context.rb +1 -1
  62. data/lib/graphql/query/context.rb +6 -5
  63. data/lib/graphql/query/variable_validation_error.rb +1 -1
  64. data/lib/graphql/query.rb +19 -23
  65. data/lib/graphql/railtie.rb +7 -0
  66. data/lib/graphql/schema/addition.rb +1 -1
  67. data/lib/graphql/schema/argument.rb +7 -8
  68. data/lib/graphql/schema/build_from_definition.rb +99 -53
  69. data/lib/graphql/schema/directive/flagged.rb +3 -1
  70. data/lib/graphql/schema/directive.rb +2 -2
  71. data/lib/graphql/schema/enum.rb +36 -1
  72. data/lib/graphql/schema/enum_value.rb +1 -1
  73. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  74. data/lib/graphql/schema/field.rb +27 -13
  75. data/lib/graphql/schema/field_extension.rb +1 -1
  76. data/lib/graphql/schema/has_single_input_argument.rb +3 -1
  77. data/lib/graphql/schema/input_object.rb +77 -40
  78. data/lib/graphql/schema/interface.rb +3 -2
  79. data/lib/graphql/schema/loader.rb +1 -1
  80. data/lib/graphql/schema/member/has_arguments.rb +25 -17
  81. data/lib/graphql/schema/member/has_dataloader.rb +60 -0
  82. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  83. data/lib/graphql/schema/member/has_directives.rb +4 -4
  84. data/lib/graphql/schema/member/has_fields.rb +19 -1
  85. data/lib/graphql/schema/member/has_interfaces.rb +5 -5
  86. data/lib/graphql/schema/member/has_validators.rb +1 -1
  87. data/lib/graphql/schema/member/scoped.rb +1 -1
  88. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  89. data/lib/graphql/schema/member.rb +1 -0
  90. data/lib/graphql/schema/object.rb +25 -8
  91. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  92. data/lib/graphql/schema/resolver.rb +12 -10
  93. data/lib/graphql/schema/subscription.rb +52 -6
  94. data/lib/graphql/schema/union.rb +1 -1
  95. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  96. data/lib/graphql/schema/validator.rb +1 -1
  97. data/lib/graphql/schema/visibility/migration.rb +1 -0
  98. data/lib/graphql/schema/visibility/profile.rb +95 -243
  99. data/lib/graphql/schema/visibility/visit.rb +190 -0
  100. data/lib/graphql/schema/visibility.rb +169 -28
  101. data/lib/graphql/schema/warden.rb +18 -5
  102. data/lib/graphql/schema.rb +93 -44
  103. data/lib/graphql/static_validation/all_rules.rb +1 -1
  104. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  105. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +1 -1
  106. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  107. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  108. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  109. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  110. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  111. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  112. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  113. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  114. data/lib/graphql/static_validation/validation_context.rb +1 -0
  115. data/lib/graphql/static_validation/validator.rb +6 -1
  116. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  117. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  118. data/lib/graphql/subscriptions/event.rb +12 -1
  119. data/lib/graphql/subscriptions/serialize.rb +1 -1
  120. data/lib/graphql/subscriptions.rb +1 -1
  121. data/lib/graphql/testing/helpers.rb +7 -4
  122. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  123. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  124. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  125. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  126. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  127. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  128. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  129. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  130. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  131. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  132. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  133. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  134. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  135. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  136. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  137. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  138. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  139. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  140. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  141. data/lib/graphql/tracing/null_trace.rb +9 -0
  142. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  143. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  144. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  145. data/lib/graphql/tracing/platform_trace.rb +5 -0
  146. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  147. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  148. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  149. data/lib/graphql/tracing/scout_trace.rb +32 -55
  150. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  151. data/lib/graphql/tracing/sentry_trace.rb +62 -94
  152. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  153. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  154. data/lib/graphql/tracing/trace.rb +111 -1
  155. data/lib/graphql/tracing.rb +31 -30
  156. data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
  157. data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
  158. data/lib/graphql/types.rb +18 -11
  159. data/lib/graphql/version.rb +1 -1
  160. data/lib/graphql.rb +55 -47
  161. metadata +146 -11
  162. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  163. data/lib/graphql/backtrace/trace.rb +0 -93
  164. data/lib/graphql/backtrace/tracer.rb +0 -80
  165. data/lib/graphql/schema/null_mask.rb +0 -11
  166. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -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
@@ -109,7 +111,7 @@ module GraphQL
109
111
  # @param parser [Object] An object for handling definition string parsing (must respond to `parse`)
110
112
  # @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
111
113
  # @return [Class] the schema described by `document`
112
- def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {})
114
+ def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {}, base_types: {})
113
115
  # If the file ends in `.graphql` or `.graphqls`, treat it like a filepath
114
116
  if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls")
115
117
  GraphQL::Schema::BuildFromDefinition.from_definition_path(
@@ -118,6 +120,7 @@ module GraphQL
118
120
  default_resolve: default_resolve,
119
121
  parser: parser,
120
122
  using: using,
123
+ base_types: base_types,
121
124
  )
122
125
  else
123
126
  GraphQL::Schema::BuildFromDefinition.from_definition(
@@ -126,6 +129,7 @@ module GraphQL
126
129
  default_resolve: default_resolve,
127
130
  parser: parser,
128
131
  using: using,
132
+ base_types: base_types,
129
133
  )
130
134
  end
131
135
  end
@@ -164,9 +168,6 @@ module GraphQL
164
168
  mods.each { |mod| new_class.include(mod) }
165
169
  new_class.include(DefaultTraceClass)
166
170
  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
171
  end
171
172
  trace_class_for(:default, build: true)
172
173
  end
@@ -213,11 +214,6 @@ module GraphQL
213
214
  const_set(:DefaultTrace, Class.new(base_class) do
214
215
  include DefaultTraceClass
215
216
  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
217
  else
222
218
  # First, see if the superclass has a custom-defined class for this.
223
219
  # Then, if it doesn't, use this class's default trace
@@ -233,7 +229,7 @@ module GraphQL
233
229
  add_trace_options_for(mode, default_options)
234
230
 
235
231
  Class.new(base_class) do
236
- mods.any? && include(*mods)
232
+ !mods.empty? && include(*mods)
237
233
  end
238
234
  end
239
235
  end
@@ -321,7 +317,7 @@ module GraphQL
321
317
  # @param plugin [#use] A Schema plugin
322
318
  # @return void
323
319
  def use(plugin, **kwargs)
324
- if kwargs.any?
320
+ if !kwargs.empty?
325
321
  plugin.use(self, **kwargs)
326
322
  else
327
323
  plugin.use(self)
@@ -446,7 +442,12 @@ module GraphQL
446
442
  raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
447
443
  elsif use_visibility_profile?
448
444
  if block_given?
449
- @query_object = lazy_load_block
445
+ if visibility.preload?
446
+ @query_object = lazy_load_block.call
447
+ self.visibility.query_configured(@query_object)
448
+ else
449
+ @query_object = lazy_load_block
450
+ end
450
451
  else
451
452
  @query_object = new_query_object
452
453
  self.visibility.query_configured(@query_object)
@@ -480,7 +481,12 @@ module GraphQL
480
481
  raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
481
482
  elsif use_visibility_profile?
482
483
  if block_given?
483
- @mutation_object = lazy_load_block
484
+ if visibility.preload?
485
+ @mutation_object = lazy_load_block.call
486
+ self.visibility.mutation_configured(@mutation_object)
487
+ else
488
+ @mutation_object = lazy_load_block
489
+ end
484
490
  else
485
491
  @mutation_object = new_mutation_object
486
492
  self.visibility.mutation_configured(@mutation_object)
@@ -514,7 +520,12 @@ module GraphQL
514
520
  raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
515
521
  elsif use_visibility_profile?
516
522
  if block_given?
517
- @subscription_object = lazy_load_block
523
+ if visibility.preload?
524
+ @subscription_object = lazy_load_block.call
525
+ visibility.subscription_configured(@subscription_object)
526
+ else
527
+ @subscription_object = lazy_load_block
528
+ end
518
529
  else
519
530
  @subscription_object = new_subscription_object
520
531
  self.visibility.subscription_configured(@subscription_object)
@@ -676,7 +687,7 @@ module GraphQL
676
687
  # and generally speaking, we won't inherit any values.
677
688
  # So optimize the most common case -- don't create a duplicate Hash.
678
689
  inherited_value = find_inherited_value(:references_to, EMPTY_HASH)
679
- if inherited_value.any?
690
+ if !inherited_value.empty?
680
691
  inherited_value.merge(own_references_to)
681
692
  else
682
693
  own_references_to
@@ -812,13 +823,13 @@ module GraphQL
812
823
 
813
824
  attr_writer :validate_timeout
814
825
 
815
- def validate_timeout(new_validate_timeout = nil)
816
- if new_validate_timeout
826
+ def validate_timeout(new_validate_timeout = NOT_CONFIGURED)
827
+ if !NOT_CONFIGURED.equal?(new_validate_timeout)
817
828
  @validate_timeout = new_validate_timeout
818
829
  elsif defined?(@validate_timeout)
819
830
  @validate_timeout
820
831
  else
821
- find_inherited_value(:validate_timeout)
832
+ find_inherited_value(:validate_timeout) || 3
822
833
  end
823
834
  end
824
835
 
@@ -962,7 +973,7 @@ module GraphQL
962
973
  # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema
963
974
  # @return [Array<Module>] Type definitions added to this schema
964
975
  def extra_types(*new_extra_types)
965
- if new_extra_types.any?
976
+ if !new_extra_types.empty?
966
977
  new_extra_types = new_extra_types.flatten
967
978
  @own_extra_types ||= []
968
979
  @own_extra_types.concat(new_extra_types)
@@ -987,10 +998,10 @@ module GraphQL
987
998
  # @param new_orphan_types [Array<Class<GraphQL::Schema::Object>>] Object types to register as implementations of interfaces in the schema.
988
999
  # @return [Array<Class<GraphQL::Schema::Object>>] All previously-registered orphan types for this schema
989
1000
  def orphan_types(*new_orphan_types)
990
- if new_orphan_types.any?
1001
+ if !new_orphan_types.empty?
991
1002
  new_orphan_types = new_orphan_types.flatten
992
1003
  non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object }
993
- if non_object_types.any?
1004
+ if !non_object_types.empty?
994
1005
  raise ArgumentError, <<~ERR
995
1006
  Only object type classes should be added as `orphan_types(...)`.
996
1007
 
@@ -1007,7 +1018,7 @@ module GraphQL
1007
1018
 
1008
1019
  inherited_ot = find_inherited_value(:orphan_types, nil)
1009
1020
  if inherited_ot
1010
- if own_orphan_types.any?
1021
+ if !own_orphan_types.empty?
1011
1022
  inherited_ot + own_orphan_types
1012
1023
  else
1013
1024
  inherited_ot
@@ -1100,6 +1111,9 @@ module GraphQL
1100
1111
  }
1101
1112
  end
1102
1113
 
1114
+ # @api private
1115
+ attr_accessor :using_backtrace
1116
+
1103
1117
  # @api private
1104
1118
  def handle_or_reraise(context, err)
1105
1119
  handler = Execution::Errors.find_handler_for(self, err.class)
@@ -1113,6 +1127,10 @@ module GraphQL
1113
1127
  end
1114
1128
  handler[:handler].call(err, obj, args, context, field)
1115
1129
  else
1130
+ if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
1131
+ err = GraphQL::Backtrace::TracedError.new(err, context)
1132
+ end
1133
+
1116
1134
  raise err
1117
1135
  end
1118
1136
  end
@@ -1282,7 +1300,10 @@ module GraphQL
1282
1300
  def type_error(type_error, ctx)
1283
1301
  case type_error
1284
1302
  when GraphQL::InvalidNullError
1285
- ctx.errors << type_error
1303
+ execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
1304
+ execution_error.path = ctx[:current_path]
1305
+
1306
+ ctx.errors << execution_error
1286
1307
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
1287
1308
  raise type_error
1288
1309
  when GraphQL::IntegerDecodingError
@@ -1317,12 +1338,12 @@ module GraphQL
1317
1338
  # Add several directives at once
1318
1339
  # @param new_directives [Class]
1319
1340
  def directives(*new_directives)
1320
- if new_directives.any?
1341
+ if !new_directives.empty?
1321
1342
  new_directives.flatten.each { |d| directive(d) }
1322
1343
  end
1323
1344
 
1324
1345
  inherited_dirs = find_inherited_value(:directives, default_directives)
1325
- if own_directives.any?
1346
+ if !own_directives.empty?
1326
1347
  inherited_dirs.merge(own_directives)
1327
1348
  else
1328
1349
  inherited_dirs
@@ -1350,6 +1371,16 @@ module GraphQL
1350
1371
  }.freeze
1351
1372
  end
1352
1373
 
1374
+ # @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema
1375
+ attr_accessor :detailed_trace
1376
+
1377
+ # @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex})
1378
+ # @return [Boolean] When `true`, save a detailed trace for this query.
1379
+ # @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true
1380
+ def detailed_trace?(query)
1381
+ raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition."
1382
+ end
1383
+
1353
1384
  def tracer(new_tracer, silence_deprecation_warning: false)
1354
1385
  if !silence_deprecation_warning
1355
1386
  warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
@@ -1367,14 +1398,22 @@ module GraphQL
1367
1398
  find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
1368
1399
  end
1369
1400
 
1370
- # Mix `trace_mod` into this schema's `Trace` class so that its methods
1371
- # will be called at runtime.
1401
+ # Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
1402
+ #
1403
+ # You can attach a module to run in only _some_ circumstances by using `mode:`. When a module is added with `mode:`,
1404
+ # it will only run for queries with a matching `context[:trace_mode]`.
1405
+ #
1406
+ # Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration).
1407
+ #
1408
+ # @example Adding a trace in a special mode
1409
+ # # only runs when `query.context[:trace_mode]` is `:special`
1410
+ # trace_with SpecialTrace, mode: :special
1372
1411
  #
1373
1412
  # @param trace_mod [Module] A module that implements tracing methods
1374
1413
  # @param mode [Symbol] Trace module will only be used for this trade mode
1375
1414
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1376
1415
  # @return [void]
1377
- # @see GraphQL::Tracing::Trace for available tracing methods
1416
+ # @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
1378
1417
  def trace_with(trace_mod, mode: :default, **options)
1379
1418
  if mode.is_a?(Array)
1380
1419
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
@@ -1424,29 +1463,36 @@ module GraphQL
1424
1463
  #
1425
1464
  # If no `mode:` is given, then {default_trace_mode} will be used.
1426
1465
  #
1466
+ # If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then
1467
+ # DetailedTrace's mode will override the passed-in `mode`.
1468
+ #
1427
1469
  # @param mode [Symbol] Trace modules for this trade mode will be included
1428
1470
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1429
1471
  # @return [Tracing::Trace]
1430
1472
  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
1473
+ should_sample = if detailed_trace
1474
+ if (query = options[:query])
1475
+ detailed_trace?(query)
1476
+ elsif (multiplex = options[:multiplex])
1477
+ if multiplex.queries.length == 1
1478
+ detailed_trace?(multiplex.queries.first)
1479
+ else
1480
+ detailed_trace?(multiplex)
1481
+ end
1443
1482
  end
1444
1483
  else
1445
- default_trace_mode
1484
+ false
1485
+ end
1486
+
1487
+ if should_sample
1488
+ mode = detailed_trace.trace_mode
1489
+ else
1490
+ target = options[:query] || options[:multiplex]
1491
+ mode ||= target && target.context[:trace_mode]
1446
1492
  end
1447
1493
 
1448
- options_trace_mode ||= trace_mode
1449
- base_trace_options = trace_options_for(options_trace_mode)
1494
+ trace_mode = mode || default_trace_mode
1495
+ base_trace_options = trace_options_for(trace_mode)
1450
1496
  trace_options = base_trace_options.merge(options)
1451
1497
  trace_class_for_mode = trace_class_for(trace_mode, build: true)
1452
1498
  trace_class_for_mode.new(**trace_options)
@@ -1787,3 +1833,6 @@ module GraphQL
1787
1833
  end
1788
1834
  end
1789
1835
  end
1836
+
1837
+ require "graphql/schema/loader"
1838
+ require "graphql/schema/printer"
@@ -34,7 +34,7 @@ module GraphQL
34
34
  GraphQL::StaticValidation::VariableUsagesAreAllowed,
35
35
  GraphQL::StaticValidation::MutationRootExists,
36
36
  GraphQL::StaticValidation::QueryRootExists,
37
- GraphQL::StaticValidation::SubscriptionRootExists,
37
+ GraphQL::StaticValidation::SubscriptionRootExistsAndSingleSubscriptionSelection,
38
38
  GraphQL::StaticValidation::InputObjectNamesAreUnique,
39
39
  GraphQL::StaticValidation::OneOfInputObjectsAreValid,
40
40
  ]
@@ -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|