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 +4 -4
- 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/query/null_context.rb +1 -1
- data/lib/graphql/query.rb +49 -16
- data/lib/graphql/schema/always_visible.rb +6 -3
- data/lib/graphql/schema/argument.rb +1 -0
- data/lib/graphql/schema/build_from_definition.rb +1 -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/validator/required_validator.rb +28 -4
- data/lib/graphql/schema/visibility/migration.rb +31 -34
- data/lib/graphql/schema/visibility/{subset.rb → profile.rb} +31 -17
- data/lib/graphql/schema/visibility.rb +57 -12
- data/lib/graphql/schema/warden.rb +14 -14
- data/lib/graphql/schema.rb +49 -31
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
- data/lib/graphql/testing/helpers.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10c07ea3257376b80dc347e8735c085cb1708ab2e10bef481030a9cafb395617
|
4
|
+
data.tar.gz: fa2c5a429ec7f5fcef99dad3784b118b4c59144d851fa8158a5c25fc52846404
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/graphql/dataloader.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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)
|
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
|
-
|
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
|
105
|
-
|
105
|
+
if use_visibility_profile.nil?
|
106
|
+
use_visibility_profile = warden ? false : schema.use_visibility_profile?
|
106
107
|
end
|
107
108
|
|
108
|
-
|
109
|
-
|
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
|
-
@
|
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
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
|
4
|
+
module AlwaysVisible
|
5
5
|
def self.use(schema, **opts)
|
6
|
-
schema.
|
7
|
-
|
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
|
@@ -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::
|
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::
|
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::
|
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::
|
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 [
|
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:
|
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,
|
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::
|
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 {
|
23
|
+
# (In that case, it uses {Profile} directly.)
|
24
24
|
#
|
25
25
|
# @example Adding this plugin
|
26
26
|
#
|
27
|
-
# use GraphQL::Schema::Visibility
|
27
|
+
# use GraphQL::Schema::Visibility, migration_errors: true
|
28
28
|
#
|
29
|
-
class Migration < GraphQL::Schema::Visibility::
|
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,
|
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,
|
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,
|
49
|
-
if warden_result.is_a?(Array) &&
|
50
|
-
all_results = warden_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
|
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}#{
|
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-
|
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
|
-
@
|
85
|
-
context[:
|
86
|
-
@
|
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
|
91
|
-
warden_schema = schema
|
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.
|
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
|
-
|
101
|
-
@warden_types =
|
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
|
-
@
|
104
|
+
@profile_types.loaded_types
|
109
105
|
end
|
110
106
|
|
111
|
-
|
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
|
-
|
131
|
-
define_method(
|
132
|
-
call_method_and_compare(
|
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 = @
|
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
|
-
|
16
|
-
|
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
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
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?(
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
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/
|
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
|
-
|
9
|
-
|
10
|
-
|
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.
|
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
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
50
|
+
def migration_errors?
|
51
|
+
@migration_errors
|
52
|
+
end
|
23
53
|
|
24
|
-
|
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
|
65
|
-
@
|
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
|
-
@
|
73
|
+
@visibility_profile = Warden::VisibilityProfile.new(self)
|
74
74
|
end
|
75
75
|
|
76
76
|
# @api private
|
77
|
-
module
|
77
|
+
module NullVisibilityProfile
|
78
78
|
def self.new(context:, schema:)
|
79
|
-
NullWarden.new(context: context, schema: schema).
|
79
|
+
NullWarden.new(context: context, schema: schema).visibility_profile
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
-
attr_reader :
|
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
|
108
|
-
@
|
107
|
+
def visibility_profile
|
108
|
+
@visibility_profile ||= VisibilityProfile.new(self)
|
109
109
|
end
|
110
110
|
|
111
|
-
class
|
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 = @
|
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]
|
data/lib/graphql/schema.rb
CHANGED
@@ -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
|
337
|
-
|
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
|
367
|
-
return Visibility::
|
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::
|
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
|
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
|
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
|
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
|
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
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
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::
|
537
|
+
GraphQL::Schema::Visibility::Profile
|
530
538
|
end
|
531
539
|
end
|
532
540
|
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
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
|
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::
|
564
|
+
return Visibility::Profile.from_context(context, self).possible_types(type)
|
552
565
|
else
|
553
|
-
raise "Schema.possible_types is not implemented for `
|
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
|
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
|
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::
|
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
|
data/lib/graphql/version.rb
CHANGED
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.
|
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-
|
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/
|
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
|