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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  3. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  4. data/lib/generators/graphql/type_generator.rb +1 -1
  5. data/lib/graphql/analysis.rb +1 -1
  6. data/lib/graphql/dataloader/async_dataloader.rb +3 -2
  7. data/lib/graphql/dataloader/source.rb +1 -1
  8. data/lib/graphql/dataloader.rb +31 -10
  9. data/lib/graphql/execution/interpreter/resolve.rb +10 -6
  10. data/lib/graphql/invalid_null_error.rb +1 -1
  11. data/lib/graphql/language/comment.rb +18 -0
  12. data/lib/graphql/language/document_from_schema_definition.rb +38 -4
  13. data/lib/graphql/language/lexer.rb +15 -12
  14. data/lib/graphql/language/nodes.rb +22 -14
  15. data/lib/graphql/language/parser.rb +5 -0
  16. data/lib/graphql/language/printer.rb +23 -7
  17. data/lib/graphql/language.rb +6 -5
  18. data/lib/graphql/query/null_context.rb +1 -1
  19. data/lib/graphql/query.rb +49 -16
  20. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +23 -8
  21. data/lib/graphql/schema/always_visible.rb +6 -3
  22. data/lib/graphql/schema/argument.rb +14 -1
  23. data/lib/graphql/schema/build_from_definition.rb +1 -0
  24. data/lib/graphql/schema/enum.rb +3 -0
  25. data/lib/graphql/schema/enum_value.rb +9 -1
  26. data/lib/graphql/schema/field.rb +35 -14
  27. data/lib/graphql/schema/input_object.rb +20 -7
  28. data/lib/graphql/schema/interface.rb +1 -0
  29. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  30. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  31. data/lib/graphql/schema/member/has_fields.rb +2 -2
  32. data/lib/graphql/schema/printer.rb +1 -0
  33. data/lib/graphql/schema/resolver.rb +3 -4
  34. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  35. data/lib/graphql/schema/visibility/migration.rb +186 -0
  36. data/lib/graphql/schema/visibility/profile.rb +523 -0
  37. data/lib/graphql/schema/visibility.rb +75 -0
  38. data/lib/graphql/schema/warden.rb +77 -15
  39. data/lib/graphql/schema.rb +203 -61
  40. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  41. data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
  42. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  43. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +2 -1
  44. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
  45. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
  46. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
  47. data/lib/graphql/static_validation/validation_context.rb +15 -0
  48. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
  49. data/lib/graphql/subscriptions.rb +3 -1
  50. data/lib/graphql/testing/helpers.rb +2 -1
  51. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  52. data/lib/graphql/version.rb +1 -1
  53. metadata +11 -9
  54. data/lib/graphql/schema/subset.rb +0 -509
  55. 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