graphql 2.4.5 → 2.5.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/analysis/analyzer.rb +2 -1
  5. data/lib/graphql/analysis/query_complexity.rb +87 -7
  6. data/lib/graphql/analysis/visitor.rb +37 -40
  7. data/lib/graphql/analysis.rb +12 -9
  8. data/lib/graphql/autoload.rb +1 -0
  9. data/lib/graphql/backtrace/table.rb +118 -55
  10. data/lib/graphql/backtrace.rb +1 -19
  11. data/lib/graphql/current.rb +6 -1
  12. data/lib/graphql/dashboard/application_controller.rb +41 -0
  13. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  14. data/lib/graphql/dashboard/installable.rb +22 -0
  15. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  16. data/lib/graphql/dashboard/limiters.rb +93 -0
  17. data/lib/graphql/dashboard/operation_store.rb +199 -0
  18. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  19. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  20. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  21. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  22. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  23. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  24. data/lib/graphql/dashboard/statics/icon.png +0 -0
  25. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  26. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  42. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  43. data/lib/graphql/dashboard.rb +96 -0
  44. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  45. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  46. data/lib/graphql/dataloader/async_dataloader.rb +38 -15
  47. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  48. data/lib/graphql/dataloader/source.rb +18 -6
  49. data/lib/graphql/dataloader.rb +110 -26
  50. data/lib/graphql/date_encoding_error.rb +1 -1
  51. data/lib/graphql/dig.rb +2 -1
  52. data/lib/graphql/execution/interpreter/resolve.rb +10 -16
  53. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +58 -5
  54. data/lib/graphql/execution/interpreter/runtime.rb +229 -93
  55. data/lib/graphql/execution/interpreter.rb +15 -24
  56. data/lib/graphql/execution/multiplex.rb +7 -6
  57. data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
  58. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  59. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  60. data/lib/graphql/execution/next/runner.rb +389 -0
  61. data/lib/graphql/execution/next/selections_step.rb +37 -0
  62. data/lib/graphql/execution/next.rb +69 -0
  63. data/lib/graphql/execution.rb +1 -0
  64. data/lib/graphql/execution_error.rb +13 -10
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +7 -3
  67. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  68. data/lib/graphql/introspection/entry_points.rb +11 -3
  69. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  70. data/lib/graphql/introspection/field_type.rb +13 -5
  71. data/lib/graphql/introspection/input_value_type.rb +21 -13
  72. data/lib/graphql/introspection/type_type.rb +64 -28
  73. data/lib/graphql/invalid_name_error.rb +1 -1
  74. data/lib/graphql/invalid_null_error.rb +25 -16
  75. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  76. data/lib/graphql/language/lexer.rb +16 -5
  77. data/lib/graphql/language/nodes.rb +8 -1
  78. data/lib/graphql/language/parser.rb +16 -8
  79. data/lib/graphql/language/static_visitor.rb +37 -33
  80. data/lib/graphql/language/visitor.rb +59 -55
  81. data/lib/graphql/language.rb +21 -12
  82. data/lib/graphql/pagination/connection.rb +2 -0
  83. data/lib/graphql/pagination/connections.rb +32 -0
  84. data/lib/graphql/query/context.rb +6 -10
  85. data/lib/graphql/query/null_context.rb +9 -3
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query.rb +64 -64
  88. data/lib/graphql/railtie.rb +1 -1
  89. data/lib/graphql/schema/addition.rb +3 -1
  90. data/lib/graphql/schema/always_visible.rb +1 -0
  91. data/lib/graphql/schema/argument.rb +24 -8
  92. data/lib/graphql/schema/build_from_definition.rb +113 -54
  93. data/lib/graphql/schema/directive/flagged.rb +2 -0
  94. data/lib/graphql/schema/directive.rb +52 -2
  95. data/lib/graphql/schema/enum.rb +36 -1
  96. data/lib/graphql/schema/enum_value.rb +1 -1
  97. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  98. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  99. data/lib/graphql/schema/field.rb +101 -51
  100. data/lib/graphql/schema/field_extension.rb +33 -0
  101. data/lib/graphql/schema/input_object.rb +45 -38
  102. data/lib/graphql/schema/interface.rb +2 -1
  103. data/lib/graphql/schema/list.rb +1 -1
  104. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  105. data/lib/graphql/schema/member/has_arguments.rb +56 -19
  106. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  107. data/lib/graphql/schema/member/has_dataloader.rb +79 -0
  108. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  109. data/lib/graphql/schema/member/has_directives.rb +1 -1
  110. data/lib/graphql/schema/member/has_fields.rb +81 -5
  111. data/lib/graphql/schema/member/has_interfaces.rb +3 -3
  112. data/lib/graphql/schema/member/scoped.rb +1 -1
  113. data/lib/graphql/schema/member/type_system_helpers.rb +17 -3
  114. data/lib/graphql/schema/member.rb +6 -0
  115. data/lib/graphql/schema/object.rb +18 -8
  116. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  117. data/lib/graphql/schema/resolver.rb +52 -6
  118. data/lib/graphql/schema/scalar.rb +1 -6
  119. data/lib/graphql/schema/subscription.rb +50 -4
  120. data/lib/graphql/schema/timeout.rb +19 -2
  121. data/lib/graphql/schema/validator/required_validator.rb +71 -14
  122. data/lib/graphql/schema/visibility/migration.rb +3 -2
  123. data/lib/graphql/schema/visibility/profile.rb +115 -23
  124. data/lib/graphql/schema/visibility.rb +49 -32
  125. data/lib/graphql/schema/warden.rb +23 -2
  126. data/lib/graphql/schema.rb +333 -68
  127. data/lib/graphql/static_validation/all_rules.rb +2 -2
  128. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  129. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
  130. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  131. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  132. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  133. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  134. data/lib/graphql/static_validation/validator.rb +6 -1
  135. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  136. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  137. data/lib/graphql/subscriptions/event.rb +12 -1
  138. data/lib/graphql/subscriptions/serialize.rb +1 -1
  139. data/lib/graphql/subscriptions.rb +1 -1
  140. data/lib/graphql/testing/helpers.rb +17 -11
  141. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  142. data/lib/graphql/testing.rb +1 -0
  143. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  144. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  145. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  146. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  147. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  148. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  149. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  150. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  151. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  152. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  153. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  154. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  155. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  156. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  157. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  158. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  159. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  160. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  161. data/lib/graphql/tracing/notifications_trace.rb +184 -34
  162. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  163. data/lib/graphql/tracing/null_trace.rb +9 -0
  164. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  165. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  166. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  167. data/lib/graphql/tracing/platform_trace.rb +5 -0
  168. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  169. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  170. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  171. data/lib/graphql/tracing/scout_trace.rb +32 -55
  172. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  173. data/lib/graphql/tracing/sentry_trace.rb +64 -94
  174. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  175. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  176. data/lib/graphql/tracing/trace.rb +111 -1
  177. data/lib/graphql/tracing.rb +31 -30
  178. data/lib/graphql/type_kinds.rb +1 -0
  179. data/lib/graphql/types/relay/connection_behaviors.rb +9 -7
  180. data/lib/graphql/types/relay/edge_behaviors.rb +5 -4
  181. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  182. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  183. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  184. data/lib/graphql/unauthorized_error.rb +5 -1
  185. data/lib/graphql/version.rb +1 -1
  186. data/lib/graphql.rb +12 -31
  187. metadata +174 -11
  188. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  189. data/lib/graphql/backtrace/trace.rb +0 -93
  190. data/lib/graphql/backtrace/tracer.rb +0 -80
  191. data/lib/graphql/schema/null_mask.rb +0 -11
  192. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -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
 
@@ -31,10 +31,37 @@ module GraphQL
31
31
  # @return [Symbol, nil]
32
32
  attr_reader :name
33
33
 
34
- def initialize(name: nil, context:, schema:)
34
+ def freeze
35
+ @cached_visible.default_proc = nil
36
+ @cached_visible_fields.default_proc = nil
37
+ @cached_visible_fields.each do |type, fields|
38
+ fields.default_proc = nil
39
+ end
40
+ @cached_visible_arguments.default_proc = nil
41
+ @cached_visible_arguments.each do |type, fields|
42
+ fields.default_proc = nil
43
+ end
44
+ @cached_parent_fields.default_proc = nil
45
+ @cached_parent_fields.each do |type, fields|
46
+ fields.default_proc = nil
47
+ end
48
+ @cached_parent_arguments.default_proc = nil
49
+ @cached_parent_arguments.each do |type, args|
50
+ args.default_proc = nil
51
+ end
52
+ @cached_possible_types.default_proc = nil
53
+ @cached_enum_values.default_proc = nil
54
+ @cached_fields.default_proc = nil
55
+ @cached_arguments.default_proc = nil
56
+ @loadable_possible_types.default_proc = nil
57
+ super
58
+ end
59
+
60
+ def initialize(name: nil, context:, schema:, visibility:)
35
61
  @name = name
36
62
  @context = context
37
63
  @schema = schema
64
+ @visibility = visibility
38
65
  @all_types = {}
39
66
  @all_types_loaded = false
40
67
  @unvisited_types = []
@@ -47,12 +74,21 @@ module GraphQL
47
74
  end.compare_by_identity
48
75
  }.compare_by_identity
49
76
 
50
- @cached_visible_arguments = Hash.new do |h, arg|
51
- h[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type]
52
- true
53
- else
54
- false
55
- end
77
+ @cached_visible_arguments = Hash.new do |h, owner|
78
+ h[owner] = Hash.new do |h2, arg|
79
+ h2[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type]
80
+ case owner
81
+ when GraphQL::Schema::Field
82
+ @cached_visible_fields[owner.owner][owner]
83
+ when Class
84
+ @cached_visible[owner]
85
+ else
86
+ raise "Unexpected argument owner for `#{arg.path}`: #{owner.inspect}"
87
+ end
88
+ else
89
+ false
90
+ end
91
+ end.compare_by_identity
56
92
  end.compare_by_identity
57
93
 
58
94
  @cached_parent_fields = Hash.new do |h, type|
@@ -82,8 +118,10 @@ module GraphQL
82
118
  end.compare_by_identity
83
119
 
84
120
  @cached_arguments = Hash.new do |h, owner|
85
- h[owner] = non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments)
121
+ h[owner] = non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments[owner])
86
122
  end.compare_by_identity
123
+
124
+ @loadable_possible_types = Hash.new { |h, union_type| h[union_type] = union_type.possible_types }.compare_by_identity
87
125
  end
88
126
 
89
127
  def field_on_visible_interface?(field, owner)
@@ -111,7 +149,7 @@ module GraphQL
111
149
  end
112
150
 
113
151
  def type(type_name)
114
- t = @schema.visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
152
+ t = @visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
115
153
  if t
116
154
  if t.is_a?(Array)
117
155
  vis_t = nil
@@ -157,7 +195,7 @@ module GraphQL
157
195
  end
158
196
  end
159
197
  end
160
- visible_f.ensure_loaded
198
+ visible_f&.ensure_loaded
161
199
  elsif f && @cached_visible_fields[owner][f.ensure_loaded]
162
200
  f
163
201
  else
@@ -178,7 +216,7 @@ module GraphQL
178
216
  if arg.is_a?(Array)
179
217
  visible_arg = nil
180
218
  arg.each do |arg_defn|
181
- if @cached_visible_arguments[arg_defn]
219
+ if @cached_visible_arguments[owner][arg_defn]
182
220
  if visible_arg.nil?
183
221
  visible_arg = arg_defn
184
222
  else
@@ -188,7 +226,7 @@ module GraphQL
188
226
  end
189
227
  visible_arg
190
228
  else
191
- if arg && @cached_visible_arguments[arg]
229
+ if arg && @cached_visible_arguments[owner][arg]
192
230
  arg
193
231
  else
194
232
  nil
@@ -239,14 +277,17 @@ module GraphQL
239
277
  end
240
278
 
241
279
  def directives
242
- @all_directives ||= @schema.visibility.all_directives.select { |dir|
243
- @cached_visible[dir] && @schema.visibility.all_references[dir].any? { |ref| ref == true || (@cached_visible[ref] && referenced?(ref)) }
280
+ @all_directives ||= @visibility.all_directives.select { |dir|
281
+ @cached_visible[dir] && @visibility.all_references[dir].any? { |ref| ref == true || (@cached_visible[ref] && referenced?(ref)) }
244
282
  }
245
283
  end
246
284
 
247
285
  def loadable?(t, _ctx)
248
- load_all_types
249
- !@all_types[t.graphql_name] && @cached_visible[t]
286
+ @cached_visible[t] && !referenced?(t)
287
+ end
288
+
289
+ def loadable_possible_types(t, _ctx)
290
+ @loadable_possible_types[t]
250
291
  end
251
292
 
252
293
  def loaded_types
@@ -262,13 +303,51 @@ module GraphQL
262
303
  @cached_visible[enum_value]
263
304
  end
264
305
 
306
+ def preload
307
+ load_all_types
308
+ @all_types.each do |type_name, type_defn|
309
+ if type_defn.kind.fields?
310
+ fields(type_defn).each do |f|
311
+ field(type_defn, f.graphql_name)
312
+ arguments(f).each do |arg|
313
+ argument(f, arg.graphql_name)
314
+ end
315
+ end
316
+ @schema.introspection_system.dynamic_fields.each do |f|
317
+ field(type_defn, f.graphql_name)
318
+ end
319
+ elsif type_defn.kind.input_object?
320
+ arguments(type_defn).each do |arg|
321
+ argument(type_defn, arg.graphql_name)
322
+ end
323
+ elsif type_defn.kind.enum?
324
+ enum_values(type_defn)
325
+ end
326
+ # Lots more to do here
327
+ end
328
+ @schema.introspection_system.entry_points.each do |f|
329
+ arguments(f).each do |arg|
330
+ argument(f, arg.graphql_name)
331
+ end
332
+ field(@schema.query, f.graphql_name)
333
+ end
334
+ @schema.introspection_system.dynamic_fields.each do |f|
335
+ arguments(f).each do |arg|
336
+ argument(f, arg.graphql_name)
337
+ end
338
+ end
339
+
340
+ end
341
+
265
342
  private
266
343
 
267
344
  def non_duplicate_items(definitions, visibility_cache)
268
345
  non_dups = []
346
+ names = Set.new
269
347
  definitions.each do |defn|
270
348
  if visibility_cache[defn]
271
- if (dup_defn = non_dups.find { |d| d.graphql_name == defn.graphql_name })
349
+ if !names.add?(defn.graphql_name)
350
+ dup_defn = non_dups.find { |d| d.graphql_name == defn.graphql_name }
272
351
  raise_duplicate_definition(dup_defn, defn)
273
352
  end
274
353
  non_dups << defn
@@ -286,7 +365,7 @@ module GraphQL
286
365
  @all_types_loaded = true
287
366
  visit = Visibility::Visit.new(@schema) do |member|
288
367
  if member.is_a?(Module) && member.respond_to?(:kind)
289
- if @cached_visible[member]
368
+ if @cached_visible[member] && referenced?(member)
290
369
  type_name = member.graphql_name
291
370
  if (prev_t = @all_types[type_name]) && !prev_t.equal?(member)
292
371
  raise_duplicate_definition(member, prev_t)
@@ -306,16 +385,29 @@ module GraphQL
306
385
  end
307
386
 
308
387
  def referenced?(type_defn)
309
- @schema.visibility.all_references[type_defn].any? { |r| r == true || @cached_visible[r] }
388
+ @visibility.all_references[type_defn].any? do |ref|
389
+ case ref
390
+ when GraphQL::Schema::Argument
391
+ @cached_visible_arguments[ref.owner][ref]
392
+ when GraphQL::Schema::Field
393
+ @cached_visible_fields[ref.owner][ref]
394
+ when Module
395
+ @cached_visible[ref]
396
+ when true
397
+ true
398
+ end
399
+ end
310
400
  end
311
401
 
312
402
  def possible_types_for(type)
313
403
  case type.kind.name
314
404
  when "INTERFACE"
315
405
  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
406
+ @visibility.all_interface_type_memberships[type].each do |impl_type, type_memberships|
407
+ if impl_type.kind.object? && referenced?(impl_type) && @cached_visible[impl_type]
408
+ if type_memberships.any? { |itm| @cached_visible[itm] }
409
+ pts << impl_type
410
+ end
319
411
  end
320
412
  end
321
413
  pts
@@ -10,13 +10,14 @@ module GraphQL
10
10
  class Visibility
11
11
  # @param schema [Class<GraphQL::Schema>]
12
12
  # @param profiles [Hash<Symbol => Hash>] A hash of `name => context` pairs for preloading visibility profiles
13
- # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?`)
13
+ # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?` and `Rails.env.staging?`)
14
14
  # @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
15
- def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails) ? Rails.env.production? : nil), migration_errors: false)
15
+ def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? (Rails.env.production? || Rails.env.staging?) : nil), migration_errors: false)
16
+ profiles&.each { |name, ctx|
17
+ ctx[:visibility_profile] = name
18
+ ctx.freeze
19
+ }
16
20
  schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
17
- if preload
18
- schema.visibility.preload
19
- end
20
21
  end
21
22
 
22
23
  def initialize(schema, dynamic:, preload:, profiles:, migration_errors:)
@@ -39,6 +40,17 @@ module GraphQL
39
40
  @types = nil
40
41
  @all_references = nil
41
42
  @loaded_all = false
43
+ if preload
44
+ self.preload
45
+ end
46
+ end
47
+
48
+ def freeze
49
+ load_all
50
+ @visit = true
51
+ @interface_type_memberships.default_proc = nil
52
+ @all_references.default_proc = nil
53
+ super
42
54
  end
43
55
 
44
56
  def all_directives
@@ -61,6 +73,8 @@ module GraphQL
61
73
  @types[type_name]
62
74
  end
63
75
 
76
+ attr_accessor :types
77
+
64
78
  def preload?
65
79
  @preload
66
80
  end
@@ -81,9 +95,8 @@ module GraphQL
81
95
  types_to_visit.compact!
82
96
  ensure_all_loaded(types_to_visit)
83
97
  @profiles.each do |profile_name, example_ctx|
84
- example_ctx[:visibility_profile] = profile_name
85
- prof = profile_for(example_ctx, profile_name)
86
- prof.all_types # force loading
98
+ prof = profile_for(example_ctx)
99
+ prof.preload
87
100
  end
88
101
  end
89
102
 
@@ -145,27 +158,27 @@ module GraphQL
145
158
 
146
159
  attr_reader :cached_profiles
147
160
 
148
- def profile_for(context, visibility_profile)
161
+ def profile_for(context)
149
162
  if !@profiles.empty?
150
- if visibility_profile.nil?
151
- if @dynamic
152
- if context.is_a?(Query::NullContext)
153
- top_level_profile
154
- else
155
- @schema.visibility_profile_class.new(context: context, schema: @schema)
156
- end
157
- elsif !@profiles.empty?
158
- 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."
163
+ visibility_profile = context[:visibility_profile]
164
+ if @profiles.include?(visibility_profile)
165
+ profile_ctx = @profiles[visibility_profile]
166
+ @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: profile_ctx, schema: @schema, visibility: self)
167
+ elsif @dynamic
168
+ if context.is_a?(Query::NullContext)
169
+ top_level_profile
170
+ else
171
+ @schema.visibility_profile_class.new(context: context, schema: @schema, visibility: self)
159
172
  end
160
- elsif !@profiles.include?(visibility_profile)
161
- 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."
173
+ elsif !context.key?(:visibility_profile)
174
+ 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."
162
175
  else
163
- @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: context, schema: @schema)
176
+ 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."
164
177
  end
165
178
  elsif context.is_a?(Query::NullContext)
166
179
  top_level_profile
167
180
  else
168
- @schema.visibility_profile_class.new(context: context, schema: @schema)
181
+ @schema.visibility_profile_class.new(context: context, schema: @schema, visibility: self)
169
182
  end
170
183
  end
171
184
 
@@ -178,7 +191,7 @@ module GraphQL
178
191
  if refresh
179
192
  @top_level_profile = nil
180
193
  end
181
- @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema)
194
+ @top_level_profile ||= @schema.visibility_profile_class.new(context: @schema.null_context, schema: @schema, visibility: self)
182
195
  end
183
196
 
184
197
  private
@@ -199,7 +212,11 @@ module GraphQL
199
212
  def load_all(types: nil)
200
213
  if @visit.nil?
201
214
  # Set up the visit system
202
- @interface_type_memberships = Hash.new { |h, interface_type| h[interface_type] = [] }.compare_by_identity
215
+ @interface_type_memberships = Hash.new { |h, interface_type|
216
+ h[interface_type] = Hash.new { |h2, obj_type|
217
+ h2[obj_type] = []
218
+ }.compare_by_identity
219
+ }.compare_by_identity
203
220
  @directives = []
204
221
  @types = {} # String => Module
205
222
  @all_references = Hash.new { |h, member| h[member] = Set.new.compare_by_identity }.compare_by_identity
@@ -222,7 +239,9 @@ module GraphQL
222
239
  elsif member.respond_to?(:interface_type_memberships)
223
240
  member.interface_type_memberships.each do |itm|
224
241
  @all_references[itm.abstract_type] << member
225
- @interface_type_memberships[itm.abstract_type] << itm
242
+ # `itm.object_type` may not actually be `member` if this implementation
243
+ # is inherited from a superclass
244
+ @interface_type_memberships[itm.abstract_type][member] << itm
226
245
  end
227
246
  elsif member < GraphQL::Schema::Union
228
247
  @unions_for_references << member
@@ -271,13 +290,11 @@ module GraphQL
271
290
 
272
291
  # TODO: somehow don't iterate over all these,
273
292
  # only the ones that may have been modified
274
- @interface_type_memberships.each do |int_type, type_memberships|
275
- referers = @all_references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) }
276
- if !referers.empty?
277
- type_memberships.each do |type_membership|
278
- implementor_type = type_membership.object_type
279
- # Add new items only:
280
- @all_references[implementor_type] |= referers
293
+ @interface_type_memberships.each do |int_type, obj_type_memberships|
294
+ referrers = @all_references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) }
295
+ if !referrers.empty?
296
+ obj_type_memberships.each_key do |impl_type|
297
+ @all_references[impl_type] |= referrers
281
298
  end
282
299
  end
283
300
  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(abstract_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
@@ -226,7 +232,22 @@ module GraphQL
226
232
 
227
233
  # @return [Boolean] True if this type is used for `loads:` but not in the schema otherwise and not _explicitly_ hidden.
228
234
  def loadable?(type, _ctx)
229
- !reachable_type_set.include?(type) && visible_type?(type)
235
+ visible_type?(type) &&
236
+ !referenced?(type) &&
237
+ (type.respond_to?(:interfaces) ? interfaces(type).all? { |i| loadable?(i, _ctx) } : true)
238
+ end
239
+
240
+ # This abstract type was determined to be used for `loads` only.
241
+ # All its possible types are valid possibilities here -- no filtering.
242
+ def loadable_possible_types(abstract_type, _ctx)
243
+ @loadable_possible_types ||= read_through do |t|
244
+ if t.is_a?(Class) # union
245
+ t.possible_types
246
+ else
247
+ @schema.possible_types(abstract_type)
248
+ end
249
+ end
250
+ @loadable_possible_types[abstract_type]
230
251
  end
231
252
 
232
253
  # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)