graphql 2.3.18 → 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.
- 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 +48 -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
|
@@ -317,6 +317,9 @@ module GraphQL
|
|
|
317
317
|
GraphQL::StaticValidation::Validator.new(schema: self)
|
|
318
318
|
end
|
|
319
319
|
|
|
320
|
+
# Add `plugin` to this schema
|
|
321
|
+
# @param plugin [#use] A Schema plugin
|
|
322
|
+
# @return void
|
|
320
323
|
def use(plugin, **kwargs)
|
|
321
324
|
if kwargs.any?
|
|
322
325
|
plugin.use(self, **kwargs)
|
|
@@ -334,8 +337,9 @@ module GraphQL
|
|
|
334
337
|
# @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
|
|
335
338
|
# @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
|
|
336
339
|
def types(context = GraphQL::Query::NullContext.instance)
|
|
337
|
-
if
|
|
338
|
-
|
|
340
|
+
if use_visibility_profile?
|
|
341
|
+
types = Visibility::Profile.from_context(context, self)
|
|
342
|
+
return types.all_types_h
|
|
339
343
|
end
|
|
340
344
|
all_types = non_introspection_types.merge(introspection_system.types)
|
|
341
345
|
visible_types = {}
|
|
@@ -362,17 +366,19 @@ module GraphQL
|
|
|
362
366
|
end
|
|
363
367
|
|
|
364
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}
|
|
365
371
|
# @return [Module, nil] A type, or nil if there's no type called `type_name`
|
|
366
|
-
def get_type(type_name, context = GraphQL::Query::NullContext.instance)
|
|
367
|
-
if
|
|
368
|
-
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)
|
|
369
375
|
end
|
|
370
376
|
local_entry = own_types[type_name]
|
|
371
377
|
type_defn = case local_entry
|
|
372
378
|
when nil
|
|
373
379
|
nil
|
|
374
380
|
when Array
|
|
375
|
-
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)
|
|
376
382
|
local_entry
|
|
377
383
|
else
|
|
378
384
|
visible_t = nil
|
|
@@ -398,7 +404,7 @@ module GraphQL
|
|
|
398
404
|
|
|
399
405
|
type_defn ||
|
|
400
406
|
introspection_system.types[type_name] || # todo context-specific introspection?
|
|
401
|
-
(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)
|
|
402
408
|
end
|
|
403
409
|
|
|
404
410
|
# @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility?
|
|
@@ -430,7 +436,7 @@ module GraphQL
|
|
|
430
436
|
if @query_object
|
|
431
437
|
dup_defn = new_query_object || yield
|
|
432
438
|
raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
|
|
433
|
-
elsif
|
|
439
|
+
elsif use_visibility_profile?
|
|
434
440
|
@query_object = block_given? ? lazy_load_block : new_query_object
|
|
435
441
|
else
|
|
436
442
|
@query_object = new_query_object || lazy_load_block.call
|
|
@@ -449,7 +455,7 @@ module GraphQL
|
|
|
449
455
|
if @mutation_object
|
|
450
456
|
dup_defn = new_mutation_object || yield
|
|
451
457
|
raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
|
|
452
|
-
elsif
|
|
458
|
+
elsif use_visibility_profile?
|
|
453
459
|
@mutation_object = block_given? ? lazy_load_block : new_mutation_object
|
|
454
460
|
else
|
|
455
461
|
@mutation_object = new_mutation_object || lazy_load_block.call
|
|
@@ -468,7 +474,7 @@ module GraphQL
|
|
|
468
474
|
if @subscription_object
|
|
469
475
|
dup_defn = new_subscription_object || yield
|
|
470
476
|
raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
|
|
471
|
-
elsif
|
|
477
|
+
elsif use_visibility_profile?
|
|
472
478
|
@subscription_object = block_given? ? lazy_load_block : new_subscription_object
|
|
473
479
|
add_subscription_extension_if_necessary
|
|
474
480
|
else
|
|
@@ -502,7 +508,7 @@ module GraphQL
|
|
|
502
508
|
end
|
|
503
509
|
|
|
504
510
|
def root_types
|
|
505
|
-
if
|
|
511
|
+
if use_visibility_profile?
|
|
506
512
|
[query, mutation, subscription].compact
|
|
507
513
|
else
|
|
508
514
|
@root_types
|
|
@@ -521,37 +527,43 @@ module GraphQL
|
|
|
521
527
|
|
|
522
528
|
attr_writer :warden_class
|
|
523
529
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
|
529
536
|
else
|
|
530
|
-
GraphQL::Schema::Visibility::
|
|
537
|
+
GraphQL::Schema::Visibility::Profile
|
|
531
538
|
end
|
|
532
539
|
end
|
|
533
540
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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?
|
|
541
551
|
else
|
|
542
552
|
false
|
|
543
553
|
end
|
|
544
554
|
end
|
|
545
555
|
|
|
546
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}
|
|
547
559
|
# @return [Hash<String, Module>] All possible types, if no `type` is given.
|
|
548
560
|
# @return [Array<Module>] Possible types for `type`, if it's given.
|
|
549
|
-
def possible_types(type = nil, context = GraphQL::Query::NullContext.instance)
|
|
550
|
-
if
|
|
561
|
+
def possible_types(type = nil, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
|
|
562
|
+
if use_visibility_profile
|
|
551
563
|
if type
|
|
552
|
-
return Visibility::
|
|
564
|
+
return Visibility::Profile.from_context(context, self).possible_types(type)
|
|
553
565
|
else
|
|
554
|
-
raise "Schema.possible_types is not implemented for `
|
|
566
|
+
raise "Schema.possible_types is not implemented for `use_visibility_profile?`"
|
|
555
567
|
end
|
|
556
568
|
end
|
|
557
569
|
if type
|
|
@@ -571,7 +583,7 @@ module GraphQL
|
|
|
571
583
|
introspection_system.possible_types[type] ||
|
|
572
584
|
(
|
|
573
585
|
superclass.respond_to?(:possible_types) ?
|
|
574
|
-
superclass.possible_types(type, context) :
|
|
586
|
+
superclass.possible_types(type, context, use_visibility_profile) :
|
|
575
587
|
EMPTY_ARRAY
|
|
576
588
|
)
|
|
577
589
|
end
|
|
@@ -927,7 +939,7 @@ module GraphQL
|
|
|
927
939
|
To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
|
|
928
940
|
ERR
|
|
929
941
|
end
|
|
930
|
-
add_type_and_traverse(new_orphan_types, root: false) unless
|
|
942
|
+
add_type_and_traverse(new_orphan_types, root: false) unless use_visibility_profile?
|
|
931
943
|
own_orphan_types.concat(new_orphan_types.flatten)
|
|
932
944
|
end
|
|
933
945
|
|
|
@@ -1069,6 +1081,11 @@ module GraphQL
|
|
|
1069
1081
|
child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
|
|
1070
1082
|
end
|
|
1071
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
|
|
1072
1089
|
super
|
|
1073
1090
|
end
|
|
1074
1091
|
|
|
@@ -1186,7 +1203,7 @@ module GraphQL
|
|
|
1186
1203
|
# @param new_directive [Class]
|
|
1187
1204
|
# @return void
|
|
1188
1205
|
def directive(new_directive)
|
|
1189
|
-
if
|
|
1206
|
+
if use_visibility_profile?
|
|
1190
1207
|
own_directives[new_directive.graphql_name] = new_directive
|
|
1191
1208
|
else
|
|
1192
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
|