graphql 2.3.18 → 2.3.20

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,26 +11,28 @@ module GraphQL
11
11
  # - It doesn't use {Schema}'s top-level caches (eg {Schema.references_to}, {Schema.possible_types}, {Schema.types})
12
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
13
  # - It checks `.visible?` on root introspection types
14
- #
15
- # In the future, {Subset} will support lazy-loading types as needed during execution and multi-request caching of subsets.
16
- class Subset
17
- # @return [Schema::Visibility::Subset]
14
+ # - It can be used to cache profiles by name for re-use across queries
15
+ class Profile
16
+ # @return [Schema::Visibility::Profile]
18
17
  def self.from_context(ctx, schema)
19
18
  if ctx.respond_to?(:types) && (types = ctx.types).is_a?(self)
20
19
  types
21
20
  else
22
- # TODO use a cached instance from the schema
23
- self.new(context: ctx, schema: schema)
21
+ schema.visibility.profile_for(ctx, nil)
24
22
  end
25
23
  end
26
24
 
27
25
  def self.pass_thru(context:, schema:)
28
- subset = self.new(context: context, schema: schema)
29
- subset.instance_variable_set(:@cached_visible, Hash.new { |h,k| h[k] = true })
30
- subset
26
+ profile = self.new(context: context, schema: schema)
27
+ profile.instance_variable_set(:@cached_visible, Hash.new { |h,k| h[k] = true })
28
+ profile
31
29
  end
32
30
 
33
- def initialize(context:, schema:)
31
+ # @return [Symbol, nil]
32
+ attr_reader :name
33
+
34
+ def initialize(name: nil, context:, schema:)
35
+ @name = name
34
36
  @context = context
35
37
  @schema = schema
36
38
  @all_types = {}
@@ -67,6 +69,7 @@ module GraphQL
67
69
  @cached_visible_arguments = Hash.new do |h, arg|
68
70
  h[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type]
69
71
  add_type(arg_type, arg)
72
+ arg.validate_default_value
70
73
  true
71
74
  else
72
75
  false
@@ -403,8 +406,9 @@ module GraphQL
403
406
 
404
407
  @unfiltered_interface_type_memberships = Hash.new { |h, k| h[k] = [] }.compare_by_identity
405
408
  @add_possible_types = Set.new
409
+ @late_types = []
406
410
 
407
- while @unvisited_types.any?
411
+ while @unvisited_types.any? || @late_types.any?
408
412
  while t = @unvisited_types.pop
409
413
  # These have already been checked for `.visible?`
410
414
  visit_type(t)
@@ -418,6 +422,12 @@ module GraphQL
418
422
  end
419
423
  end
420
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
421
431
  end
422
432
 
423
433
  @all_types.delete_if { |type_name, type_defn| !referenced?(type_defn) }
@@ -470,12 +480,16 @@ module GraphQL
470
480
  type.type_memberships.each do |tm|
471
481
  if @cached_visible[tm]
472
482
  obj_t = tm.object_type
473
- if obj_t.is_a?(String)
474
- obj_t = Member::BuildType.constantize(obj_t)
475
- tm.object_type = obj_t
476
- end
477
- if @cached_visible[obj_t]
478
- add_type(obj_t, tm)
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
479
493
  end
480
494
  end
481
495
  end
@@ -1,28 +1,73 @@
1
1
  # frozen_string_literal: true
2
- require "graphql/schema/visibility/subset"
2
+ require "graphql/schema/visibility/profile"
3
3
  require "graphql/schema/visibility/migration"
4
4
 
5
5
  module GraphQL
6
6
  class Schema
7
+ # Use this plugin to make some parts of your schema hidden from some viewers.
8
+ #
7
9
  class Visibility
8
- def self.use(schema, preload: nil, migration_errors: false)
9
- schema.visibility = self.new(schema, preload: preload)
10
- schema.use_schema_visibility = true
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
11
21
  if migration_errors
12
- schema.subset_class = Migration
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
13
34
  end
14
35
  end
15
36
 
16
- def initialize(schema, preload:)
17
- @schema = schema
18
- @cached_subsets = {}
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
19
49
 
20
- if preload.nil? && defined?(Rails) && Rails.env.production?
21
- preload = true
22
- end
50
+ def migration_errors?
51
+ @migration_errors
52
+ end
23
53
 
24
- if preload
54
+ attr_reader :cached_profiles
25
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)
26
71
  end
27
72
  end
28
73
  end
@@ -61,8 +61,8 @@ module GraphQL
61
61
  def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
62
62
  def arguments(owner, ctx); owner.arguments(ctx); end
63
63
  def loadable?(type, ctx); type.visible?(ctx); end
64
- def schema_subset
65
- @schema_subset ||= Warden::SchemaSubset.new(self)
64
+ def visibility_profile
65
+ @visibility_profile ||= Warden::VisibilityProfile.new(self)
66
66
  end
67
67
  end
68
68
  end
@@ -70,17 +70,17 @@ module GraphQL
70
70
  class NullWarden
71
71
  def initialize(_filter = nil, context:, schema:)
72
72
  @schema = schema
73
- @schema_subset = Warden::SchemaSubset.new(self)
73
+ @visibility_profile = Warden::VisibilityProfile.new(self)
74
74
  end
75
75
 
76
76
  # @api private
77
- module NullSubset
77
+ module NullVisibilityProfile
78
78
  def self.new(context:, schema:)
79
- NullWarden.new(context: context, schema: schema).schema_subset
79
+ NullWarden.new(context: context, schema: schema).visibility_profile
80
80
  end
81
81
  end
82
82
 
83
- attr_reader :schema_subset
83
+ attr_reader :visibility_profile
84
84
 
85
85
  def visible_field?(field_defn, _ctx = nil, owner = nil); true; end
86
86
  def visible_argument?(arg_defn, _ctx = nil); true; end
@@ -88,7 +88,7 @@ module GraphQL
88
88
  def visible_enum_value?(enum_value, _ctx = nil); true; end
89
89
  def visible_type_membership?(type_membership, _ctx = nil); true; end
90
90
  def interface_type_memberships(obj_type, _ctx = nil); obj_type.interface_type_memberships; end
91
- def get_type(type_name); @schema.get_type(type_name); end # rubocop:disable Development/ContextIsPassedCop
91
+ def get_type(type_name); @schema.get_type(type_name, Query::NullContext.instance, false); end # rubocop:disable Development/ContextIsPassedCop
92
92
  def arguments(argument_owner, ctx = nil); argument_owner.all_argument_definitions; end
93
93
  def enum_values(enum_defn); enum_defn.enum_values; end # rubocop:disable Development/ContextIsPassedCop
94
94
  def get_argument(parent_type, argument_name); parent_type.get_argument(argument_name); end # rubocop:disable Development/ContextIsPassedCop
@@ -100,15 +100,15 @@ module GraphQL
100
100
  def reachable_type?(type_name); true; end
101
101
  def loadable?(type, _ctx); true; end
102
102
  def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop
103
- def possible_types(type_defn); @schema.possible_types(type_defn); end
103
+ def possible_types(type_defn); @schema.possible_types(type_defn, Query::NullContext.instance, false); end
104
104
  def interfaces(obj_type); obj_type.interfaces; end
105
105
  end
106
106
 
107
- def schema_subset
108
- @schema_subset ||= SchemaSubset.new(self)
107
+ def visibility_profile
108
+ @visibility_profile ||= VisibilityProfile.new(self)
109
109
  end
110
110
 
111
- class SchemaSubset
111
+ class VisibilityProfile
112
112
  def initialize(warden)
113
113
  @warden = warden
114
114
  end
@@ -193,7 +193,7 @@ module GraphQL
193
193
  @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
194
194
  @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
195
195
  @visible_and_reachable_type = @unions = @unfiltered_interfaces =
196
- @reachable_type_set = @schema_subset =
196
+ @reachable_type_set = @visibility_profile =
197
197
  nil
198
198
  end
199
199
 
@@ -218,7 +218,7 @@ module GraphQL
218
218
  # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
219
219
  def get_type(type_name)
220
220
  @visible_types ||= read_through do |name|
221
- type_defn = @schema.get_type(name, @context)
221
+ type_defn = @schema.get_type(name, @context, false)
222
222
  if type_defn && visible_and_reachable_type?(type_defn)
223
223
  type_defn
224
224
  else
@@ -265,7 +265,7 @@ module GraphQL
265
265
  # @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
266
266
  def possible_types(type_defn)
267
267
  @visible_possible_types ||= read_through { |type_defn|
268
- pt = @schema.possible_types(type_defn, @context)
268
+ pt = @schema.possible_types(type_defn, @context, false)
269
269
  pt.select { |t| visible_and_reachable_type?(t) }
270
270
  }
271
271
  @visible_possible_types[type_defn]