graphql 2.4.13 → 2.5.11

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/query_complexity.rb +87 -7
  3. data/lib/graphql/backtrace/table.rb +37 -14
  4. data/lib/graphql/current.rb +1 -1
  5. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  6. data/lib/graphql/dashboard/installable.rb +22 -0
  7. data/lib/graphql/dashboard/limiters.rb +93 -0
  8. data/lib/graphql/dashboard/operation_store.rb +199 -0
  9. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  10. data/lib/graphql/dashboard/statics/dashboard.css +27 -0
  11. data/lib/graphql/dashboard/statics/dashboard.js +74 -9
  12. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  13. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  14. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  15. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  16. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  17. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  18. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  19. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  20. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  21. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  24. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  25. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  26. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  27. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +49 -1
  28. data/lib/graphql/dashboard.rb +45 -29
  29. data/lib/graphql/dataloader/active_record_association_source.rb +28 -8
  30. data/lib/graphql/dataloader/active_record_source.rb +26 -5
  31. data/lib/graphql/dataloader/null_dataloader.rb +7 -0
  32. data/lib/graphql/dataloader/source.rb +16 -4
  33. data/lib/graphql/dig.rb +2 -1
  34. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  35. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -1
  36. data/lib/graphql/execution/interpreter/runtime.rb +163 -59
  37. data/lib/graphql/execution/interpreter.rb +5 -13
  38. data/lib/graphql/execution/multiplex.rb +6 -1
  39. data/lib/graphql/invalid_null_error.rb +15 -2
  40. data/lib/graphql/language/lexer.rb +9 -2
  41. data/lib/graphql/language/nodes.rb +5 -1
  42. data/lib/graphql/language/parser.rb +14 -6
  43. data/lib/graphql/query/context.rb +3 -8
  44. data/lib/graphql/query/partial.rb +179 -0
  45. data/lib/graphql/query.rb +59 -55
  46. data/lib/graphql/schema/addition.rb +3 -1
  47. data/lib/graphql/schema/always_visible.rb +1 -0
  48. data/lib/graphql/schema/argument.rb +9 -3
  49. data/lib/graphql/schema/build_from_definition.rb +96 -47
  50. data/lib/graphql/schema/directive/flagged.rb +2 -0
  51. data/lib/graphql/schema/directive.rb +33 -1
  52. data/lib/graphql/schema/field.rb +23 -1
  53. data/lib/graphql/schema/input_object.rb +38 -30
  54. data/lib/graphql/schema/list.rb +1 -1
  55. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  56. data/lib/graphql/schema/member/has_dataloader.rb +4 -2
  57. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  58. data/lib/graphql/schema/member/has_interfaces.rb +2 -2
  59. data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
  60. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  61. data/lib/graphql/schema/resolver.rb +1 -0
  62. data/lib/graphql/schema/scalar.rb +1 -6
  63. data/lib/graphql/schema/timeout.rb +19 -2
  64. data/lib/graphql/schema/validator/required_validator.rb +15 -6
  65. data/lib/graphql/schema/visibility/migration.rb +2 -2
  66. data/lib/graphql/schema/visibility/profile.rb +107 -21
  67. data/lib/graphql/schema/visibility.rb +41 -29
  68. data/lib/graphql/schema/warden.rb +13 -5
  69. data/lib/graphql/schema.rb +228 -32
  70. data/lib/graphql/static_validation/all_rules.rb +2 -2
  71. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  72. data/lib/graphql/static_validation/rules/fields_will_merge.rb +78 -16
  73. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  74. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  75. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  76. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  77. data/lib/graphql/testing/helpers.rb +5 -2
  78. data/lib/graphql/tracing/active_support_notifications_trace.rb +7 -0
  79. data/lib/graphql/tracing/appoptics_tracing.rb +5 -0
  80. data/lib/graphql/tracing/appsignal_trace.rb +26 -61
  81. data/lib/graphql/tracing/data_dog_trace.rb +41 -164
  82. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  83. data/lib/graphql/tracing/new_relic_trace.rb +34 -164
  84. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  85. data/lib/graphql/tracing/null_trace.rb +1 -1
  86. data/lib/graphql/tracing/perfetto_trace.rb +16 -19
  87. data/lib/graphql/tracing/prometheus_trace.rb +47 -74
  88. data/lib/graphql/tracing/scout_trace.rb +25 -59
  89. data/lib/graphql/tracing/sentry_trace.rb +56 -99
  90. data/lib/graphql/tracing/statsd_trace.rb +24 -47
  91. data/lib/graphql/tracing/trace.rb +0 -17
  92. data/lib/graphql/tracing.rb +1 -0
  93. data/lib/graphql/type_kinds.rb +1 -0
  94. data/lib/graphql/version.rb +1 -1
  95. data/lib/graphql.rb +1 -1
  96. metadata +35 -26
  97. data/lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb +0 -63
  98. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ module RactorShareable
5
+ def self.extended(schema_class)
6
+ schema_class.extend(SchemaExtension)
7
+ schema_class.freeze_schema
8
+ end
9
+
10
+ module SchemaExtension
11
+
12
+ def freeze_error_handlers(handlers)
13
+ handlers[:subclass_handlers].default_proc = nil
14
+ handlers[:subclass_handlers].each do |_class, subclass_handlers|
15
+ freeze_error_handlers(subclass_handlers)
16
+ end
17
+ Ractor.make_shareable(handlers)
18
+ end
19
+
20
+ def freeze_schema
21
+ # warm some ivars:
22
+ default_analysis_engine
23
+ default_execution_strategy
24
+ GraphQL.default_parser
25
+ default_logger
26
+ freeze_error_handlers(error_handlers)
27
+ # TODO: this freezes errors of parent classes which could cause trouble
28
+ parent_class = superclass
29
+ while parent_class.respond_to?(:error_handlers)
30
+ freeze_error_handlers(parent_class.error_handlers)
31
+ parent_class = parent_class.superclass
32
+ end
33
+
34
+ own_tracers.freeze
35
+ @frozen_tracers = tracers.freeze
36
+ own_trace_modes.each do |m|
37
+ trace_options_for(m)
38
+ build_trace_mode(m)
39
+ end
40
+ build_trace_mode(:default)
41
+ Ractor.make_shareable(@trace_options_for_mode)
42
+ Ractor.make_shareable(own_trace_modes)
43
+ Ractor.make_shareable(own_multiplex_analyzers)
44
+ @frozen_multiplex_analyzers = Ractor.make_shareable(multiplex_analyzers)
45
+ Ractor.make_shareable(own_query_analyzers)
46
+ @frozen_query_analyzers = Ractor.make_shareable(query_analyzers)
47
+ Ractor.make_shareable(own_plugins)
48
+ own_plugins.each do |(plugin, options)|
49
+ Ractor.make_shareable(plugin)
50
+ Ractor.make_shareable(options)
51
+ end
52
+ @frozen_plugins = Ractor.make_shareable(plugins)
53
+ Ractor.make_shareable(own_references_to)
54
+ @frozen_directives = Ractor.make_shareable(directives)
55
+
56
+ Ractor.make_shareable(visibility)
57
+ Ractor.make_shareable(introspection_system)
58
+ extend(FrozenMethods)
59
+
60
+ Ractor.make_shareable(self)
61
+ superclass.respond_to?(:freeze_schema) && superclass.freeze_schema
62
+ end
63
+
64
+ module FrozenMethods
65
+ def tracers; @frozen_tracers; end
66
+ def multiplex_analyzers; @frozen_multiplex_analyzers; end
67
+ def query_analyzers; @frozen_query_analyzers; end
68
+ def plugins; @frozen_plugins; end
69
+ def directives; @frozen_directives; end
70
+
71
+ # This actually accumulates info during execution...
72
+ # How to support it?
73
+ def lazy?(_obj); false; end
74
+ def sync_lazy(obj); obj; end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -28,6 +28,7 @@ module GraphQL
28
28
  extend Schema::Member::HasPath
29
29
  extend Schema::Member::HasDirectives
30
30
  include Schema::Member::HasDataloader
31
+ extend Schema::Member::HasDeprecationReason
31
32
 
32
33
  # @param object [Object] The application object that this field is being resolved on
33
34
  # @param context [GraphQL::Query::Context]
@@ -50,12 +50,7 @@ module GraphQL
50
50
  end
51
51
 
52
52
  if coerced_result.nil?
53
- str_value = if value == Float::INFINITY
54
- ""
55
- else
56
- " #{GraphQL::Language.serialize(value)}"
57
- end
58
- Query::InputValidationResult.from_problem("Could not coerce value#{str_value} to #{graphql_name}")
53
+ Query::InputValidationResult.from_problem("Could not coerce value #{GraphQL::Language.serialize(value)} to #{graphql_name}")
59
54
  elsif coerced_result.is_a?(GraphQL::CoercionError)
60
55
  Query::InputValidationResult.from_problem(coerced_result.message, message: coerced_result.message, extensions: coerced_result.extensions)
61
56
  else
@@ -71,15 +71,23 @@ module GraphQL
71
71
  def execute_field(query:, field:, **_rest)
72
72
  timeout_state = query.context.namespace(@timeout).fetch(:state)
73
73
  # If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
74
- if timeout_state != false && Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
74
+ if timeout_state == false
75
+ super
76
+ elsif Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
75
77
  error = GraphQL::Schema::Timeout::TimeoutError.new(field)
76
78
  # Only invoke the timeout callback for the first timeout
77
79
  if !timeout_state[:timed_out]
78
80
  timeout_state[:timed_out] = true
79
81
  @timeout.handle_timeout(error, query)
82
+ timeout_state = query.context.namespace(@timeout).fetch(:state)
80
83
  end
81
84
 
82
- error
85
+ # `handle_timeout` may have set this to be `false`
86
+ if timeout_state != false
87
+ error
88
+ else
89
+ super
90
+ end
83
91
  else
84
92
  super
85
93
  end
@@ -102,6 +110,15 @@ module GraphQL
102
110
  # override to do something interesting
103
111
  end
104
112
 
113
+ # Call this method (eg, from {#handle_timeout}) to disable timeout tracking
114
+ # for the given query.
115
+ # @param query [GraphQL::Query]
116
+ # @return [void]
117
+ def disable_timeout(query)
118
+ query.context.namespace(self)[:state] = false
119
+ nil
120
+ end
121
+
105
122
  # This error is raised when a query exceeds `max_seconds`.
106
123
  # Since it's a child of {GraphQL::ExecutionError},
107
124
  # its message will be added to the response's `errors` key.
@@ -96,26 +96,35 @@ module GraphQL
96
96
  end
97
97
 
98
98
  def build_message(context)
99
- argument_definitions = @validated.arguments(context).values
99
+ argument_definitions = context.types.arguments(@validated)
100
+
100
101
  required_names = @one_of.map do |arg_keyword|
101
102
  if arg_keyword.is_a?(Array)
102
- names = arg_keyword.map { |arg| arg_keyword_to_grapqhl_name(argument_definitions, arg) }
103
+ names = arg_keyword.map { |arg| arg_keyword_to_graphql_name(argument_definitions, arg) }
104
+ names.compact! # hidden arguments are `nil`
103
105
  "(" + names.join(" and ") + ")"
104
106
  else
105
- arg_keyword_to_grapqhl_name(argument_definitions, arg_keyword)
107
+ arg_keyword_to_graphql_name(argument_definitions, arg_keyword)
106
108
  end
107
109
  end
110
+ required_names.compact! # remove entries for hidden arguments
111
+
108
112
 
109
- if required_names.size == 1
113
+ case required_names.size
114
+ when 0
115
+ # The required definitions were hidden from the client.
116
+ # Another option here would be to raise an error in the application....
117
+ "%{validated} is missing a required argument."
118
+ when 1
110
119
  "%{validated} must include the following argument: #{required_names.first}."
111
120
  else
112
121
  "%{validated} must include exactly one of the following arguments: #{required_names.join(", ")}."
113
122
  end
114
123
  end
115
124
 
116
- def arg_keyword_to_grapqhl_name(argument_definitions, arg_keyword)
125
+ def arg_keyword_to_graphql_name(argument_definitions, arg_keyword)
117
126
  argument_definition = argument_definitions.find { |defn| defn.keyword == arg_keyword }
118
- argument_definition.graphql_name
127
+ argument_definition&.graphql_name
119
128
  end
120
129
  end
121
130
  end
@@ -76,10 +76,10 @@ module GraphQL
76
76
  end
77
77
  end
78
78
 
79
- def initialize(context:, schema:, name: nil)
79
+ def initialize(context:, schema:, name: nil, visibility:)
80
80
  @name = name
81
81
  @skip_error = context[:skip_visibility_migration_error] || context.is_a?(Query::NullContext) || context.is_a?(Hash)
82
- @profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema)
82
+ @profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema, visibility: visibility)
83
83
  if !@skip_error
84
84
  context[:visibility_migration_running] = true
85
85
  warden_ctx_vals = context.to_h.dup
@@ -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,7 +118,7 @@ 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
87
123
 
88
124
  @loadable_possible_types = Hash.new { |h, union_type| h[union_type] = union_type.possible_types }.compare_by_identity
@@ -113,7 +149,7 @@ module GraphQL
113
149
  end
114
150
 
115
151
  def type(type_name)
116
- t = @schema.visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
152
+ t = @visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
117
153
  if t
118
154
  if t.is_a?(Array)
119
155
  vis_t = nil
@@ -180,7 +216,7 @@ module GraphQL
180
216
  if arg.is_a?(Array)
181
217
  visible_arg = nil
182
218
  arg.each do |arg_defn|
183
- if @cached_visible_arguments[arg_defn]
219
+ if @cached_visible_arguments[owner][arg_defn]
184
220
  if visible_arg.nil?
185
221
  visible_arg = arg_defn
186
222
  else
@@ -190,7 +226,7 @@ module GraphQL
190
226
  end
191
227
  visible_arg
192
228
  else
193
- if arg && @cached_visible_arguments[arg]
229
+ if arg && @cached_visible_arguments[owner][arg]
194
230
  arg
195
231
  else
196
232
  nil
@@ -241,14 +277,13 @@ module GraphQL
241
277
  end
242
278
 
243
279
  def directives
244
- @all_directives ||= @schema.visibility.all_directives.select { |dir|
245
- @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)) }
246
282
  }
247
283
  end
248
284
 
249
285
  def loadable?(t, _ctx)
250
- load_all_types
251
- !@all_types[t.graphql_name] && @cached_visible[t]
286
+ @cached_visible[t] && !referenced?(t)
252
287
  end
253
288
 
254
289
  def loadable_possible_types(t, _ctx)
@@ -268,13 +303,51 @@ module GraphQL
268
303
  @cached_visible[enum_value]
269
304
  end
270
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
+
271
342
  private
272
343
 
273
344
  def non_duplicate_items(definitions, visibility_cache)
274
345
  non_dups = []
346
+ names = Set.new
275
347
  definitions.each do |defn|
276
348
  if visibility_cache[defn]
277
- 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 }
278
351
  raise_duplicate_definition(dup_defn, defn)
279
352
  end
280
353
  non_dups << defn
@@ -292,7 +365,7 @@ module GraphQL
292
365
  @all_types_loaded = true
293
366
  visit = Visibility::Visit.new(@schema) do |member|
294
367
  if member.is_a?(Module) && member.respond_to?(:kind)
295
- if @cached_visible[member]
368
+ if @cached_visible[member] && referenced?(member)
296
369
  type_name = member.graphql_name
297
370
  if (prev_t = @all_types[type_name]) && !prev_t.equal?(member)
298
371
  raise_duplicate_definition(member, prev_t)
@@ -312,16 +385,29 @@ module GraphQL
312
385
  end
313
386
 
314
387
  def referenced?(type_defn)
315
- @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
316
400
  end
317
401
 
318
402
  def possible_types_for(type)
319
403
  case type.kind.name
320
404
  when "INTERFACE"
321
405
  pts = []
322
- @schema.visibility.all_interface_type_memberships[type].each do |(itm, impl_type)|
323
- if @cached_visible[itm] && @cached_visible[impl_type] && referenced?(impl_type)
324
- pts << impl_type
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
325
411
  end
326
412
  end
327
413
  pts
@@ -12,15 +12,12 @@ module GraphQL
12
12
  # @param profiles [Hash<Symbol => Hash>] A hash of `name => context` pairs for preloading visibility profiles
13
13
  # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?`)
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? : nil), migration_errors: false)
16
16
  profiles&.each { |name, ctx|
17
17
  ctx[:visibility_profile] = name
18
18
  ctx.freeze
19
19
  }
20
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
24
21
  end
25
22
 
26
23
  def initialize(schema, dynamic:, preload:, profiles:, migration_errors:)
@@ -43,6 +40,17 @@ module GraphQL
43
40
  @types = nil
44
41
  @all_references = nil
45
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
46
54
  end
47
55
 
48
56
  def all_directives
@@ -65,6 +73,8 @@ module GraphQL
65
73
  @types[type_name]
66
74
  end
67
75
 
76
+ attr_accessor :types
77
+
68
78
  def preload?
69
79
  @preload
70
80
  end
@@ -86,7 +96,7 @@ module GraphQL
86
96
  ensure_all_loaded(types_to_visit)
87
97
  @profiles.each do |profile_name, example_ctx|
88
98
  prof = profile_for(example_ctx)
89
- prof.all_types # force loading
99
+ prof.preload
90
100
  end
91
101
  end
92
102
 
@@ -148,28 +158,27 @@ module GraphQL
148
158
 
149
159
  attr_reader :cached_profiles
150
160
 
151
- def profile_for(context, visibility_profile = context[:visibility_profile])
161
+ def profile_for(context)
152
162
  if !@profiles.empty?
153
- if visibility_profile.nil?
154
- if @dynamic
155
- if context.is_a?(Query::NullContext)
156
- top_level_profile
157
- else
158
- @schema.visibility_profile_class.new(context: context, schema: @schema)
159
- end
160
- elsif !@profiles.empty?
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."
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)
162
172
  end
163
- elsif !@profiles.include?(visibility_profile)
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."
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."
165
175
  else
166
- profile_ctx = @profiles[visibility_profile]
167
- @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: profile_ctx, 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."
168
177
  end
169
178
  elsif context.is_a?(Query::NullContext)
170
179
  top_level_profile
171
180
  else
172
- @schema.visibility_profile_class.new(context: context, schema: @schema)
181
+ @schema.visibility_profile_class.new(context: context, schema: @schema, visibility: self)
173
182
  end
174
183
  end
175
184
 
@@ -182,7 +191,7 @@ module GraphQL
182
191
  if refresh
183
192
  @top_level_profile = nil
184
193
  end
185
- @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema)
194
+ @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema, visibility: self)
186
195
  end
187
196
 
188
197
  private
@@ -203,7 +212,11 @@ module GraphQL
203
212
  def load_all(types: nil)
204
213
  if @visit.nil?
205
214
  # Set up the visit system
206
- @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
207
220
  @directives = []
208
221
  @types = {} # String => Module
209
222
  @all_references = Hash.new { |h, member| h[member] = Set.new.compare_by_identity }.compare_by_identity
@@ -228,7 +241,7 @@ module GraphQL
228
241
  @all_references[itm.abstract_type] << member
229
242
  # `itm.object_type` may not actually be `member` if this implementation
230
243
  # is inherited from a superclass
231
- @interface_type_memberships[itm.abstract_type] << [itm, member]
244
+ @interface_type_memberships[itm.abstract_type][member] << itm
232
245
  end
233
246
  elsif member < GraphQL::Schema::Union
234
247
  @unions_for_references << member
@@ -277,12 +290,11 @@ module GraphQL
277
290
 
278
291
  # TODO: somehow don't iterate over all these,
279
292
  # 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
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
286
298
  end
287
299
  end
288
300
  end
@@ -107,7 +107,7 @@ module GraphQL
107
107
  def get_field(parent_type, field_name); @schema.get_field(parent_type, field_name); end
108
108
  def reachable_type?(type_name); true; end
109
109
  def loadable?(type, _ctx); true; end
110
- def loadable_possible_types(union_type, _ctx); union_type.possible_types; end
110
+ def loadable_possible_types(abstract_type, _ctx); union_type.possible_types; end
111
111
  def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop
112
112
  def possible_types(type_defn); @schema.possible_types(type_defn, Query::NullContext.instance, false); end
113
113
  def interfaces(obj_type); obj_type.interfaces; end
@@ -232,14 +232,22 @@ module GraphQL
232
232
 
233
233
  # @return [Boolean] True if this type is used for `loads:` but not in the schema otherwise and not _explicitly_ hidden.
234
234
  def loadable?(type, _ctx)
235
- !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)
236
238
  end
237
239
 
238
- def loadable_possible_types(union_type, _ctx)
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)
239
243
  @loadable_possible_types ||= read_through do |t|
240
- t.possible_types # unfiltered
244
+ if t.is_a?(Class) # union
245
+ t.possible_types
246
+ else
247
+ @schema.possible_types(abstract_type)
248
+ end
241
249
  end
242
- @loadable_possible_types[union_type]
250
+ @loadable_possible_types[abstract_type]
243
251
  end
244
252
 
245
253
  # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)