graphql 2.4.3 → 2.5.3

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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/query_complexity.rb +87 -7
  4. data/lib/graphql/analysis/visitor.rb +38 -41
  5. data/lib/graphql/analysis.rb +15 -12
  6. data/lib/graphql/autoload.rb +38 -0
  7. data/lib/graphql/backtrace/table.rb +118 -55
  8. data/lib/graphql/backtrace.rb +1 -19
  9. data/lib/graphql/current.rb +7 -2
  10. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  11. data/lib/graphql/dashboard/installable.rb +22 -0
  12. data/lib/graphql/dashboard/limiters.rb +93 -0
  13. data/lib/graphql/dashboard/operation_store.rb +199 -0
  14. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  15. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  16. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  17. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  18. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  19. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  20. data/lib/graphql/dashboard/statics/icon.png +0 -0
  21. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  24. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  25. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  26. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  37. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  38. data/lib/graphql/dashboard.rb +158 -0
  39. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  40. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  41. data/lib/graphql/dataloader/async_dataloader.rb +21 -9
  42. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  43. data/lib/graphql/dataloader/source.rb +3 -3
  44. data/lib/graphql/dataloader.rb +43 -14
  45. data/lib/graphql/dig.rb +2 -1
  46. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  47. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -4
  48. data/lib/graphql/execution/interpreter/runtime.rb +96 -52
  49. data/lib/graphql/execution/interpreter.rb +16 -7
  50. data/lib/graphql/execution/multiplex.rb +6 -5
  51. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  52. data/lib/graphql/invalid_name_error.rb +1 -1
  53. data/lib/graphql/invalid_null_error.rb +19 -16
  54. data/lib/graphql/language/cache.rb +13 -0
  55. data/lib/graphql/language/document_from_schema_definition.rb +8 -7
  56. data/lib/graphql/language/lexer.rb +11 -4
  57. data/lib/graphql/language/nodes.rb +3 -0
  58. data/lib/graphql/language/parser.rb +15 -8
  59. data/lib/graphql/language/printer.rb +8 -8
  60. data/lib/graphql/language/static_visitor.rb +37 -33
  61. data/lib/graphql/language/visitor.rb +59 -55
  62. data/lib/graphql/pagination/connection.rb +1 -1
  63. data/lib/graphql/query/context/scoped_context.rb +1 -1
  64. data/lib/graphql/query/context.rb +7 -5
  65. data/lib/graphql/query/variable_validation_error.rb +1 -1
  66. data/lib/graphql/query.rb +22 -32
  67. data/lib/graphql/railtie.rb +7 -0
  68. data/lib/graphql/schema/addition.rb +1 -1
  69. data/lib/graphql/schema/always_visible.rb +1 -0
  70. data/lib/graphql/schema/argument.rb +7 -8
  71. data/lib/graphql/schema/build_from_definition.rb +99 -53
  72. data/lib/graphql/schema/directive/flagged.rb +3 -1
  73. data/lib/graphql/schema/directive.rb +2 -2
  74. data/lib/graphql/schema/enum.rb +36 -1
  75. data/lib/graphql/schema/enum_value.rb +1 -1
  76. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  77. data/lib/graphql/schema/field.rb +27 -13
  78. data/lib/graphql/schema/field_extension.rb +1 -1
  79. data/lib/graphql/schema/has_single_input_argument.rb +3 -1
  80. data/lib/graphql/schema/input_object.rb +77 -40
  81. data/lib/graphql/schema/interface.rb +3 -2
  82. data/lib/graphql/schema/list.rb +1 -1
  83. data/lib/graphql/schema/loader.rb +1 -1
  84. data/lib/graphql/schema/member/has_arguments.rb +25 -17
  85. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  86. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  87. data/lib/graphql/schema/member/has_directives.rb +4 -4
  88. data/lib/graphql/schema/member/has_fields.rb +19 -1
  89. data/lib/graphql/schema/member/has_interfaces.rb +5 -5
  90. data/lib/graphql/schema/member/has_validators.rb +1 -1
  91. data/lib/graphql/schema/member/scoped.rb +1 -1
  92. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  93. data/lib/graphql/schema/member.rb +1 -0
  94. data/lib/graphql/schema/object.rb +25 -8
  95. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  96. data/lib/graphql/schema/resolver.rb +12 -10
  97. data/lib/graphql/schema/subscription.rb +52 -6
  98. data/lib/graphql/schema/union.rb +1 -1
  99. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  100. data/lib/graphql/schema/validator.rb +1 -1
  101. data/lib/graphql/schema/visibility/migration.rb +1 -0
  102. data/lib/graphql/schema/visibility/profile.rb +98 -244
  103. data/lib/graphql/schema/visibility/visit.rb +190 -0
  104. data/lib/graphql/schema/visibility.rb +178 -38
  105. data/lib/graphql/schema/warden.rb +18 -5
  106. data/lib/graphql/schema.rb +266 -54
  107. data/lib/graphql/static_validation/all_rules.rb +1 -1
  108. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  109. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  110. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
  111. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  112. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  113. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  114. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  115. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  116. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  117. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  118. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  119. data/lib/graphql/static_validation/validation_context.rb +1 -0
  120. data/lib/graphql/static_validation/validator.rb +6 -1
  121. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  122. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  123. data/lib/graphql/subscriptions/event.rb +12 -1
  124. data/lib/graphql/subscriptions/serialize.rb +1 -1
  125. data/lib/graphql/subscriptions.rb +1 -1
  126. data/lib/graphql/testing/helpers.rb +7 -4
  127. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  128. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  129. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  130. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  131. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  132. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  133. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  134. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  135. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  136. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  137. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  138. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  139. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  140. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  141. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  142. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  143. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  144. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  145. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  146. data/lib/graphql/tracing/null_trace.rb +9 -0
  147. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  148. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  149. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  150. data/lib/graphql/tracing/platform_trace.rb +5 -0
  151. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  152. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  153. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  154. data/lib/graphql/tracing/scout_trace.rb +32 -55
  155. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  156. data/lib/graphql/tracing/sentry_trace.rb +62 -94
  157. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  158. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  159. data/lib/graphql/tracing/trace.rb +111 -1
  160. data/lib/graphql/tracing.rb +31 -30
  161. data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
  162. data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
  163. data/lib/graphql/types.rb +18 -11
  164. data/lib/graphql/version.rb +1 -1
  165. data/lib/graphql.rb +55 -47
  166. metadata +146 -11
  167. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  168. data/lib/graphql/backtrace/trace.rb +0 -93
  169. data/lib/graphql/backtrace/tracer.rb +0 -80
  170. data/lib/graphql/schema/null_mask.rb +0 -11
  171. 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
 
@@ -38,42 +38,30 @@ module GraphQL
38
38
  @all_types = {}
39
39
  @all_types_loaded = false
40
40
  @unvisited_types = []
41
- @referenced_types = Hash.new { |h, type_defn| h[type_defn] = [] }.compare_by_identity
42
- @cached_directives = {}
43
41
  @all_directives = nil
44
- @cached_visible = Hash.new { |h, member|
45
- h[member] = @schema.visible?(member, @context)
46
- }.compare_by_identity
42
+ @cached_visible = Hash.new { |h, member| h[member] = @schema.visible?(member, @context) }.compare_by_identity
47
43
 
48
44
  @cached_visible_fields = Hash.new { |h, owner|
49
45
  h[owner] = Hash.new do |h2, field|
50
- h2[field] = if @cached_visible[field] &&
51
- (ret_type = field.type.unwrap) &&
52
- @cached_visible[ret_type] &&
53
- (owner == field.owner || (!owner.kind.object?) || field_on_visible_interface?(field, owner))
54
-
55
- if !field.introspection?
56
- # The problem is that some introspection fields may have references
57
- # to non-custom introspection types.
58
- # If those were added here, they'd cause a DuplicateNamesError.
59
- # This is basically a bug -- those fields _should_ reference the custom types.
60
- add_type(ret_type, field)
46
+ h2[field] = visible_field_for(owner, field)
47
+ end.compare_by_identity
48
+ }.compare_by_identity
49
+
50
+ @cached_visible_arguments = Hash.new do |h, owner|
51
+ h[owner] = Hash.new do |h2, arg|
52
+ h2[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type]
53
+ case owner
54
+ when GraphQL::Schema::Field
55
+ @cached_visible_fields[owner.owner][owner]
56
+ when Class
57
+ @cached_visible[owner]
58
+ else
59
+ raise "Unexpected argument owner for `#{arg.path}`: #{owner.inspect}"
61
60
  end
62
- true
63
61
  else
64
62
  false
65
63
  end
66
64
  end.compare_by_identity
67
- }.compare_by_identity
68
-
69
- @cached_visible_arguments = Hash.new do |h, arg|
70
- h[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type]
71
- add_type(arg_type, arg)
72
- arg.validate_default_value
73
- true
74
- else
75
- false
76
- end
77
65
  end.compare_by_identity
78
66
 
79
67
  @cached_parent_fields = Hash.new do |h, type|
@@ -88,39 +76,7 @@ module GraphQL
88
76
  end
89
77
  end.compare_by_identity
90
78
 
91
- @cached_possible_types = Hash.new do |h, type|
92
- h[type] = case type.kind.name
93
- when "INTERFACE"
94
- load_all_types
95
- pts = []
96
- @unfiltered_interface_type_memberships[type].each { |itm|
97
- if @cached_visible[itm] && (ot = itm.object_type) && @cached_visible[ot] && referenced?(ot)
98
- pts << ot
99
- end
100
- }
101
- pts
102
- when "UNION"
103
- pts = []
104
- type.type_memberships.each { |tm|
105
- if @cached_visible[tm] &&
106
- (ot = tm.object_type) &&
107
- @cached_visible[ot] &&
108
- referenced?(ot)
109
- pts << ot
110
- end
111
- }
112
- pts
113
- when "OBJECT"
114
- load_all_types
115
- if @all_types[type.graphql_name] == type
116
- [type]
117
- else
118
- EmptyObjects::EMPTY_ARRAY
119
- end
120
- else
121
- GraphQL::EmptyObjects::EMPTY_ARRAY
122
- end
123
- end.compare_by_identity
79
+ @cached_possible_types = Hash.new { |h, type| h[type] = possible_types_for(type) }.compare_by_identity
124
80
 
125
81
  @cached_enum_values = Hash.new do |h, enum_t|
126
82
  values = non_duplicate_items(enum_t.enum_values(@context), @cached_visible)
@@ -135,8 +91,10 @@ module GraphQL
135
91
  end.compare_by_identity
136
92
 
137
93
  @cached_arguments = Hash.new do |h, owner|
138
- h[owner] = non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments)
94
+ h[owner] = non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments[owner])
139
95
  end.compare_by_identity
96
+
97
+ @loadable_possible_types = Hash.new { |h, union_type| h[union_type] = union_type.possible_types }.compare_by_identity
140
98
  end
141
99
 
142
100
  def field_on_visible_interface?(field, owner)
@@ -164,17 +122,12 @@ module GraphQL
164
122
  end
165
123
 
166
124
  def type(type_name)
167
- t = if (loaded_t = @all_types[type_name])
168
- loaded_t
169
- elsif !@all_types_loaded
170
- load_all_types
171
- @all_types[type_name]
172
- end
125
+ t = @schema.visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
173
126
  if t
174
127
  if t.is_a?(Array)
175
128
  vis_t = nil
176
129
  t.each do |t_defn|
177
- if @cached_visible[t_defn]
130
+ if @cached_visible[t_defn] && referenced?(t_defn)
178
131
  if vis_t.nil?
179
132
  vis_t = t_defn
180
133
  else
@@ -184,7 +137,7 @@ module GraphQL
184
137
  end
185
138
  vis_t
186
139
  else
187
- if t && @cached_visible[t]
140
+ if t && @cached_visible[t] && referenced?(t)
188
141
  t
189
142
  else
190
143
  nil
@@ -215,7 +168,7 @@ module GraphQL
215
168
  end
216
169
  end
217
170
  end
218
- visible_f.ensure_loaded
171
+ visible_f&.ensure_loaded
219
172
  elsif f && @cached_visible_fields[owner][f.ensure_loaded]
220
173
  f
221
174
  else
@@ -236,7 +189,7 @@ module GraphQL
236
189
  if arg.is_a?(Array)
237
190
  visible_arg = nil
238
191
  arg.each do |arg_defn|
239
- if @cached_visible_arguments[arg_defn]
192
+ if @cached_visible_arguments[owner][arg_defn]
240
193
  if visible_arg.nil?
241
194
  visible_arg = arg_defn
242
195
  else
@@ -246,7 +199,7 @@ module GraphQL
246
199
  end
247
200
  visible_arg
248
201
  else
249
- if arg && @cached_visible_arguments[arg]
202
+ if arg && @cached_visible_arguments[owner][arg]
250
203
  arg
251
204
  else
252
205
  nil
@@ -267,15 +220,15 @@ module GraphQL
267
220
  end
268
221
 
269
222
  def query_root
270
- add_if_visible(@schema.query)
223
+ ((t = @schema.query) && @cached_visible[t]) ? t : nil
271
224
  end
272
225
 
273
226
  def mutation_root
274
- add_if_visible(@schema.mutation)
227
+ ((t = @schema.mutation) && @cached_visible[t]) ? t : nil
275
228
  end
276
229
 
277
230
  def subscription_root
278
- add_if_visible(@schema.subscription)
231
+ ((t = @schema.subscription) && @cached_visible[t]) ? t : nil
279
232
  end
280
233
 
281
234
  def all_types
@@ -293,38 +246,31 @@ module GraphQL
293
246
  end
294
247
 
295
248
  def directive_exists?(dir_name)
296
- if (dir = @schema.directives[dir_name]) && @cached_visible[dir]
297
- !!dir
298
- else
299
- load_all_types
300
- !!@cached_directives[dir_name]
301
- end
249
+ directives.any? { |d| d.graphql_name == dir_name }
302
250
  end
303
251
 
304
252
  def directives
305
- @all_directives ||= begin
306
- load_all_types
307
- dirs = []
308
- @schema.directives.each do |name, dir_defn|
309
- if !@cached_directives[name] && @cached_visible[dir_defn]
310
- dirs << dir_defn
311
- end
312
- end
313
- dirs.concat(@cached_directives.values)
314
- end
253
+ @all_directives ||= @schema.visibility.all_directives.select { |dir|
254
+ @cached_visible[dir] && @schema.visibility.all_references[dir].any? { |ref| ref == true || (@cached_visible[ref] && referenced?(ref)) }
255
+ }
315
256
  end
316
257
 
317
258
  def loadable?(t, _ctx)
259
+ load_all_types
318
260
  !@all_types[t.graphql_name] && @cached_visible[t]
319
261
  end
320
262
 
263
+ def loadable_possible_types(t, _ctx)
264
+ @loadable_possible_types[t]
265
+ end
266
+
321
267
  def loaded_types
322
268
  @all_types.values
323
269
  end
324
270
 
325
- def reachable_type?(name)
271
+ def reachable_type?(type_name)
326
272
  load_all_types
327
- !!@all_types[name]
273
+ !!@all_types[type_name]
328
274
  end
329
275
 
330
276
  def visible_enum_value?(enum_value, _ctx = nil)
@@ -333,34 +279,13 @@ module GraphQL
333
279
 
334
280
  private
335
281
 
336
- def add_if_visible(t)
337
- (t && @cached_visible[t]) ? (add_type(t, true); t) : nil
338
- end
339
-
340
- def add_type(t, by_member)
341
- if t && @cached_visible[t]
342
- n = t.graphql_name
343
- if (prev_t = @all_types[n])
344
- if !prev_t.equal?(t)
345
- raise_duplicate_definition(prev_t, t)
346
- end
347
- false
348
- else
349
- @referenced_types[t] << by_member
350
- @all_types[n] = t
351
- @unvisited_types << t
352
- true
353
- end
354
- else
355
- false
356
- end
357
- end
358
-
359
282
  def non_duplicate_items(definitions, visibility_cache)
360
283
  non_dups = []
284
+ names = Set.new
361
285
  definitions.each do |defn|
362
286
  if visibility_cache[defn]
363
- if (dup_defn = non_dups.find { |d| d.graphql_name == defn.graphql_name })
287
+ if !names.add?(defn.graphql_name)
288
+ dup_defn = non_dups.find { |d| d.graphql_name == defn.graphql_name }
364
289
  raise_duplicate_definition(dup_defn, defn)
365
290
  end
366
291
  non_dups << defn
@@ -373,153 +298,82 @@ module GraphQL
373
298
  raise DuplicateNamesError.new(duplicated_name: first_defn.path, duplicated_definition_1: first_defn.inspect, duplicated_definition_2: second_defn.inspect)
374
299
  end
375
300
 
376
- def referenced?(t)
377
- load_all_types
378
- @referenced_types[t].any? { |reference| (reference == true) || @cached_visible[reference] }
379
- end
380
-
381
301
  def load_all_types
382
302
  return if @all_types_loaded
383
303
  @all_types_loaded = true
384
- entry_point_types = [
385
- query_root,
386
- mutation_root,
387
- subscription_root,
388
- *@schema.introspection_system.types.values,
389
- ]
390
-
391
- # Don't include any orphan_types whose interfaces aren't visible.
392
- @schema.orphan_types.each do |orphan_type|
393
- if @cached_visible[orphan_type] &&
394
- orphan_type.interface_type_memberships.any? { |tm| @cached_visible[tm] && @cached_visible[tm.abstract_type] }
395
- entry_point_types << orphan_type
396
- end
397
- end
398
-
399
- @schema.directives.each do |_dir_name, dir_class|
400
- if @cached_visible[dir_class]
401
- arguments(dir_class).each do |arg|
402
- entry_point_types << arg.type.unwrap
403
- end
404
- end
405
- end
406
-
407
- entry_point_types.compact! # Root types might be nil
408
- entry_point_types.flatten! # handle multiple defns
409
- entry_point_types.each { |t| add_type(t, true) }
410
-
411
- @unfiltered_interface_type_memberships = Hash.new { |h, k| h[k] = [] }.compare_by_identity
412
- @add_possible_types = Set.new
413
- @late_types = []
414
-
415
- while @unvisited_types.any? || @late_types.any?
416
- while t = @unvisited_types.pop
417
- # These have already been checked for `.visible?`
418
- visit_type(t)
419
- end
420
- @add_possible_types.each do |int_t|
421
- itms = @unfiltered_interface_type_memberships[int_t]
422
- itms.each do |itm|
423
- if @cached_visible[itm] && (obj_type = itm.object_type) && @cached_visible[obj_type]
424
- add_type(obj_type, itm)
304
+ visit = Visibility::Visit.new(@schema) do |member|
305
+ if member.is_a?(Module) && member.respond_to?(:kind)
306
+ if @cached_visible[member] && referenced?(member)
307
+ type_name = member.graphql_name
308
+ if (prev_t = @all_types[type_name]) && !prev_t.equal?(member)
309
+ raise_duplicate_definition(member, prev_t)
425
310
  end
311
+ @all_types[type_name] = member
312
+ true
313
+ else
314
+ false
426
315
  end
427
- end
428
- @add_possible_types.clear
429
-
430
- while (union_tm = @late_types.shift)
431
- late_obj_t = union_tm.object_type
432
- obj_t = @all_types[late_obj_t.graphql_name] || raise("Failed to resolve #{late_obj_t.graphql_name.inspect} from #{union_tm.inspect}")
433
- union_tm.abstract_type.assign_type_membership_object_type(obj_t)
316
+ else
317
+ @cached_visible[member]
434
318
  end
435
319
  end
436
-
320
+ visit.visit_each
437
321
  @all_types.delete_if { |type_name, type_defn| !referenced?(type_defn) }
438
322
  nil
439
323
  end
440
324
 
441
- def visit_type(type)
442
- visit_directives(type)
443
- case type.kind.name
444
- when "OBJECT", "INTERFACE"
445
- if type.kind.object?
446
- type.interface_type_memberships.each do |itm|
447
- @unfiltered_interface_type_memberships[itm.abstract_type] << itm
448
- end
449
- # recurse into visible implemented interfaces
450
- interfaces(type).each do |interface|
451
- add_type(interface, type)
452
- end
453
- else
454
- type.orphan_types.each { |t| add_type(t, type)}
325
+ def referenced?(type_defn)
326
+ @schema.visibility.all_references[type_defn].any? do |ref|
327
+ case ref
328
+ when GraphQL::Schema::Argument
329
+ @cached_visible_arguments[ref.owner][ref]
330
+ when GraphQL::Schema::Field
331
+ @cached_visible_fields[ref.owner][ref]
332
+ when Module
333
+ @cached_visible[ref]
334
+ when true
335
+ true
455
336
  end
337
+ end
338
+ end
456
339
 
457
- # recurse into visible fields
458
- t_f = type.all_field_definitions
459
- t_f.each do |field|
460
- field.ensure_loaded
461
- if @cached_visible[field]
462
- visit_directives(field)
463
- field_type = field.type.unwrap
464
- if field_type.kind.interface?
465
- @add_possible_types.add(field_type)
466
- end
467
- add_type(field_type, field)
468
-
469
- # recurse into visible arguments
470
- arguments(field).each do |argument|
471
- visit_directives(argument)
472
- add_type(argument.type.unwrap, argument)
473
- end
340
+ def possible_types_for(type)
341
+ case type.kind.name
342
+ when "INTERFACE"
343
+ pts = []
344
+ @schema.visibility.all_interface_type_memberships[type].each do |(itm, impl_type)|
345
+ if @cached_visible[itm] && @cached_visible[impl_type] && referenced?(impl_type)
346
+ pts << impl_type
474
347
  end
475
348
  end
476
- when "INPUT_OBJECT"
477
- # recurse into visible arguments
478
- arguments(type).each do |argument|
479
- visit_directives(argument)
480
- add_type(argument.type.unwrap, argument)
481
- end
349
+ pts
482
350
  when "UNION"
483
- # recurse into visible possible types
484
- type.type_memberships.each do |tm|
485
- if @cached_visible[tm]
486
- obj_t = tm.object_type
487
- if obj_t.is_a?(GraphQL::Schema::LateBoundType)
488
- @late_types << tm
489
- else
490
- if obj_t.is_a?(String)
491
- obj_t = Member::BuildType.constantize(obj_t)
492
- tm.object_type = obj_t
493
- end
494
- if @cached_visible[obj_t]
495
- add_type(obj_t, tm)
496
- end
497
- end
351
+ pts = []
352
+ type.type_memberships.each { |tm|
353
+ if @cached_visible[tm] &&
354
+ (ot = tm.object_type) &&
355
+ @cached_visible[ot] &&
356
+ referenced?(ot)
357
+ pts << ot
498
358
  end
359
+ }
360
+ pts
361
+ when "OBJECT"
362
+ if @cached_visible[type]
363
+ [type]
364
+ else
365
+ EmptyObjects::EMPTY_ARRAY
499
366
  end
500
- when "ENUM"
501
- enum_values(type).each do |val|
502
- visit_directives(val)
503
- end
504
- when "SCALAR"
505
- # pass
367
+ else
368
+ GraphQL::EmptyObjects::EMPTY_ARRAY
506
369
  end
507
370
  end
508
371
 
509
- def visit_directives(member)
510
- member.directives.each { |dir|
511
- dir_class = dir.class
512
- if @cached_visible[dir_class]
513
- dir_name = dir_class.graphql_name
514
- if (existing_dir = @cached_directives[dir_name])
515
- if existing_dir != dir_class
516
- raise ArgumentError, "Two directives for `@#{dir_name}`: #{existing_dir}, #{dir.class}"
517
- end
518
- else
519
- @cached_directives[dir.graphql_name] = dir_class
520
- end
521
- end
522
- }
372
+ def visible_field_for(owner, field)
373
+ @cached_visible[field] &&
374
+ (ret_type = field.type.unwrap) &&
375
+ @cached_visible[ret_type] &&
376
+ (owner == field.owner || (!owner.kind.object?) || field_on_visible_interface?(field, owner))
523
377
  end
524
378
  end
525
379
  end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Visibility
5
+ class Visit
6
+ def initialize(schema, &visit_block)
7
+ @schema = schema
8
+ @late_bound_types = nil
9
+ @unvisited_types = nil
10
+ # These accumulate between calls to prevent re-visiting the same types
11
+ @visited_types = Set.new.compare_by_identity
12
+ @visited_directives = Set.new.compare_by_identity
13
+ @visit_block = visit_block
14
+ end
15
+
16
+ def entry_point_types
17
+ ept = [
18
+ @schema.query,
19
+ @schema.mutation,
20
+ @schema.subscription,
21
+ *@schema.introspection_system.types.values,
22
+ *@schema.orphan_types,
23
+ ]
24
+ ept.compact!
25
+ ept
26
+ end
27
+
28
+ def entry_point_directives
29
+ @schema.directives.values
30
+ end
31
+
32
+ def visit_each(types: entry_point_types, directives: entry_point_directives)
33
+ @unvisited_types && raise("Can't re-enter `visit_each` on this Visit (another visit is already in progress)")
34
+ @unvisited_types = types
35
+ @late_bound_types = []
36
+ directives_to_visit = directives
37
+
38
+ while !@unvisited_types.empty? || !@late_bound_types.empty?
39
+ while (type = @unvisited_types.pop)
40
+ if @visited_types.add?(type) && @visit_block.call(type)
41
+ directives_to_visit.concat(type.directives)
42
+ case type.kind.name
43
+ when "OBJECT", "INTERFACE"
44
+ type.interface_type_memberships.each do |itm|
45
+ append_unvisited_type(type, itm.abstract_type)
46
+ end
47
+ if type.kind.interface?
48
+ type.orphan_types.each do |orphan_type|
49
+ append_unvisited_type(type, orphan_type)
50
+ end
51
+ end
52
+
53
+ type.all_field_definitions.each do |field|
54
+ field.ensure_loaded
55
+ if @visit_block.call(field)
56
+ directives_to_visit.concat(field.directives)
57
+ append_unvisited_type(type, field.type.unwrap)
58
+ field.all_argument_definitions.each do |argument|
59
+ if @visit_block.call(argument)
60
+ directives_to_visit.concat(argument.directives)
61
+ append_unvisited_type(field, argument.type.unwrap)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ when "INPUT_OBJECT"
67
+ type.all_argument_definitions.each do |argument|
68
+ if @visit_block.call(argument)
69
+ directives_to_visit.concat(argument.directives)
70
+ append_unvisited_type(type, argument.type.unwrap)
71
+ end
72
+ end
73
+ when "UNION"
74
+ type.type_memberships.each do |tm|
75
+ append_unvisited_type(type, tm.object_type)
76
+ end
77
+ when "ENUM"
78
+ type.all_enum_value_definitions.each do |val|
79
+ if @visit_block.call(val)
80
+ directives_to_visit.concat(val.directives)
81
+ end
82
+ end
83
+ when "SCALAR"
84
+ # pass -- nothing else to visit
85
+ else
86
+ raise "Invariant: unhandled type kind: #{type.kind.inspect}"
87
+ end
88
+ end
89
+ end
90
+
91
+ directives_to_visit.each do |dir|
92
+ dir_class = dir.is_a?(Class) ? dir : dir.class
93
+ if @visited_directives.add?(dir_class) && @visit_block.call(dir_class)
94
+ dir_class.all_argument_definitions.each do |arg_defn|
95
+ if @visit_block.call(arg_defn)
96
+ directives_to_visit.concat(arg_defn.directives)
97
+ append_unvisited_type(dir_class, arg_defn.type.unwrap)
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ missed_late_types_streak = 0
104
+ while (owner, late_type = @late_bound_types.shift)
105
+ if (late_type.is_a?(String) && (type = Member::BuildType.constantize(type))) ||
106
+ (late_type.is_a?(LateBoundType) && (type = @visited_types.find { |t| t.graphql_name == late_type.graphql_name }))
107
+ missed_late_types_streak = 0 # might succeed next round
108
+ update_type_owner(owner, type)
109
+ append_unvisited_type(owner, type)
110
+ else
111
+ # Didn't find it -- keep trying
112
+ missed_late_types_streak += 1
113
+ @late_bound_types << [owner, late_type]
114
+ if missed_late_types_streak == @late_bound_types.size
115
+ raise UnresolvedLateBoundTypeError.new(type: late_type)
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ @unvisited_types = nil
122
+ nil
123
+ end
124
+
125
+ private
126
+
127
+ def append_unvisited_type(owner, type)
128
+ if type.is_a?(LateBoundType) || type.is_a?(String)
129
+ @late_bound_types << [owner, type]
130
+ else
131
+ @unvisited_types << type
132
+ end
133
+ end
134
+
135
+ def update_type_owner(owner, type)
136
+ case owner
137
+ when Module
138
+ if owner.kind.union?
139
+ owner.assign_type_membership_object_type(type)
140
+ elsif type.kind.interface?
141
+ new_interfaces = []
142
+ owner.interfaces.each do |int_t|
143
+ if int_t.is_a?(String) && int_t == type.graphql_name
144
+ new_interfaces << type
145
+ elsif int_t.is_a?(LateBoundType) && int_t.graphql_name == type.graphql_name
146
+ new_interfaces << type
147
+ else
148
+ # Don't re-add proper interface definitions,
149
+ # they were probably already added, maybe with options.
150
+ end
151
+ end
152
+ owner.implements(*new_interfaces)
153
+ new_interfaces.each do |int|
154
+ pt = @possible_types[int] ||= []
155
+ if !pt.include?(owner) && owner.is_a?(Class)
156
+ pt << owner
157
+ end
158
+ int.interfaces.each do |indirect_int|
159
+ if indirect_int.is_a?(LateBoundType) && (indirect_int_type = get_type(indirect_int.graphql_name)) # rubocop:disable Development/ContextIsPassedCop
160
+ update_type_owner(owner, indirect_int_type)
161
+ end
162
+ end
163
+ end
164
+ end
165
+ when GraphQL::Schema::Argument, GraphQL::Schema::Field
166
+ orig_type = owner.type
167
+ # Apply list/non-null wrapper as needed
168
+ if orig_type.respond_to?(:of_type)
169
+ transforms = []
170
+ while (orig_type.respond_to?(:of_type))
171
+ if orig_type.kind.non_null?
172
+ transforms << :to_non_null_type
173
+ elsif orig_type.kind.list?
174
+ transforms << :to_list_type
175
+ else
176
+ raise "Invariant: :of_type isn't non-null or list"
177
+ end
178
+ orig_type = orig_type.of_type
179
+ end
180
+ transforms.reverse_each { |t| type = type.public_send(t) }
181
+ end
182
+ owner.type = type
183
+ else
184
+ raise "Unexpected update: #{owner.inspect} #{type.inspect}"
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end