graphql 2.4.2 → 2.4.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbf9779d0fbc1da481a481d987489b980818c5ea6afb352f4e65f0654ea14163
4
- data.tar.gz: 91528aabb657b8602c7f3143b6a9dc3a770a336f6a31af0f89fce1c27e5e00a0
3
+ metadata.gz: caa82217695402eca9cd3782abf5deb4b27dd61d8aa3c16e7c4a555e1c09634a
4
+ data.tar.gz: a39b5821df1a8656616fd1831878c365c930289c0f94d733b788ab4298f24a96
5
5
  SHA512:
6
- metadata.gz: c73cedf9cd1fc6ab4d91f026dfe627cb1490d5888d1252f591b944359e1f537a479c5d7f7ceed691ddd79883ae1ba027dfb952e969d14617e6a19becee792aa6
7
- data.tar.gz: 4d429bb74350c9fbbaa0e33498ccb193acfa6691f62342b38923965e69bc23bb377e6ade8a56b8f5d5c785ea24a2f6e04a47a8384000a45140d320e7461c6832
6
+ metadata.gz: 1181acff2ccf066def91b1f39644b449fe715d3cfa18437c7edf74dfa5af01a45cbd1e00db2413810917fc290d1d3abc8bcc0620354458721d4df184973beb17
7
+ data.tar.gz: 61bcb56a34d0b0beb61672e3bae868dc5bd7a9caf351d85e0a78afd16c12347a076767e63ce55cc468c6edc5247caa10e2ccace32447b54c9dbc4f864846db5b
@@ -56,7 +56,14 @@ module GraphQL
56
56
  else
57
57
  @arguments = if @field
58
58
  @query.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args|
59
- args.is_a?(Execution::Interpreter::Arguments) ? args.keyword_arguments : args
59
+ case args
60
+ when Execution::Interpreter::Arguments
61
+ args.keyword_arguments
62
+ when GraphQL::ExecutionError
63
+ EmptyObjects::EMPTY_HASH
64
+ else
65
+ args
66
+ end
60
67
  end
61
68
  else
62
69
  nil
@@ -38,38 +38,17 @@ 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)
61
- end
62
- true
63
- else
64
- false
65
- end
46
+ h2[field] = visible_field_for(owner, field)
66
47
  end.compare_by_identity
67
48
  }.compare_by_identity
68
49
 
69
50
  @cached_visible_arguments = Hash.new do |h, arg|
70
51
  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
52
  true
74
53
  else
75
54
  false
@@ -88,39 +67,7 @@ module GraphQL
88
67
  end
89
68
  end.compare_by_identity
90
69
 
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
70
+ @cached_possible_types = Hash.new { |h, type| h[type] = possible_types_for(type) }.compare_by_identity
124
71
 
125
72
  @cached_enum_values = Hash.new do |h, enum_t|
126
73
  values = non_duplicate_items(enum_t.enum_values(@context), @cached_visible)
@@ -164,17 +111,12 @@ module GraphQL
164
111
  end
165
112
 
166
113
  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
114
+ t = @schema.visibility.get_type(type_name) # rubocop:disable ContextIsPassedCop
173
115
  if t
174
116
  if t.is_a?(Array)
175
117
  vis_t = nil
176
118
  t.each do |t_defn|
177
- if @cached_visible[t_defn]
119
+ if @cached_visible[t_defn] && referenced?(t_defn)
178
120
  if vis_t.nil?
179
121
  vis_t = t_defn
180
122
  else
@@ -184,7 +126,7 @@ module GraphQL
184
126
  end
185
127
  vis_t
186
128
  else
187
- if t && @cached_visible[t]
129
+ if t && @cached_visible[t] && referenced?(t)
188
130
  t
189
131
  else
190
132
  nil
@@ -267,15 +209,15 @@ module GraphQL
267
209
  end
268
210
 
269
211
  def query_root
270
- add_if_visible(@schema.query)
212
+ ((t = @schema.query) && @cached_visible[t]) ? t : nil
271
213
  end
272
214
 
273
215
  def mutation_root
274
- add_if_visible(@schema.mutation)
216
+ ((t = @schema.mutation) && @cached_visible[t]) ? t : nil
275
217
  end
276
218
 
277
219
  def subscription_root
278
- add_if_visible(@schema.subscription)
220
+ ((t = @schema.subscription) && @cached_visible[t]) ? t : nil
279
221
  end
280
222
 
281
223
  def all_types
@@ -293,28 +235,15 @@ module GraphQL
293
235
  end
294
236
 
295
237
  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
238
+ directives.any? { |d| d.graphql_name == dir_name }
302
239
  end
303
240
 
304
241
  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
242
+ @all_directives ||= @schema.visibility.all_directives.select { |dir| @cached_visible[dir] }
315
243
  end
316
244
 
317
245
  def loadable?(t, _ctx)
246
+ load_all_types
318
247
  !@all_types[t.graphql_name] && @cached_visible[t]
319
248
  end
320
249
 
@@ -322,9 +251,9 @@ module GraphQL
322
251
  @all_types.values
323
252
  end
324
253
 
325
- def reachable_type?(name)
254
+ def reachable_type?(type_name)
326
255
  load_all_types
327
- !!@all_types[name]
256
+ !!@all_types[type_name]
328
257
  end
329
258
 
330
259
  def visible_enum_value?(enum_value, _ctx = nil)
@@ -333,29 +262,6 @@ module GraphQL
333
262
 
334
263
  private
335
264
 
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
265
  def non_duplicate_items(definitions, visibility_cache)
360
266
  non_dups = []
361
267
  definitions.each do |defn|
@@ -373,153 +279,71 @@ module GraphQL
373
279
  raise DuplicateNamesError.new(duplicated_name: first_defn.path, duplicated_definition_1: first_defn.inspect, duplicated_definition_2: second_defn.inspect)
374
280
  end
375
281
 
376
- def referenced?(t)
377
- load_all_types
378
- @referenced_types[t].any? { |reference| (reference == true) || @cached_visible[reference] }
379
- end
380
-
381
282
  def load_all_types
382
283
  return if @all_types_loaded
383
284
  @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)
285
+ visit = Visibility::Visit.new(@schema) do |member|
286
+ if member.is_a?(Module) && member.respond_to?(:kind)
287
+ if @cached_visible[member]
288
+ type_name = member.graphql_name
289
+ if (prev_t = @all_types[type_name]) && !prev_t.equal?(member)
290
+ raise_duplicate_definition(member, prev_t)
425
291
  end
292
+ @all_types[type_name] = member
293
+ true
294
+ else
295
+ false
426
296
  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)
297
+ else
298
+ @cached_visible[member]
434
299
  end
435
300
  end
436
-
301
+ visit.visit_each
437
302
  @all_types.delete_if { |type_name, type_defn| !referenced?(type_defn) }
438
303
  nil
439
304
  end
440
305
 
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)}
455
- end
456
-
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)
306
+ def referenced?(type_defn)
307
+ @schema.visibility.all_references[type_defn].any? { |r| r == true || @cached_visible[r] }
308
+ end
468
309
 
469
- # recurse into visible arguments
470
- arguments(field).each do |argument|
471
- visit_directives(argument)
472
- add_type(argument.type.unwrap, argument)
473
- end
310
+ def possible_types_for(type)
311
+ case type.kind.name
312
+ when "INTERFACE"
313
+ pts = []
314
+ @schema.visibility.all_interface_type_memberships[type].each do |itm|
315
+ if @cached_visible[itm] && (ot = itm.object_type) && @cached_visible[ot] && referenced?(ot)
316
+ pts << ot
474
317
  end
475
318
  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
319
+ pts
482
320
  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
321
+ pts = []
322
+ type.type_memberships.each { |tm|
323
+ if @cached_visible[tm] &&
324
+ (ot = tm.object_type) &&
325
+ @cached_visible[ot] &&
326
+ referenced?(ot)
327
+ pts << ot
498
328
  end
329
+ }
330
+ pts
331
+ when "OBJECT"
332
+ if @cached_visible[type]
333
+ [type]
334
+ else
335
+ EmptyObjects::EMPTY_ARRAY
499
336
  end
500
- when "ENUM"
501
- enum_values(type).each do |val|
502
- visit_directives(val)
503
- end
504
- when "SCALAR"
505
- # pass
337
+ else
338
+ GraphQL::EmptyObjects::EMPTY_ARRAY
506
339
  end
507
340
  end
508
341
 
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
- }
342
+ def visible_field_for(owner, field)
343
+ @cached_visible[field] &&
344
+ (ret_type = field.type.unwrap) &&
345
+ @cached_visible[ret_type] &&
346
+ (owner == field.owner || (!owner.kind.object?) || field_on_visible_interface?(field, owner))
523
347
  end
524
348
  end
525
349
  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.any? || @late_bound_types.any?
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 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
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/schema/visibility/profile"
3
3
  require "graphql/schema/visibility/migration"
4
+ require "graphql/schema/visibility/visit"
4
5
 
5
6
  module GraphQL
6
7
  class Schema
@@ -13,40 +14,76 @@ module GraphQL
13
14
  # @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
14
15
  def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails) ? Rails.env.production? : nil), migration_errors: false)
15
16
  schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
17
+ if preload
18
+ schema.visibility.preload
19
+ end
16
20
  end
17
21
 
18
22
  def initialize(schema, dynamic:, preload:, profiles:, migration_errors:)
19
23
  @schema = schema
20
24
  schema.use_visibility_profile = true
21
- if migration_errors
22
- schema.visibility_profile_class = Migration
25
+ schema.visibility_profile_class = if migration_errors
26
+ Visibility::Migration
27
+ else
28
+ Visibility::Profile
23
29
  end
24
30
  @preload = preload
25
31
  @profiles = profiles
26
32
  @cached_profiles = {}
27
33
  @dynamic = dynamic
28
34
  @migration_errors = migration_errors
29
- if preload
30
- # Traverse the schema now (and in the *_configured hooks below)
31
- # To make sure things are loaded during boot
32
- @preloaded_types = Set.new
33
- types_to_visit = [
34
- @schema.query,
35
- @schema.mutation,
36
- @schema.subscription,
37
- *@schema.introspection_system.types.values,
38
- *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
39
- *@schema.orphan_types,
40
- ]
41
- # Root types may have been nil:
42
- types_to_visit.compact!
43
- ensure_all_loaded(types_to_visit)
44
-
45
- profiles.each do |profile_name, example_ctx|
46
- example_ctx[:visibility_profile] = profile_name
47
- prof = profile_for(example_ctx, profile_name)
48
- prof.all_types # force loading
49
- end
35
+ # Top-level type caches:
36
+ @visit = nil
37
+ @interface_type_memberships = nil
38
+ @directives = nil
39
+ @types = nil
40
+ @references = nil
41
+ @loaded_all = false
42
+ end
43
+
44
+ def all_directives
45
+ load_all
46
+ @directives
47
+ end
48
+
49
+ def all_interface_type_memberships
50
+ load_all
51
+ @interface_type_memberships
52
+ end
53
+
54
+ def all_references
55
+ load_all
56
+ @references
57
+ end
58
+
59
+ def get_type(type_name)
60
+ load_all
61
+ @types[type_name]
62
+ end
63
+
64
+ def preload?
65
+ @preload
66
+ end
67
+
68
+ def preload
69
+ # Traverse the schema now (and in the *_configured hooks below)
70
+ # To make sure things are loaded during boot
71
+ @preloaded_types = Set.new
72
+ types_to_visit = [
73
+ @schema.query,
74
+ @schema.mutation,
75
+ @schema.subscription,
76
+ *@schema.introspection_system.types.values,
77
+ *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
78
+ *@schema.orphan_types,
79
+ ]
80
+ # Root types may have been nil:
81
+ types_to_visit.compact!
82
+ ensure_all_loaded(types_to_visit)
83
+ @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
50
87
  end
51
88
  end
52
89
 
@@ -112,7 +149,11 @@ module GraphQL
112
149
  if @profiles.any?
113
150
  if visibility_profile.nil?
114
151
  if @dynamic
115
- @schema.visibility_profile_class.new(context: context, schema: @schema)
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
116
157
  elsif @profiles.any?
117
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."
118
159
  end
@@ -121,11 +162,25 @@ module GraphQL
121
162
  else
122
163
  @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: context, schema: @schema)
123
164
  end
165
+ elsif context.is_a?(Query::NullContext)
166
+ top_level_profile
124
167
  else
125
168
  @schema.visibility_profile_class.new(context: context, schema: @schema)
126
169
  end
127
170
  end
128
171
 
172
+ attr_reader :top_level
173
+
174
+ # @api private
175
+ attr_reader :unfiltered_interface_type_memberships
176
+
177
+ def top_level_profile(refresh: false)
178
+ if refresh
179
+ @top_level_profile = nil
180
+ end
181
+ @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema)
182
+ end
183
+
129
184
  private
130
185
 
131
186
  def ensure_all_loaded(types_to_visit)
@@ -137,6 +192,88 @@ module GraphQL
137
192
  end
138
193
  end
139
194
  end
195
+ top_level_profile(refresh: true)
196
+ nil
197
+ end
198
+
199
+ def load_all(types: nil)
200
+ if @visit.nil?
201
+ # Set up the visit system
202
+ @interface_type_memberships = Hash.new { |h, interface_type| h[interface_type] = [] }.compare_by_identity
203
+ @directives = []
204
+ @types = {} # String => Module
205
+ @references = Hash.new { |h, member| h[member] = [] }.compare_by_identity
206
+ @unions_for_references = Set.new
207
+ @visit = Visibility::Visit.new(@schema) do |member|
208
+ if member.is_a?(Module)
209
+ type_name = member.graphql_name
210
+ if (prev_t = @types[type_name])
211
+ if prev_t.is_a?(Array)
212
+ prev_t << member
213
+ else
214
+ @types[type_name] = [member, prev_t]
215
+ end
216
+ else
217
+ @types[member.graphql_name] = member
218
+ end
219
+ if member < GraphQL::Schema::Directive
220
+ @directives << member
221
+ elsif member.respond_to?(:interface_type_memberships)
222
+ member.interface_type_memberships.each do |itm|
223
+ @references[itm.abstract_type] << member
224
+ @interface_type_memberships[itm.abstract_type] << itm
225
+ end
226
+ elsif member < GraphQL::Schema::Union
227
+ @unions_for_references << member
228
+ end
229
+ elsif member.is_a?(GraphQL::Schema::Argument)
230
+ member.validate_default_value
231
+ @references[member.type.unwrap] << member
232
+ elsif member.is_a?(GraphQL::Schema::Field)
233
+ @references[member.type.unwrap] << member
234
+ end
235
+ true
236
+ end
237
+
238
+ @schema.root_types.each do |t|
239
+ @references[t] << true
240
+ end
241
+
242
+ @schema.introspection_system.types.each_value do |t|
243
+ @references[t] << true
244
+ end
245
+ @visit.visit_each(types: []) # visit default directives
246
+ end
247
+
248
+ if types
249
+ @visit.visit_each(types: types, directives: [])
250
+ elsif @loaded_all == false
251
+ @loaded_all = true
252
+ @visit.visit_each
253
+ else
254
+ # already loaded all
255
+ return
256
+ end
257
+
258
+ # TODO: somehow don't iterate over all these,
259
+ # only the ones that may have been modified
260
+ @interface_type_memberships.each do |int_type, type_memberships|
261
+ referers = @references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) }
262
+ if referers.any?
263
+ type_memberships.each do |type_membership|
264
+ implementor_type = type_membership.object_type
265
+ # Add new items only:
266
+ @references[implementor_type] |= referers
267
+ end
268
+ end
269
+ end
270
+
271
+ @unions_for_references.each do |union_type|
272
+ refs = @references[union_type]
273
+ union_type.all_possible_types.each do |object_type|
274
+ @references[object_type] |= refs # Add new items
275
+ end
276
+ end
140
277
  end
141
278
  end
142
279
  end
@@ -446,7 +446,12 @@ module GraphQL
446
446
  raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
447
447
  elsif use_visibility_profile?
448
448
  if block_given?
449
- @query_object = lazy_load_block
449
+ if visibility.preload?
450
+ @query_object = lazy_load_block.call
451
+ self.visibility.query_configured(@query_object)
452
+ else
453
+ @query_object = lazy_load_block
454
+ end
450
455
  else
451
456
  @query_object = new_query_object
452
457
  self.visibility.query_configured(@query_object)
@@ -480,7 +485,12 @@ module GraphQL
480
485
  raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
481
486
  elsif use_visibility_profile?
482
487
  if block_given?
483
- @mutation_object = lazy_load_block
488
+ if visibility.preload?
489
+ @mutation_object = lazy_load_block.call
490
+ self.visibility.mutation_configured(@mutation_object)
491
+ else
492
+ @mutation_object = lazy_load_block
493
+ end
484
494
  else
485
495
  @mutation_object = new_mutation_object
486
496
  self.visibility.mutation_configured(@mutation_object)
@@ -492,7 +502,7 @@ module GraphQL
492
502
  nil
493
503
  elsif @mutation_object.is_a?(Proc)
494
504
  @mutation_object = @mutation_object.call
495
- self.visibility&.mutation_configured(@query_object)
505
+ self.visibility&.mutation_configured(@mutation_object)
496
506
  @mutation_object
497
507
  else
498
508
  @mutation_object || find_inherited_value(:mutation)
@@ -514,7 +524,12 @@ module GraphQL
514
524
  raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
515
525
  elsif use_visibility_profile?
516
526
  if block_given?
517
- @subscription_object = lazy_load_block
527
+ if visibility.preload?
528
+ @subscription_object = lazy_load_block.call
529
+ visibility.subscription_configured(@subscription_object)
530
+ else
531
+ @subscription_object = lazy_load_block
532
+ end
518
533
  else
519
534
  @subscription_object = new_subscription_object
520
535
  self.visibility.subscription_configured(@subscription_object)
@@ -29,6 +29,7 @@ module GraphQL
29
29
  @visitor = visitor_class.new(document, self)
30
30
  end
31
31
 
32
+ # TODO stop using def_delegators because of Array allocations
32
33
  def_delegators :@visitor,
33
34
  :path, :type_definition, :field_definition, :argument_definition,
34
35
  :parent_type_definition, :directive_definition, :object_types, :dependencies
@@ -92,7 +92,7 @@ module GraphQL
92
92
  end
93
93
  graphql_result
94
94
  else
95
- unfiltered_type = Schema::Visibility::Profile.null_profile(schema: schema, context: context).type(type_name)
95
+ unfiltered_type = schema.use_visibility_profile? ? schema.visibility.get_type(type_name) : schema.get_type(type_name) # rubocop:disable ContextIsPassedCop
96
96
  if unfiltered_type
97
97
  raise TypeNotVisibleError.new(type_name: type_name)
98
98
  else
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.4.2"
3
+ VERSION = "2.4.4"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.2
4
+ version: 2.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-07 00:00:00.000000000 Z
11
+ date: 2024-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -504,6 +504,7 @@ files:
504
504
  - lib/graphql/schema/visibility.rb
505
505
  - lib/graphql/schema/visibility/migration.rb
506
506
  - lib/graphql/schema/visibility/profile.rb
507
+ - lib/graphql/schema/visibility/visit.rb
507
508
  - lib/graphql/schema/warden.rb
508
509
  - lib/graphql/schema/wrapper.rb
509
510
  - lib/graphql/static_validation.rb