graphql 2.3.14 → 2.4.0
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.
- checksums.yaml +4 -4
- data/lib/generators/graphql/orm_mutations_base.rb +1 -1
- data/lib/generators/graphql/templates/base_resolver.erb +2 -0
- data/lib/generators/graphql/type_generator.rb +1 -1
- data/lib/graphql/analysis.rb +1 -1
- data/lib/graphql/dataloader/async_dataloader.rb +3 -2
- data/lib/graphql/dataloader/source.rb +1 -1
- data/lib/graphql/dataloader.rb +31 -10
- data/lib/graphql/execution/interpreter/resolve.rb +10 -6
- data/lib/graphql/invalid_null_error.rb +1 -1
- data/lib/graphql/language/comment.rb +18 -0
- data/lib/graphql/language/document_from_schema_definition.rb +38 -4
- data/lib/graphql/language/lexer.rb +15 -12
- data/lib/graphql/language/nodes.rb +22 -14
- data/lib/graphql/language/parser.rb +5 -0
- data/lib/graphql/language/printer.rb +23 -7
- data/lib/graphql/language.rb +6 -5
- data/lib/graphql/query/null_context.rb +1 -1
- data/lib/graphql/query.rb +49 -16
- data/lib/graphql/rubocop/graphql/field_type_in_block.rb +23 -8
- data/lib/graphql/schema/always_visible.rb +6 -3
- data/lib/graphql/schema/argument.rb +14 -1
- data/lib/graphql/schema/build_from_definition.rb +1 -0
- data/lib/graphql/schema/enum.rb +3 -0
- data/lib/graphql/schema/enum_value.rb +9 -1
- data/lib/graphql/schema/field.rb +35 -14
- data/lib/graphql/schema/input_object.rb +20 -7
- data/lib/graphql/schema/interface.rb +1 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
- data/lib/graphql/schema/member/has_arguments.rb +2 -2
- data/lib/graphql/schema/member/has_fields.rb +2 -2
- data/lib/graphql/schema/printer.rb +1 -0
- data/lib/graphql/schema/resolver.rb +3 -4
- data/lib/graphql/schema/validator/required_validator.rb +28 -4
- data/lib/graphql/schema/visibility/migration.rb +186 -0
- data/lib/graphql/schema/visibility/profile.rb +523 -0
- data/lib/graphql/schema/visibility.rb +75 -0
- data/lib/graphql/schema/warden.rb +77 -15
- data/lib/graphql/schema.rb +203 -61
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +2 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
- data/lib/graphql/static_validation/validation_context.rb +15 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
- data/lib/graphql/subscriptions.rb +3 -1
- data/lib/graphql/testing/helpers.rb +2 -1
- data/lib/graphql/tracing/notifications_trace.rb +2 -2
- data/lib/graphql/version.rb +1 -1
- metadata +11 -9
- data/lib/graphql/schema/subset.rb +0 -509
- data/lib/graphql/schema/types_migration.rb +0 -187
@@ -0,0 +1,523 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Visibility
|
6
|
+
# This class filters the types, fields, arguments, enum values, and directives in a schema
|
7
|
+
# based on the given `context`.
|
8
|
+
#
|
9
|
+
# It's like {Warden}, but has some differences:
|
10
|
+
#
|
11
|
+
# - It doesn't use {Schema}'s top-level caches (eg {Schema.references_to}, {Schema.possible_types}, {Schema.types})
|
12
|
+
# - It doesn't hide Interface or Union types when all their possible types are hidden. (Instead, those types should implement `.visible?` to hide in that case.)
|
13
|
+
# - It checks `.visible?` on root introspection types
|
14
|
+
# - It can be used to cache profiles by name for re-use across queries
|
15
|
+
class Profile
|
16
|
+
# @return [Schema::Visibility::Profile]
|
17
|
+
def self.from_context(ctx, schema)
|
18
|
+
if ctx.respond_to?(:types) && (types = ctx.types).is_a?(self)
|
19
|
+
types
|
20
|
+
else
|
21
|
+
schema.visibility.profile_for(ctx, nil)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.pass_thru(context:, schema:)
|
26
|
+
profile = self.new(context: context, schema: schema)
|
27
|
+
profile.instance_variable_set(:@cached_visible, Hash.new { |h,k| h[k] = true })
|
28
|
+
profile
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Symbol, nil]
|
32
|
+
attr_reader :name
|
33
|
+
|
34
|
+
def initialize(name: nil, context:, schema:)
|
35
|
+
@name = name
|
36
|
+
@context = context
|
37
|
+
@schema = schema
|
38
|
+
@all_types = {}
|
39
|
+
@all_types_loaded = false
|
40
|
+
@unvisited_types = []
|
41
|
+
@referenced_types = Hash.new { |h, type_defn| h[type_defn] = [] }.compare_by_identity
|
42
|
+
@cached_directives = {}
|
43
|
+
@all_directives = nil
|
44
|
+
@cached_visible = Hash.new { |h, member|
|
45
|
+
h[member] = @schema.visible?(member, @context)
|
46
|
+
}.compare_by_identity
|
47
|
+
|
48
|
+
@cached_visible_fields = Hash.new { |h, owner|
|
49
|
+
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
|
66
|
+
end.compare_by_identity
|
67
|
+
}.compare_by_identity
|
68
|
+
|
69
|
+
@cached_visible_arguments = Hash.new do |h, arg|
|
70
|
+
h[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type]
|
71
|
+
add_type(arg_type, arg)
|
72
|
+
arg.validate_default_value
|
73
|
+
true
|
74
|
+
else
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end.compare_by_identity
|
78
|
+
|
79
|
+
@cached_parent_fields = Hash.new do |h, type|
|
80
|
+
h[type] = Hash.new do |h2, field_name|
|
81
|
+
h2[field_name] = type.get_field(field_name, @context)
|
82
|
+
end
|
83
|
+
end.compare_by_identity
|
84
|
+
|
85
|
+
@cached_parent_arguments = Hash.new do |h, arg_owner|
|
86
|
+
h[arg_owner] = Hash.new do |h2, arg_name|
|
87
|
+
h2[arg_name] = arg_owner.get_argument(arg_name, @context)
|
88
|
+
end
|
89
|
+
end.compare_by_identity
|
90
|
+
|
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
|
124
|
+
|
125
|
+
@cached_enum_values = Hash.new do |h, enum_t|
|
126
|
+
values = non_duplicate_items(enum_t.all_enum_value_definitions, @cached_visible)
|
127
|
+
if values.size == 0
|
128
|
+
raise GraphQL::Schema::Enum::MissingValuesError.new(enum_t)
|
129
|
+
end
|
130
|
+
h[enum_t] = values
|
131
|
+
end.compare_by_identity
|
132
|
+
|
133
|
+
@cached_fields = Hash.new do |h, owner|
|
134
|
+
h[owner] = non_duplicate_items(owner.all_field_definitions.each(&:ensure_loaded), @cached_visible_fields[owner])
|
135
|
+
end.compare_by_identity
|
136
|
+
|
137
|
+
@cached_arguments = Hash.new do |h, owner|
|
138
|
+
h[owner] = non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments)
|
139
|
+
end.compare_by_identity
|
140
|
+
end
|
141
|
+
|
142
|
+
def field_on_visible_interface?(field, owner)
|
143
|
+
ints = owner.interface_type_memberships.map(&:abstract_type)
|
144
|
+
field_name = field.graphql_name
|
145
|
+
filtered_ints = interfaces(owner)
|
146
|
+
any_interface_has_field = false
|
147
|
+
any_interface_has_visible_field = false
|
148
|
+
ints.each do |int_t|
|
149
|
+
if (_int_f_defn = @cached_parent_fields[int_t][field_name])
|
150
|
+
any_interface_has_field = true
|
151
|
+
|
152
|
+
if filtered_ints.include?(int_t) # TODO cycles, or maybe not necessary since previously checked? && @cached_visible_fields[owner][field]
|
153
|
+
any_interface_has_visible_field = true
|
154
|
+
break
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
if any_interface_has_field
|
160
|
+
any_interface_has_visible_field
|
161
|
+
else
|
162
|
+
true
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
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
|
173
|
+
if t
|
174
|
+
if t.is_a?(Array)
|
175
|
+
vis_t = nil
|
176
|
+
t.each do |t_defn|
|
177
|
+
if @cached_visible[t_defn]
|
178
|
+
if vis_t.nil?
|
179
|
+
vis_t = t_defn
|
180
|
+
else
|
181
|
+
raise_duplicate_definition(vis_t, t_defn)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
vis_t
|
186
|
+
else
|
187
|
+
if t && @cached_visible[t]
|
188
|
+
t
|
189
|
+
else
|
190
|
+
nil
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def field(owner, field_name)
|
197
|
+
f = if owner.kind.fields? && (field = @cached_parent_fields[owner][field_name])
|
198
|
+
field
|
199
|
+
elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
|
200
|
+
entry_point_field
|
201
|
+
elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
|
202
|
+
dynamic_field
|
203
|
+
else
|
204
|
+
nil
|
205
|
+
end
|
206
|
+
if f.is_a?(Array)
|
207
|
+
visible_f = nil
|
208
|
+
f.each do |f_defn|
|
209
|
+
if @cached_visible_fields[owner][f_defn]
|
210
|
+
|
211
|
+
if visible_f.nil?
|
212
|
+
visible_f = f_defn
|
213
|
+
else
|
214
|
+
raise_duplicate_definition(visible_f, f_defn)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
visible_f.ensure_loaded
|
219
|
+
elsif f && @cached_visible_fields[owner][f.ensure_loaded]
|
220
|
+
f
|
221
|
+
else
|
222
|
+
nil
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def fields(owner)
|
227
|
+
@cached_fields[owner]
|
228
|
+
end
|
229
|
+
|
230
|
+
def arguments(owner)
|
231
|
+
@cached_arguments[owner]
|
232
|
+
end
|
233
|
+
|
234
|
+
def argument(owner, arg_name)
|
235
|
+
arg = @cached_parent_arguments[owner][arg_name]
|
236
|
+
if arg.is_a?(Array)
|
237
|
+
visible_arg = nil
|
238
|
+
arg.each do |arg_defn|
|
239
|
+
if @cached_visible_arguments[arg_defn]
|
240
|
+
if visible_arg.nil?
|
241
|
+
visible_arg = arg_defn
|
242
|
+
else
|
243
|
+
raise_duplicate_definition(visible_arg, arg_defn)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
visible_arg
|
248
|
+
else
|
249
|
+
if arg && @cached_visible_arguments[arg]
|
250
|
+
arg
|
251
|
+
else
|
252
|
+
nil
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def possible_types(type)
|
258
|
+
@cached_possible_types[type]
|
259
|
+
end
|
260
|
+
|
261
|
+
def interfaces(obj_or_int_type)
|
262
|
+
ints = obj_or_int_type.interface_type_memberships
|
263
|
+
.select { |itm| @cached_visible[itm] && @cached_visible[itm.abstract_type] }
|
264
|
+
.map!(&:abstract_type)
|
265
|
+
ints.uniq! # Remove any duplicate interfaces implemented via other interfaces
|
266
|
+
ints
|
267
|
+
end
|
268
|
+
|
269
|
+
def query_root
|
270
|
+
add_if_visible(@schema.query)
|
271
|
+
end
|
272
|
+
|
273
|
+
def mutation_root
|
274
|
+
add_if_visible(@schema.mutation)
|
275
|
+
end
|
276
|
+
|
277
|
+
def subscription_root
|
278
|
+
add_if_visible(@schema.subscription)
|
279
|
+
end
|
280
|
+
|
281
|
+
def all_types
|
282
|
+
load_all_types
|
283
|
+
@all_types.values
|
284
|
+
end
|
285
|
+
|
286
|
+
def all_types_h
|
287
|
+
load_all_types
|
288
|
+
@all_types
|
289
|
+
end
|
290
|
+
|
291
|
+
def enum_values(owner)
|
292
|
+
@cached_enum_values[owner]
|
293
|
+
end
|
294
|
+
|
295
|
+
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
|
302
|
+
end
|
303
|
+
|
304
|
+
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
|
315
|
+
end
|
316
|
+
|
317
|
+
def loadable?(t, _ctx)
|
318
|
+
!@all_types[t.graphql_name] && @cached_visible[t]
|
319
|
+
end
|
320
|
+
|
321
|
+
def loaded_types
|
322
|
+
@all_types.values
|
323
|
+
end
|
324
|
+
|
325
|
+
def reachable_type?(name)
|
326
|
+
load_all_types
|
327
|
+
!!@all_types[name]
|
328
|
+
end
|
329
|
+
|
330
|
+
private
|
331
|
+
|
332
|
+
def add_if_visible(t)
|
333
|
+
(t && @cached_visible[t]) ? (add_type(t, true); t) : nil
|
334
|
+
end
|
335
|
+
|
336
|
+
def add_type(t, by_member)
|
337
|
+
if t && @cached_visible[t]
|
338
|
+
n = t.graphql_name
|
339
|
+
if (prev_t = @all_types[n])
|
340
|
+
if !prev_t.equal?(t)
|
341
|
+
raise_duplicate_definition(prev_t, t)
|
342
|
+
end
|
343
|
+
false
|
344
|
+
else
|
345
|
+
@referenced_types[t] << by_member
|
346
|
+
@all_types[n] = t
|
347
|
+
@unvisited_types << t
|
348
|
+
true
|
349
|
+
end
|
350
|
+
else
|
351
|
+
false
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def non_duplicate_items(definitions, visibility_cache)
|
356
|
+
non_dups = []
|
357
|
+
definitions.each do |defn|
|
358
|
+
if visibility_cache[defn]
|
359
|
+
if (dup_defn = non_dups.find { |d| d.graphql_name == defn.graphql_name })
|
360
|
+
raise_duplicate_definition(dup_defn, defn)
|
361
|
+
end
|
362
|
+
non_dups << defn
|
363
|
+
end
|
364
|
+
end
|
365
|
+
non_dups
|
366
|
+
end
|
367
|
+
|
368
|
+
def raise_duplicate_definition(first_defn, second_defn)
|
369
|
+
raise DuplicateNamesError.new(duplicated_name: first_defn.path, duplicated_definition_1: first_defn.inspect, duplicated_definition_2: second_defn.inspect)
|
370
|
+
end
|
371
|
+
|
372
|
+
def referenced?(t)
|
373
|
+
load_all_types
|
374
|
+
@referenced_types[t].any? { |reference| (reference == true) || @cached_visible[reference] }
|
375
|
+
end
|
376
|
+
|
377
|
+
def load_all_types
|
378
|
+
return if @all_types_loaded
|
379
|
+
@all_types_loaded = true
|
380
|
+
entry_point_types = [
|
381
|
+
query_root,
|
382
|
+
mutation_root,
|
383
|
+
subscription_root,
|
384
|
+
*@schema.introspection_system.types.values,
|
385
|
+
]
|
386
|
+
|
387
|
+
# Don't include any orphan_types whose interfaces aren't visible.
|
388
|
+
@schema.orphan_types.each do |orphan_type|
|
389
|
+
if @cached_visible[orphan_type] &&
|
390
|
+
orphan_type.interface_type_memberships.any? { |tm| @cached_visible[tm] && @cached_visible[tm.abstract_type] }
|
391
|
+
entry_point_types << orphan_type
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
@schema.directives.each do |_dir_name, dir_class|
|
396
|
+
if @cached_visible[dir_class]
|
397
|
+
arguments(dir_class).each do |arg|
|
398
|
+
entry_point_types << arg.type.unwrap
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
entry_point_types.compact! # TODO why is this necessary?!
|
404
|
+
entry_point_types.flatten! # handle multiple defns
|
405
|
+
entry_point_types.each { |t| add_type(t, true) }
|
406
|
+
|
407
|
+
@unfiltered_interface_type_memberships = Hash.new { |h, k| h[k] = [] }.compare_by_identity
|
408
|
+
@add_possible_types = Set.new
|
409
|
+
@late_types = []
|
410
|
+
|
411
|
+
while @unvisited_types.any? || @late_types.any?
|
412
|
+
while t = @unvisited_types.pop
|
413
|
+
# These have already been checked for `.visible?`
|
414
|
+
visit_type(t)
|
415
|
+
end
|
416
|
+
@add_possible_types.each do |int_t|
|
417
|
+
itms = @unfiltered_interface_type_memberships[int_t]
|
418
|
+
itms.each do |itm|
|
419
|
+
if @cached_visible[itm] && (obj_type = itm.object_type) && @cached_visible[obj_type]
|
420
|
+
add_type(obj_type, itm)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
@add_possible_types.clear
|
425
|
+
|
426
|
+
while (union_tm = @late_types.shift)
|
427
|
+
late_obj_t = union_tm.object_type
|
428
|
+
obj_t = @all_types[late_obj_t.graphql_name] || raise("Failed to resolve #{late_obj_t.graphql_name.inspect} from #{union_tm.inspect}")
|
429
|
+
union_tm.abstract_type.assign_type_membership_object_type(obj_t)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
@all_types.delete_if { |type_name, type_defn| !referenced?(type_defn) }
|
434
|
+
nil
|
435
|
+
end
|
436
|
+
|
437
|
+
def visit_type(type)
|
438
|
+
visit_directives(type)
|
439
|
+
case type.kind.name
|
440
|
+
when "OBJECT", "INTERFACE"
|
441
|
+
if type.kind.object?
|
442
|
+
type.interface_type_memberships.each do |itm|
|
443
|
+
@unfiltered_interface_type_memberships[itm.abstract_type] << itm
|
444
|
+
end
|
445
|
+
# recurse into visible implemented interfaces
|
446
|
+
interfaces(type).each do |interface|
|
447
|
+
add_type(interface, type)
|
448
|
+
end
|
449
|
+
else
|
450
|
+
type.orphan_types.each { |t| add_type(t, type)}
|
451
|
+
end
|
452
|
+
|
453
|
+
# recurse into visible fields
|
454
|
+
t_f = type.all_field_definitions
|
455
|
+
t_f.each do |field|
|
456
|
+
field.ensure_loaded
|
457
|
+
if @cached_visible[field]
|
458
|
+
visit_directives(field)
|
459
|
+
field_type = field.type.unwrap
|
460
|
+
if field_type.kind.interface?
|
461
|
+
@add_possible_types.add(field_type)
|
462
|
+
end
|
463
|
+
add_type(field_type, field)
|
464
|
+
|
465
|
+
# recurse into visible arguments
|
466
|
+
arguments(field).each do |argument|
|
467
|
+
visit_directives(argument)
|
468
|
+
add_type(argument.type.unwrap, argument)
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
when "INPUT_OBJECT"
|
473
|
+
# recurse into visible arguments
|
474
|
+
arguments(type).each do |argument|
|
475
|
+
visit_directives(argument)
|
476
|
+
add_type(argument.type.unwrap, argument)
|
477
|
+
end
|
478
|
+
when "UNION"
|
479
|
+
# recurse into visible possible types
|
480
|
+
type.type_memberships.each do |tm|
|
481
|
+
if @cached_visible[tm]
|
482
|
+
obj_t = tm.object_type
|
483
|
+
if obj_t.is_a?(GraphQL::Schema::LateBoundType)
|
484
|
+
@late_types << tm
|
485
|
+
else
|
486
|
+
if obj_t.is_a?(String)
|
487
|
+
obj_t = Member::BuildType.constantize(obj_t)
|
488
|
+
tm.object_type = obj_t
|
489
|
+
end
|
490
|
+
if @cached_visible[obj_t]
|
491
|
+
add_type(obj_t, tm)
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
when "ENUM"
|
497
|
+
enum_values(type).each do |val|
|
498
|
+
visit_directives(val)
|
499
|
+
end
|
500
|
+
when "SCALAR"
|
501
|
+
# pass
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
def visit_directives(member)
|
506
|
+
member.directives.each { |dir|
|
507
|
+
dir_class = dir.class
|
508
|
+
if @cached_visible[dir_class]
|
509
|
+
dir_name = dir_class.graphql_name
|
510
|
+
if (existing_dir = @cached_directives[dir_name])
|
511
|
+
if existing_dir != dir_class
|
512
|
+
raise ArgumentError, "Two directives for `@#{dir_name}`: #{existing_dir}, #{dir.class}"
|
513
|
+
end
|
514
|
+
else
|
515
|
+
@cached_directives[dir.graphql_name] = dir_class
|
516
|
+
end
|
517
|
+
end
|
518
|
+
}
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "graphql/schema/visibility/profile"
|
3
|
+
require "graphql/schema/visibility/migration"
|
4
|
+
|
5
|
+
module GraphQL
|
6
|
+
class Schema
|
7
|
+
# Use this plugin to make some parts of your schema hidden from some viewers.
|
8
|
+
#
|
9
|
+
class Visibility
|
10
|
+
# @param schema [Class<GraphQL::Schema>]
|
11
|
+
# @param profiles [Hash<Symbol => Hash>] A hash of `name => context` pairs for preloading visibility profiles
|
12
|
+
# @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?`)
|
13
|
+
# @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
|
14
|
+
def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails) ? Rails.env.production? : nil), migration_errors: false)
|
15
|
+
schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(schema, dynamic:, preload:, profiles:, migration_errors:)
|
19
|
+
@schema = schema
|
20
|
+
schema.use_visibility_profile = true
|
21
|
+
if migration_errors
|
22
|
+
schema.visibility_profile_class = Migration
|
23
|
+
end
|
24
|
+
@profiles = profiles
|
25
|
+
@cached_profiles = {}
|
26
|
+
@dynamic = dynamic
|
27
|
+
@migration_errors = migration_errors
|
28
|
+
if preload
|
29
|
+
profiles.each do |profile_name, example_ctx|
|
30
|
+
example_ctx[:visibility_profile] = profile_name
|
31
|
+
prof = profile_for(example_ctx, profile_name)
|
32
|
+
prof.all_types # force loading
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Make another Visibility for `schema` based on this one
|
38
|
+
# @return [Visibility]
|
39
|
+
# @api private
|
40
|
+
def dup_for(other_schema)
|
41
|
+
self.class.new(
|
42
|
+
other_schema,
|
43
|
+
dynamic: @dynamic,
|
44
|
+
preload: @preload,
|
45
|
+
profiles: @profiles,
|
46
|
+
migration_errors: @migration_errors
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def migration_errors?
|
51
|
+
@migration_errors
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :cached_profiles
|
55
|
+
|
56
|
+
def profile_for(context, visibility_profile)
|
57
|
+
if @profiles.any?
|
58
|
+
if visibility_profile.nil?
|
59
|
+
if @dynamic
|
60
|
+
@schema.visibility_profile_class.new(context: context, schema: @schema)
|
61
|
+
elsif @profiles.any?
|
62
|
+
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."
|
63
|
+
end
|
64
|
+
elsif !@profiles.include?(visibility_profile)
|
65
|
+
raise ArgumentError, "`#{visibility_profile.inspect}` isn't allowed for `visibility_profile:` (must be one of #{@profiles.keys.map(&:inspect).join(", ")}). Or, add `#{visibility_profile.inspect}` to the list of profiles in the schema definition."
|
66
|
+
else
|
67
|
+
@cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: context, schema: @schema)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
@schema.visibility_profile_class.new(context: context, schema: @schema)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|