graphql 2.4.2 → 2.4.4
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.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/graphql/execution/lookahead.rb +8 -1
- data/lib/graphql/schema/visibility/profile.rb +59 -235
- data/lib/graphql/schema/visibility/visit.rb +190 -0
- data/lib/graphql/schema/visibility.rb +161 -24
- data/lib/graphql/schema.rb +19 -4
- 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
|
@@ -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
|
-
|
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] =
|
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
|
|
@@ -112,7 +149,11 @@ module GraphQL
|
|
112
149
|
if @profiles.any?
|
113
150
|
if visibility_profile.nil?
|
114
151
|
if @dynamic
|
115
|
-
|
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
|
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)
|
@@ -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(@
|
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
|
-
|
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
|