graphql 2.3.18 → 2.3.20

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.

@@ -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]