graphql 2.4.3 → 2.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql/schema/visibility/profile.rb +59 -235
- data/lib/graphql/schema/visibility/visit.rb +190 -0
- data/lib/graphql/schema/visibility.rb +146 -24
- data/lib/graphql/schema.rb +18 -3
- data/lib/graphql/static_validation/validation_context.rb +1 -0
- data/lib/graphql/testing/helpers.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: caa82217695402eca9cd3782abf5deb4b27dd61d8aa3c16e7c4a555e1c09634a
|
4
|
+
data.tar.gz: a39b5821df1a8656616fd1831878c365c930289c0f94d733b788ab4298f24a96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1181acff2ccf066def91b1f39644b449fe715d3cfa18437c7edf74dfa5af01a45cbd1e00db2413810917fc290d1d3abc8bcc0620354458721d4df184973beb17
|
7
|
+
data.tar.gz: 61bcb56a34d0b0beb61672e3bae868dc5bd7a9caf351d85e0a78afd16c12347a076767e63ce55cc468c6edc5247caa10e2ccace32447b54c9dbc4f864846db5b
|
@@ -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] =
|
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
|
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 =
|
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
|
-
|
212
|
+
((t = @schema.query) && @cached_visible[t]) ? t : nil
|
271
213
|
end
|
272
214
|
|
273
215
|
def mutation_root
|
274
|
-
|
216
|
+
((t = @schema.mutation) && @cached_visible[t]) ? t : nil
|
275
217
|
end
|
276
218
|
|
277
219
|
def subscription_root
|
278
|
-
|
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
|
-
|
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 ||=
|
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?(
|
254
|
+
def reachable_type?(type_name)
|
326
255
|
load_all_types
|
327
|
-
!!@all_types[
|
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
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
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
|
-
|
428
|
-
|
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
|
442
|
-
|
443
|
-
|
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
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
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
|
-
|
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
|
-
|
484
|
-
type.type_memberships.each
|
485
|
-
if @cached_visible[tm]
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
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
|
-
|
501
|
-
|
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
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
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
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
|
@@ -132,7 +169,10 @@ module GraphQL
|
|
132
169
|
end
|
133
170
|
end
|
134
171
|
|
135
|
-
|
172
|
+
attr_reader :top_level
|
173
|
+
|
174
|
+
# @api private
|
175
|
+
attr_reader :unfiltered_interface_type_memberships
|
136
176
|
|
137
177
|
def top_level_profile(refresh: false)
|
138
178
|
if refresh
|
@@ -141,6 +181,8 @@ module GraphQL
|
|
141
181
|
@top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema)
|
142
182
|
end
|
143
183
|
|
184
|
+
private
|
185
|
+
|
144
186
|
def ensure_all_loaded(types_to_visit)
|
145
187
|
while (type = types_to_visit.shift)
|
146
188
|
if type.kind.fields? && @preloaded_types.add?(type)
|
@@ -153,6 +195,86 @@ module GraphQL
|
|
153
195
|
top_level_profile(refresh: true)
|
154
196
|
nil
|
155
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
|
277
|
+
end
|
156
278
|
end
|
157
279
|
end
|
158
280
|
end
|
data/lib/graphql/schema.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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)
|
@@ -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
|
-
|
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 =
|
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
|
data/lib/graphql/version.rb
CHANGED
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.
|
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-
|
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
|