graphql 2.3.17 → 2.3.19

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 194930c79609165d98c3ee58c55d557ce5666860dccd4b4718666bf9080178f6
4
- data.tar.gz: c3bcc14c06fbcd90dcef7e046f96240775f903e479a8d76439cea01cfefe8373
3
+ metadata.gz: 10c07ea3257376b80dc347e8735c085cb1708ab2e10bef481030a9cafb395617
4
+ data.tar.gz: fa2c5a429ec7f5fcef99dad3784b118b4c59144d851fa8158a5c25fc52846404
5
5
  SHA512:
6
- metadata.gz: 1a6e461373942ea8ac603a0b7fb6cd6f2153280f9248d9bd0e6f5fae5c1c5e6e9dbf862114a7a518173ce46e5101a3a4e9ea5239d8f3dfcbd60752a6c086ddad
7
- data.tar.gz: 37e9dcc03c6fd735b6986efa3ea113702f53528bde840341b3d998bbd8a5a35eba11b4e38fded1bedaa8f84dca298e76fbf0d133299fbb4886dbdae36d0a8fe9
6
+ metadata.gz: 9fe9de7dafd4e61471821f13dd39300d70e69d8e64a264bb22e39482ea9d2eedea81470ee9bcb8d59cf2ba840ac8173547b9ce1bf01c45cc57446f92921c22de
7
+ data.tar.gz: 5a4343fb066b3b56129c18f9da25575db4d8eb58b569185a218bcfb069b20ecd51711988823fb25210b5239d0cb1dc03e0dc7c507ae90d6bd00301679d873f17
@@ -12,6 +12,7 @@ module GraphQL
12
12
  end
13
13
 
14
14
  def run
15
+ jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
15
16
  job_fibers = []
16
17
  next_job_fibers = []
17
18
  source_tasks = []
@@ -23,7 +24,7 @@ module GraphQL
23
24
  first_pass = false
24
25
  fiber_vars = get_fiber_variables
25
26
 
26
- while (f = (job_fibers.shift || spawn_job_fiber))
27
+ while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_fiber)))
27
28
  if f.alive?
28
29
  finished = run_fiber(f)
29
30
  if !finished
@@ -37,7 +38,7 @@ module GraphQL
37
38
  Sync do |root_task|
38
39
  set_fiber_variables(fiber_vars)
39
40
  while source_tasks.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
40
- while (task = source_tasks.shift || spawn_source_task(root_task, sources_condition))
41
+ while (task = (source_tasks.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size + next_source_tasks.size) < total_fiber_limit) && spawn_source_task(root_task, sources_condition))))
41
42
  if task.alive?
42
43
  root_task.yield # give the source task a chance to run
43
44
  next_source_tasks << task
@@ -98,7 +98,7 @@ module GraphQL
98
98
  while pending_result_keys.any? { |key| !@results.key?(key) }
99
99
  iterations += 1
100
100
  if iterations > MAX_ITERATIONS
101
- raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency."
101
+ raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency#{@dataloader.fiber_limit ? " or `fiber_limit: #{@dataloader.fiber_limit}` is set too low" : ""}."
102
102
  end
103
103
  @dataloader.yield
104
104
  end
@@ -24,18 +24,23 @@ module GraphQL
24
24
  #
25
25
  class Dataloader
26
26
  class << self
27
- attr_accessor :default_nonblocking
27
+ attr_accessor :default_nonblocking, :default_fiber_limit
28
28
  end
29
29
 
30
- NonblockingDataloader = Class.new(self) { self.default_nonblocking = true }
31
-
32
- def self.use(schema, nonblocking: nil)
33
- schema.dataloader_class = if nonblocking
30
+ def self.use(schema, nonblocking: nil, fiber_limit: nil)
31
+ dataloader_class = if nonblocking
34
32
  warn("`nonblocking: true` is deprecated from `GraphQL::Dataloader`, please use `GraphQL::Dataloader::AsyncDataloader` instead. Docs: https://graphql-ruby.org/dataloader/async_dataloader.")
35
- NonblockingDataloader
33
+ Class.new(self) { self.default_nonblocking = true }
36
34
  else
37
35
  self
38
36
  end
37
+
38
+ if fiber_limit
39
+ dataloader_class = Class.new(dataloader_class)
40
+ dataloader_class.default_fiber_limit = fiber_limit
41
+ end
42
+
43
+ schema.dataloader_class = dataloader_class
39
44
  end
40
45
 
41
46
  # Call the block with a Dataloader instance,
@@ -50,14 +55,18 @@ module GraphQL
50
55
  result
51
56
  end
52
57
 
53
- def initialize(nonblocking: self.class.default_nonblocking)
58
+ def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit)
54
59
  @source_cache = Hash.new { |h, k| h[k] = {} }
55
60
  @pending_jobs = []
56
61
  if !nonblocking.nil?
57
62
  @nonblocking = nonblocking
58
63
  end
64
+ @fiber_limit = fiber_limit
59
65
  end
60
66
 
67
+ # @return [Integer, nil]
68
+ attr_reader :fiber_limit
69
+
61
70
  def nonblocking?
62
71
  @nonblocking
63
72
  end
@@ -178,6 +187,7 @@ module GraphQL
178
187
  end
179
188
 
180
189
  def run
190
+ jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit
181
191
  job_fibers = []
182
192
  next_job_fibers = []
183
193
  source_fibers = []
@@ -187,7 +197,7 @@ module GraphQL
187
197
  while first_pass || job_fibers.any?
188
198
  first_pass = false
189
199
 
190
- while (f = (job_fibers.shift || spawn_job_fiber))
200
+ while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber)))
191
201
  if f.alive?
192
202
  finished = run_fiber(f)
193
203
  if !finished
@@ -197,8 +207,8 @@ module GraphQL
197
207
  end
198
208
  join_queues(job_fibers, next_job_fibers)
199
209
 
200
- while source_fibers.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
201
- while (f = source_fibers.shift || spawn_source_fiber)
210
+ while (source_fibers.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
211
+ while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber))
202
212
  if f.alive?
203
213
  finished = run_fiber(f)
204
214
  if !finished
@@ -242,6 +252,17 @@ module GraphQL
242
252
 
243
253
  private
244
254
 
255
+ def calculate_fiber_limit
256
+ total_fiber_limit = @fiber_limit || Float::INFINITY
257
+ if total_fiber_limit < 4
258
+ raise ArgumentError, "Dataloader fiber limit is too low (#{total_fiber_limit}), it must be at least 4"
259
+ end
260
+ total_fiber_limit -= 1 # deduct one fiber for `manager`
261
+ # Deduct at least one fiber for sources
262
+ jobs_fiber_limit = total_fiber_limit - 2
263
+ return jobs_fiber_limit, total_fiber_limit
264
+ end
265
+
245
266
  def join_queues(prev_queue, new_queue)
246
267
  @nonblocking && Fiber.scheduler.run
247
268
  prev_queue.concat(new_queue)
@@ -28,7 +28,7 @@ module GraphQL
28
28
  end
29
29
 
30
30
  def types
31
- @types ||= GraphQL::Schema::Warden::SchemaSubset.new(@warden)
31
+ @types ||= Schema::Warden::VisibilityProfile.new(@warden)
32
32
  end
33
33
  end
34
34
  end
data/lib/graphql/query.rb CHANGED
@@ -95,21 +95,24 @@ module GraphQL
95
95
  # @param root_value [Object] the object used to resolve fields on the root type
96
96
  # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
97
97
  # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
98
- def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_schema_subset: nil)
98
+ # @param visibility_profile [Symbol]
99
+ def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, visibility_profile: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_visibility_profile: nil)
99
100
  # Even if `variables: nil` is passed, use an empty hash for simpler logic
100
101
  variables ||= {}
101
102
  @schema = schema
102
103
  @context = schema.context_class.new(query: self, values: context)
103
104
 
104
- if use_schema_subset.nil?
105
- use_schema_subset = warden ? false : schema.use_schema_visibility?
105
+ if use_visibility_profile.nil?
106
+ use_visibility_profile = warden ? false : schema.use_visibility_profile?
106
107
  end
107
108
 
108
- if use_schema_subset
109
- @schema_subset = @schema.subset_class.new(context: @context, schema: @schema)
109
+ @visibility_profile = visibility_profile
110
+
111
+ if use_visibility_profile
112
+ @visibility_profile = @schema.visibility.profile_for(@context, visibility_profile)
110
113
  @warden = Schema::Warden::NullWarden.new(context: @context, schema: @schema)
111
114
  else
112
- @schema_subset = nil
115
+ @visibility_profile = nil
113
116
  @warden = warden
114
117
  end
115
118
 
@@ -187,6 +190,9 @@ module GraphQL
187
190
  @query_string ||= (document ? document.to_query_string : nil)
188
191
  end
189
192
 
193
+ # @return [Symbol, nil]
194
+ attr_reader :visibility_profile
195
+
190
196
  attr_accessor :multiplex
191
197
 
192
198
  # @return [GraphQL::Tracing::Trace]
@@ -203,15 +209,19 @@ module GraphQL
203
209
  def lookahead
204
210
  @lookahead ||= begin
205
211
  ast_node = selected_operation
206
- root_type = case ast_node.operation_type
207
- when nil, "query"
208
- types.query_root # rubocop:disable Development/ContextIsPassedCop
209
- when "mutation"
210
- types.mutation_root # rubocop:disable Development/ContextIsPassedCop
211
- when "subscription"
212
- types.subscription_root # rubocop:disable Development/ContextIsPassedCop
212
+ if ast_node.nil?
213
+ GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
214
+ else
215
+ root_type = case ast_node.operation_type
216
+ when nil, "query"
217
+ types.query_root # rubocop:disable Development/ContextIsPassedCop
218
+ when "mutation"
219
+ types.mutation_root # rubocop:disable Development/ContextIsPassedCop
220
+ when "subscription"
221
+ types.subscription_root # rubocop:disable Development/ContextIsPassedCop
222
+ end
223
+ GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
213
224
  end
214
- GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
215
225
  end
216
226
  end
217
227
 
@@ -343,10 +353,33 @@ module GraphQL
343
353
  with_prepared_ast { @warden }
344
354
  end
345
355
 
346
- def_delegators :warden, :get_type, :get_field, :possible_types, :root_type_for_operation
356
+ def get_type(type_name)
357
+ types.type(type_name) # rubocop:disable Development/ContextIsPassedCop
358
+ end
359
+
360
+ def get_field(owner, field_name)
361
+ types.field(owner, field_name) # rubocop:disable Development/ContextIsPassedCop
362
+ end
363
+
364
+ def possible_types(type)
365
+ types.possible_types(type) # rubocop:disable Development/ContextIsPassedCop
366
+ end
367
+
368
+ def root_type_for_operation(op_type)
369
+ case op_type
370
+ when "query"
371
+ types.query_root # rubocop:disable Development/ContextIsPassedCop
372
+ when "mutation"
373
+ types.mutation_root # rubocop:disable Development/ContextIsPassedCop
374
+ when "subscription"
375
+ types.subscription_root # rubocop:disable Development/ContextIsPassedCop
376
+ else
377
+ raise ArgumentError, "unexpected root type name: #{op_type.inspect}; expected 'query', 'mutation', or 'subscription'"
378
+ end
379
+ end
347
380
 
348
381
  def types
349
- @schema_subset || warden.schema_subset
382
+ @visibility_profile || warden.visibility_profile
350
383
  end
351
384
 
352
385
  # @param abstract_type [GraphQL::UnionType, GraphQL::InterfaceType]
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  class Schema
4
- class AlwaysVisible
4
+ module AlwaysVisible
5
5
  def self.use(schema, **opts)
6
- schema.warden_class = GraphQL::Schema::Warden::NullWarden
7
- schema.subset_class = GraphQL::Schema::Warden::NullWarden::NullSubset
6
+ schema.extend(self)
7
+ end
8
+
9
+ def visible?(_member, _context)
10
+ true
8
11
  end
9
12
  end
10
13
  end
@@ -364,6 +364,7 @@ module GraphQL
364
364
 
365
365
  # @api private
366
366
  def validate_default_value
367
+ return unless default_value?
367
368
  coerced_default_value = begin
368
369
  # This is weird, but we should accept single-item default values for list-type arguments.
369
370
  # If we used `coerce_isolated_input` below, it would do this for us, but it's not really
@@ -188,6 +188,7 @@ module GraphQL
188
188
 
189
189
  def self.inherited(child_class)
190
190
  child_class.definition_default_resolve = self.definition_default_resolve
191
+ super
191
192
  end
192
193
  end
193
194
 
@@ -135,7 +135,7 @@ module GraphQL
135
135
 
136
136
  def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
137
137
  warden = Warden.from_context(context)
138
- skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)
138
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
139
139
  for ancestor in ancestors
140
140
  if ancestor.respond_to?(:own_arguments) &&
141
141
  (a = ancestor.own_arguments[argument_name]) &&
@@ -210,7 +210,7 @@ module GraphQL
210
210
  # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
211
211
  def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
212
212
  warden = Warden.from_context(context)
213
- if (arg_config = own_arguments[argument_name]) && ((context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)) || (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden)))
213
+ if (arg_config = own_arguments[argument_name]) && ((context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)) || (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden)))
214
214
  visible_arg || arg_config
215
215
  elsif defined?(@resolver_class) && @resolver_class
216
216
  @resolver_class.get_field_argument(argument_name, context)
@@ -99,7 +99,7 @@ module GraphQL
99
99
  module InterfaceMethods
100
100
  def get_field(field_name, context = GraphQL::Query::NullContext.instance)
101
101
  warden = Warden.from_context(context)
102
- skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)
102
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
103
103
  for ancestor in ancestors
104
104
  if ancestor.respond_to?(:own_fields) &&
105
105
  (f_entry = ancestor.own_fields[field_name]) &&
@@ -135,7 +135,7 @@ module GraphQL
135
135
  # Objects need to check that the interface implementation is visible, too
136
136
  warden = Warden.from_context(context)
137
137
  ancs = ancestors
138
- skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)
138
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
139
139
  i = 0
140
140
  while (ancestor = ancs[i])
141
141
  if ancestor.respond_to?(:own_fields) &&
@@ -35,9 +35,10 @@ module GraphQL
35
35
  # end
36
36
  #
37
37
  class RequiredValidator < Validator
38
- # @param one_of [Symbol, Array<Symbol>] An argument, or a list of arguments, that represents a valid set of inputs for this field
38
+ # @param one_of [Array<Symbol>] A list of arguments, exactly one of which is required for this field
39
+ # @param argument [Symbol] An argument that is required for this field
39
40
  # @param message [String]
40
- def initialize(one_of: nil, argument: nil, message: "%{validated} has the wrong arguments", **default_options)
41
+ def initialize(one_of: nil, argument: nil, message: nil, **default_options)
41
42
  @one_of = if one_of
42
43
  one_of
43
44
  elsif argument
@@ -49,7 +50,7 @@ module GraphQL
49
50
  super(**default_options)
50
51
  end
51
52
 
52
- def validate(_object, _context, value)
53
+ def validate(_object, context, value)
53
54
  matched_conditions = 0
54
55
 
55
56
  if !value.nil?
@@ -73,9 +74,32 @@ module GraphQL
73
74
  if matched_conditions == 1
74
75
  nil # OK
75
76
  else
76
- @message
77
+ @message || build_message(context)
77
78
  end
78
79
  end
80
+
81
+ def build_message(context)
82
+ argument_definitions = @validated.arguments(context).values
83
+ required_names = @one_of.map do |arg_keyword|
84
+ if arg_keyword.is_a?(Array)
85
+ names = arg_keyword.map { |arg| arg_keyword_to_grapqhl_name(argument_definitions, arg) }
86
+ "(" + names.join(" and ") + ")"
87
+ else
88
+ arg_keyword_to_grapqhl_name(argument_definitions, arg_keyword)
89
+ end
90
+ end
91
+
92
+ if required_names.size == 1
93
+ "%{validated} must include the following argument: #{required_names.first}."
94
+ else
95
+ "%{validated} must include exactly one of the following arguments: #{required_names.join(", ")}."
96
+ end
97
+ end
98
+
99
+ def arg_keyword_to_grapqhl_name(argument_definitions, arg_keyword)
100
+ argument_definition = argument_definitions.find { |defn| defn.keyword == arg_keyword }
101
+ argument_definition.graphql_name
102
+ end
79
103
  end
80
104
  end
81
105
  end
@@ -2,7 +2,7 @@
2
2
  module GraphQL
3
3
  class Schema
4
4
  class Visibility
5
- # You can use this to see how {GraphQL::Schema::Warden} and {GraphQL::Schema::Visibility::Subset}
5
+ # You can use this to see how {GraphQL::Schema::Warden} and {GraphQL::Schema::Visibility::Profile}
6
6
  # handle `.visible?` differently in your schema.
7
7
  #
8
8
  # It runs the same method on both implementations and raises an error when the results diverge.
@@ -15,28 +15,24 @@ module GraphQL
15
15
  # This plugin adds two keys to `context` when running:
16
16
  #
17
17
  # - `visibility_migration_running: true`
18
- # - For the {Warden} which it instantiates, it adds `visibility_migration_warden_running: true`.
18
+ # - For the {Schema::Warden} which it instantiates, it adds `visibility_migration_warden_running: true`.
19
19
  #
20
20
  # Use those keys to modify your `visible?` behavior as needed.
21
21
  #
22
22
  # Also, in a pinch, you can set `skip_visibility_migration_error: true` in context to turn off this behavior per-query.
23
- # (In that case, it uses {Subset} directly.)
23
+ # (In that case, it uses {Profile} directly.)
24
24
  #
25
25
  # @example Adding this plugin
26
26
  #
27
- # use GraphQL::Schema::Visibility::Migration
27
+ # use GraphQL::Schema::Visibility, migration_errors: true
28
28
  #
29
- class Migration < GraphQL::Schema::Visibility::Subset
30
- def self.use(schema)
31
- schema.subset_class = self
32
- end
33
-
29
+ class Migration < GraphQL::Schema::Visibility::Profile
34
30
  class RuntimeTypesMismatchError < GraphQL::Error
35
- def initialize(method_called, warden_result, subset_result, method_args)
31
+ def initialize(method_called, warden_result, profile_result, method_args)
36
32
  super(<<~ERR)
37
33
  Mismatch in types for `##{method_called}(#{method_args.map(&:inspect).join(", ")})`:
38
34
 
39
- #{compare_results(warden_result, subset_result)}
35
+ #{compare_results(warden_result, profile_result)}
40
36
 
41
37
  Update your `.visible?` implementation to make these implementations return the same value.
42
38
 
@@ -45,9 +41,9 @@ module GraphQL
45
41
  end
46
42
 
47
43
  private
48
- def compare_results(warden_result, subset_result)
49
- if warden_result.is_a?(Array) && subset_result.is_a?(Array)
50
- all_results = warden_result | subset_result
44
+ def compare_results(warden_result, profile_result)
45
+ if warden_result.is_a?(Array) && profile_result.is_a?(Array)
46
+ all_results = warden_result | profile_result
51
47
  all_results.sort_by!(&:graphql_name)
52
48
 
53
49
  entries_text = all_results.map { |entry| "#{entry.graphql_name} (#{entry})"}
@@ -55,13 +51,13 @@ module GraphQL
55
51
  yes = " ✔ "
56
52
  no = " "
57
53
  res = "".dup
58
- res << "#{"Result".center(width)} Warden Subset \n"
54
+ res << "#{"Result".center(width)} Warden Profile \n"
59
55
  all_results.each_with_index do |entry, idx|
60
- res << "#{entries_text[idx].ljust(width)}#{warden_result.include?(entry) ? yes : no}#{subset_result.include?(entry) ? yes : no}\n"
56
+ res << "#{entries_text[idx].ljust(width)}#{warden_result.include?(entry) ? yes : no}#{profile_result.include?(entry) ? yes : no}\n"
61
57
  end
62
58
  res << "\n"
63
59
  else
64
- "- Warden returned: #{humanize(warden_result)}\n\n- Subset returned: #{humanize(subset_result)}"
60
+ "- Warden returned: #{humanize(warden_result)}\n\n- Visibility::Profile returned: #{humanize(profile_result)}"
65
61
  end
66
62
  end
67
63
  def humanize(val)
@@ -80,38 +76,39 @@ module GraphQL
80
76
  end
81
77
  end
82
78
 
83
- def initialize(context:, schema:)
84
- @skip_error = context[:skip_visibility_migration_error]
85
- context[:visibility_migration_running] = true
86
- @subset_types = GraphQL::Schema::Visibility::Subset.new(context: context, schema: schema)
79
+ def initialize(context:, schema:, name: nil)
80
+ @name = name
81
+ @skip_error = context[:skip_visibility_migration_error] || context.is_a?(Query::NullContext) || context.is_a?(Hash)
82
+ @profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema)
87
83
  if !@skip_error
84
+ context[:visibility_migration_running] = true
88
85
  warden_ctx_vals = context.to_h.dup
89
86
  warden_ctx_vals[:visibility_migration_warden_running] = true
90
- if defined?(schema::WardenCompatSchema)
91
- warden_schema = schema::WardenCompatSchema
87
+ if schema.const_defined?(:WardenCompatSchema, false) # don't use a defn from a superclass
88
+ warden_schema = schema.const_get(:WardenCompatSchema, false)
92
89
  else
93
90
  warden_schema = Class.new(schema)
94
- warden_schema.use_schema_visibility = false
91
+ warden_schema.use_visibility_profile = false
95
92
  # TODO public API
96
93
  warden_schema.send(:add_type_and_traverse, [warden_schema.query, warden_schema.mutation, warden_schema.subscription].compact, root: true)
97
94
  warden_schema.send(:add_type_and_traverse, warden_schema.directives.values + warden_schema.orphan_types, root: false)
95
+ schema.const_set(:WardenCompatSchema, warden_schema)
98
96
  end
99
97
  warden_ctx = GraphQL::Query::Context.new(query: context.query, values: warden_ctx_vals)
100
- example_warden = GraphQL::Schema::Warden.new(schema: warden_schema, context: warden_ctx)
101
- @warden_types = example_warden.schema_subset
102
- warden_ctx.warden = example_warden
103
- warden_ctx.types = @warden_types
98
+ warden_ctx.warden = GraphQL::Schema::Warden.new(schema: warden_schema, context: warden_ctx)
99
+ warden_ctx.types = @warden_types = warden_ctx.warden.visibility_profile
104
100
  end
105
101
  end
106
102
 
107
103
  def loaded_types
108
- @subset_types.loaded_types
104
+ @profile_types.loaded_types
109
105
  end
110
106
 
111
- PUBLIC_SUBSET_METHODS = [
107
+ PUBLIC_PROFILE_METHODS = [
112
108
  :enum_values,
113
109
  :interfaces,
114
110
  :all_types,
111
+ :all_types_h,
115
112
  :fields,
116
113
  :loadable?,
117
114
  :type,
@@ -127,14 +124,14 @@ module GraphQL
127
124
  :reachable_type?
128
125
  ]
129
126
 
130
- PUBLIC_SUBSET_METHODS.each do |subset_method|
131
- define_method(subset_method) do |*args|
132
- call_method_and_compare(subset_method, args)
127
+ PUBLIC_PROFILE_METHODS.each do |profile_method|
128
+ define_method(profile_method) do |*args|
129
+ call_method_and_compare(profile_method, args)
133
130
  end
134
131
  end
135
132
 
136
133
  def call_method_and_compare(method, args)
137
- res_1 = @subset_types.public_send(method, *args)
134
+ res_1 = @profile_types.public_send(method, *args)
138
135
  if @skip_error
139
136
  return res_1
140
137
  end
@@ -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]
@@ -162,6 +162,7 @@ module GraphQL
162
162
  # re-apply them here
163
163
  mods = trace_modules_for(:default)
164
164
  mods.each { |mod| new_class.include(mod) }
165
+ new_class.include(DefaultTraceClass)
165
166
  trace_mode(:default, new_class)
166
167
  backtrace_class = Class.new(new_class)
167
168
  backtrace_class.include(GraphQL::Backtrace::Trace)
@@ -316,6 +317,9 @@ module GraphQL
316
317
  GraphQL::StaticValidation::Validator.new(schema: self)
317
318
  end
318
319
 
320
+ # Add `plugin` to this schema
321
+ # @param plugin [#use] A Schema plugin
322
+ # @return void
319
323
  def use(plugin, **kwargs)
320
324
  if kwargs.any?
321
325
  plugin.use(self, **kwargs)
@@ -333,8 +337,9 @@ module GraphQL
333
337
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
334
338
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
335
339
  def types(context = GraphQL::Query::NullContext.instance)
336
- if use_schema_visibility?
337
- return Visibility::Subset.from_context(context, self).all_types_h
340
+ if use_visibility_profile?
341
+ types = Visibility::Profile.from_context(context, self)
342
+ return types.all_types_h
338
343
  end
339
344
  all_types = non_introspection_types.merge(introspection_system.types)
340
345
  visible_types = {}
@@ -361,17 +366,19 @@ module GraphQL
361
366
  end
362
367
 
363
368
  # @param type_name [String]
369
+ # @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
370
+ # @param use_visibility_profile Private, for migration to {Schema::Visibility}
364
371
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
365
- def get_type(type_name, context = GraphQL::Query::NullContext.instance)
366
- if use_schema_visibility?
367
- return Visibility::Subset.from_context(context, self).type(type_name)
372
+ def get_type(type_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
373
+ if use_visibility_profile
374
+ return Visibility::Profile.from_context(context, self).type(type_name)
368
375
  end
369
376
  local_entry = own_types[type_name]
370
377
  type_defn = case local_entry
371
378
  when nil
372
379
  nil
373
380
  when Array
374
- if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)
381
+ if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
375
382
  local_entry
376
383
  else
377
384
  visible_t = nil
@@ -397,7 +404,7 @@ module GraphQL
397
404
 
398
405
  type_defn ||
399
406
  introspection_system.types[type_name] || # todo context-specific introspection?
400
- (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context) : nil)
407
+ (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context, use_visibility_profile) : nil)
401
408
  end
402
409
 
403
410
  # @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility?
@@ -429,7 +436,7 @@ module GraphQL
429
436
  if @query_object
430
437
  dup_defn = new_query_object || yield
431
438
  raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
432
- elsif use_schema_visibility?
439
+ elsif use_visibility_profile?
433
440
  @query_object = block_given? ? lazy_load_block : new_query_object
434
441
  else
435
442
  @query_object = new_query_object || lazy_load_block.call
@@ -448,7 +455,7 @@ module GraphQL
448
455
  if @mutation_object
449
456
  dup_defn = new_mutation_object || yield
450
457
  raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
451
- elsif use_schema_visibility?
458
+ elsif use_visibility_profile?
452
459
  @mutation_object = block_given? ? lazy_load_block : new_mutation_object
453
460
  else
454
461
  @mutation_object = new_mutation_object || lazy_load_block.call
@@ -467,7 +474,7 @@ module GraphQL
467
474
  if @subscription_object
468
475
  dup_defn = new_subscription_object || yield
469
476
  raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
470
- elsif use_schema_visibility?
477
+ elsif use_visibility_profile?
471
478
  @subscription_object = block_given? ? lazy_load_block : new_subscription_object
472
479
  add_subscription_extension_if_necessary
473
480
  else
@@ -501,7 +508,7 @@ module GraphQL
501
508
  end
502
509
 
503
510
  def root_types
504
- if use_schema_visibility?
511
+ if use_visibility_profile?
505
512
  [query, mutation, subscription].compact
506
513
  else
507
514
  @root_types
@@ -520,37 +527,43 @@ module GraphQL
520
527
 
521
528
  attr_writer :warden_class
522
529
 
523
- def subset_class
524
- if defined?(@subset_class)
525
- @subset_class
526
- elsif superclass.respond_to?(:subset_class)
527
- superclass.subset_class
530
+ # @api private
531
+ def visibility_profile_class
532
+ if defined?(@visibility_profile_class)
533
+ @visibility_profile_class
534
+ elsif superclass.respond_to?(:visibility_profile_class)
535
+ superclass.visibility_profile_class
528
536
  else
529
- GraphQL::Schema::Visibility::Subset
537
+ GraphQL::Schema::Visibility::Profile
530
538
  end
531
539
  end
532
540
 
533
- attr_writer :subset_class, :use_schema_visibility, :visibility
534
-
535
- def use_schema_visibility?
536
- if defined?(@use_schema_visibility)
537
- @use_schema_visibility
538
- elsif superclass.respond_to?(:use_schema_visibility?)
539
- superclass.use_schema_visibility?
541
+ # @api private
542
+ attr_writer :visibility_profile_class, :use_visibility_profile
543
+ # @api private
544
+ attr_accessor :visibility
545
+ # @api private
546
+ def use_visibility_profile?
547
+ if defined?(@use_visibility_profile)
548
+ @use_visibility_profile
549
+ elsif superclass.respond_to?(:use_visibility_profile?)
550
+ superclass.use_visibility_profile?
540
551
  else
541
552
  false
542
553
  end
543
554
  end
544
555
 
545
556
  # @param type [Module] The type definition whose possible types you want to see
557
+ # @param context [GraphQL::Query::Context] used for filtering visible possible types at runtime
558
+ # @param use_visibility_profile Private, for migration to {Schema::Visibility}
546
559
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
547
560
  # @return [Array<Module>] Possible types for `type`, if it's given.
548
- def possible_types(type = nil, context = GraphQL::Query::NullContext.instance)
549
- if use_schema_visibility?
561
+ def possible_types(type = nil, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
562
+ if use_visibility_profile
550
563
  if type
551
- return Visibility::Subset.from_context(context, self).possible_types(type)
564
+ return Visibility::Profile.from_context(context, self).possible_types(type)
552
565
  else
553
- raise "Schema.possible_types is not implemented for `use_schema_visibility?`"
566
+ raise "Schema.possible_types is not implemented for `use_visibility_profile?`"
554
567
  end
555
568
  end
556
569
  if type
@@ -570,7 +583,7 @@ module GraphQL
570
583
  introspection_system.possible_types[type] ||
571
584
  (
572
585
  superclass.respond_to?(:possible_types) ?
573
- superclass.possible_types(type, context) :
586
+ superclass.possible_types(type, context, use_visibility_profile) :
574
587
  EMPTY_ARRAY
575
588
  )
576
589
  end
@@ -926,7 +939,7 @@ module GraphQL
926
939
  To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
927
940
  ERR
928
941
  end
929
- add_type_and_traverse(new_orphan_types, root: false) unless use_schema_visibility?
942
+ add_type_and_traverse(new_orphan_types, root: false) unless use_visibility_profile?
930
943
  own_orphan_types.concat(new_orphan_types.flatten)
931
944
  end
932
945
 
@@ -1068,6 +1081,11 @@ module GraphQL
1068
1081
  child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
1069
1082
  end
1070
1083
  child_class.singleton_class.prepend(ResolveTypeWithType)
1084
+
1085
+ if use_visibility_profile?
1086
+ vis = self.visibility
1087
+ child_class.visibility = vis.dup_for(child_class)
1088
+ end
1071
1089
  super
1072
1090
  end
1073
1091
 
@@ -1185,7 +1203,7 @@ module GraphQL
1185
1203
  # @param new_directive [Class]
1186
1204
  # @return void
1187
1205
  def directive(new_directive)
1188
- if use_schema_visibility?
1206
+ if use_visibility_profile?
1189
1207
  own_directives[new_directive.graphql_name] = new_directive
1190
1208
  else
1191
1209
  add_type_and_traverse(new_directive, root: false)
@@ -212,6 +212,7 @@ module GraphQL
212
212
 
213
213
  def find_conflict(response_key, field1, field2, mutually_exclusive: false)
214
214
  return if @conflict_count >= context.max_errors
215
+ return if field1.definition.nil? || field2.definition.nil?
215
216
 
216
217
  node1 = field1.node
217
218
  node2 = field2.node
@@ -92,7 +92,7 @@ module GraphQL
92
92
  end
93
93
  graphql_result
94
94
  else
95
- unfiltered_type = Schema::Visibility::Subset.pass_thru(schema: schema, context: context).type(type_name)
95
+ unfiltered_type = Schema::Visibility::Profile.pass_thru(schema: schema, context: context).type(type_name)
96
96
  if unfiltered_type
97
97
  raise TypeNotVisibleError.new(type_name: type_name)
98
98
  else
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.3.17"
3
+ VERSION = "2.3.19"
4
4
  end
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.3.17
4
+ version: 2.3.19
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-10-04 00:00:00.000000000 Z
11
+ date: 2024-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -503,7 +503,7 @@ files:
503
503
  - lib/graphql/schema/validator/required_validator.rb
504
504
  - lib/graphql/schema/visibility.rb
505
505
  - lib/graphql/schema/visibility/migration.rb
506
- - lib/graphql/schema/visibility/subset.rb
506
+ - lib/graphql/schema/visibility/profile.rb
507
507
  - lib/graphql/schema/warden.rb
508
508
  - lib/graphql/schema/wrapper.rb
509
509
  - lib/graphql/static_validation.rb