graphql 2.3.18 → 2.4.1
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 +3 -5
- 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/enum.rb +19 -3
- data/lib/graphql/schema/enum_value.rb +1 -1
- data/lib/graphql/schema/input_object.rb +20 -7
- data/lib/graphql/schema/member/has_arguments.rb +2 -2
- data/lib/graphql/schema/member/has_fields.rb +2 -2
- data/lib/graphql/schema/printer.rb +1 -0
- data/lib/graphql/schema/validator/required_validator.rb +28 -4
- data/lib/graphql/schema/visibility/migration.rb +34 -35
- data/lib/graphql/schema/visibility/{subset.rb → profile.rb} +37 -19
- data/lib/graphql/schema/visibility.rb +57 -12
- data/lib/graphql/schema/warden.rb +87 -21
- data/lib/graphql/schema.rb +177 -41
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +2 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
- data/lib/graphql/static_validation/validation_context.rb +15 -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: 12a25d94ae9348527390bad889be7c918390c0a1b133735612ac5a80ca0c6633
|
4
|
+
data.tar.gz: de4bb61a31dc643d359157dff498410f70a5a7bd18d6c254463a2a5294706949
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f85c5a5e4ab0083862c990fe6cd3f0ed99d36a414721d4c3d0a7a3e5c59042cdc5611a6b74d10d0e18525d9e51a32dc6aa1a2349c789b04dae75a835f4bb322a
|
7
|
+
data.tar.gz: e0304dded75753771417672c704054a1daec13ab428ed92f97dbdcee2771f21874bfb6aedf9bd0054806ed97b4483b98376f03526b129d96628735c1933f0d35
|
@@ -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)
|
@@ -18,17 +18,15 @@ module GraphQL
|
|
18
18
|
extend Forwardable
|
19
19
|
|
20
20
|
attr_reader :schema, :query, :warden, :dataloader
|
21
|
-
def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key
|
21
|
+
def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?, :to_h
|
22
22
|
|
23
23
|
def initialize
|
24
24
|
@query = NullQuery.new
|
25
25
|
@dataloader = GraphQL::Dataloader::NullDataloader.new
|
26
26
|
@schema = NullSchema
|
27
27
|
@warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
|
28
|
-
|
29
|
-
|
30
|
-
def types
|
31
|
-
@types ||= GraphQL::Schema::Warden::SchemaSubset.new(@warden)
|
28
|
+
@types = @warden.visibility_profile
|
29
|
+
freeze
|
32
30
|
end
|
33
31
|
end
|
34
32
|
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
|
-
|
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
|
data/lib/graphql/schema/enum.rb
CHANGED
@@ -86,10 +86,24 @@ module GraphQL
|
|
86
86
|
def enum_values(context = GraphQL::Query::NullContext.instance)
|
87
87
|
inherited_values = superclass.respond_to?(:enum_values) ? superclass.enum_values(context) : nil
|
88
88
|
visible_values = []
|
89
|
-
|
89
|
+
types = Warden.types_from_context(context)
|
90
90
|
own_values.each do |key, values_entry|
|
91
|
-
|
92
|
-
|
91
|
+
visible_value = nil
|
92
|
+
if values_entry.is_a?(Array)
|
93
|
+
values_entry.each do |v|
|
94
|
+
if types.visible_enum_value?(v, context)
|
95
|
+
if visible_value.nil?
|
96
|
+
visible_value = v
|
97
|
+
visible_values << v
|
98
|
+
else
|
99
|
+
raise DuplicateNamesError.new(
|
100
|
+
duplicated_name: v.path, duplicated_definition_1: visible_value.inspect, duplicated_definition_2: v.inspect
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
elsif types.visible_enum_value?(values_entry, context)
|
106
|
+
visible_values << values_entry
|
93
107
|
end
|
94
108
|
end
|
95
109
|
|
@@ -153,6 +167,8 @@ module GraphQL
|
|
153
167
|
else
|
154
168
|
nil
|
155
169
|
end
|
170
|
+
# rescue MissingValuesError
|
171
|
+
# nil
|
156
172
|
end
|
157
173
|
|
158
174
|
# Called by the runtime when a field returns a value to give back to the client.
|
@@ -74,7 +74,7 @@ module GraphQL
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def inspect
|
77
|
-
"#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}>"
|
77
|
+
"#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}#{deprecation_reason ? " @deprecation_reason=#{deprecation_reason.inspect}" : ""}>"
|
78
78
|
end
|
79
79
|
|
80
80
|
def visible?(_ctx); true; end
|
@@ -133,12 +133,14 @@ module GraphQL
|
|
133
133
|
end
|
134
134
|
# Add a method access
|
135
135
|
method_name = argument_defn.keyword
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
136
|
+
suppress_redefinition_warning do
|
137
|
+
class_eval <<-RUBY, __FILE__, __LINE__
|
138
|
+
def #{method_name}
|
139
|
+
self[#{method_name.inspect}]
|
140
|
+
end
|
141
|
+
alias_method :#{method_name}, :#{method_name}
|
142
|
+
RUBY
|
143
|
+
end
|
142
144
|
argument_defn
|
143
145
|
end
|
144
146
|
|
@@ -163,7 +165,7 @@ module GraphQL
|
|
163
165
|
|
164
166
|
# Inject missing required arguments
|
165
167
|
missing_required_inputs = ctx.types.arguments(self).reduce({}) do |m, (argument)|
|
166
|
-
if !input.key?(argument.graphql_name) && argument.type.non_null? && types.argument(self, argument.graphql_name)
|
168
|
+
if !input.key?(argument.graphql_name) && argument.type.non_null? && !argument.default_value? && types.argument(self, argument.graphql_name)
|
167
169
|
m[argument.graphql_name] = nil
|
168
170
|
end
|
169
171
|
|
@@ -243,6 +245,17 @@ module GraphQL
|
|
243
245
|
|
244
246
|
result
|
245
247
|
end
|
248
|
+
|
249
|
+
private
|
250
|
+
|
251
|
+
# Suppress redefinition warning for objectId arguments
|
252
|
+
def suppress_redefinition_warning
|
253
|
+
verbose = $VERBOSE
|
254
|
+
$VERBOSE = nil
|
255
|
+
yield
|
256
|
+
ensure
|
257
|
+
$VERBOSE = verbose
|
258
|
+
end
|
246
259
|
end
|
247
260
|
|
248
261
|
private
|
@@ -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,40 @@ 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
|
-
|
102
|
-
warden_ctx.
|
103
|
-
warden_ctx.types = @warden_types
|
98
|
+
warden_ctx.warden = GraphQL::Schema::Warden.new(schema: warden_schema, context: warden_ctx)
|
99
|
+
warden_ctx.warden.skip_warning = true
|
100
|
+
warden_ctx.types = @warden_types = warden_ctx.warden.visibility_profile
|
104
101
|
end
|
105
102
|
end
|
106
103
|
|
107
104
|
def loaded_types
|
108
|
-
@
|
105
|
+
@profile_types.loaded_types
|
109
106
|
end
|
110
107
|
|
111
|
-
|
108
|
+
PUBLIC_PROFILE_METHODS = [
|
112
109
|
:enum_values,
|
113
110
|
:interfaces,
|
114
111
|
:all_types,
|
112
|
+
:all_types_h,
|
115
113
|
:fields,
|
116
114
|
:loadable?,
|
117
115
|
:type,
|
@@ -124,17 +122,18 @@ module GraphQL
|
|
124
122
|
:mutation_root,
|
125
123
|
:possible_types,
|
126
124
|
:subscription_root,
|
127
|
-
:reachable_type
|
125
|
+
:reachable_type?,
|
126
|
+
:visible_enum_value?,
|
128
127
|
]
|
129
128
|
|
130
|
-
|
131
|
-
define_method(
|
132
|
-
call_method_and_compare(
|
129
|
+
PUBLIC_PROFILE_METHODS.each do |profile_method|
|
130
|
+
define_method(profile_method) do |*args|
|
131
|
+
call_method_and_compare(profile_method, args)
|
133
132
|
end
|
134
133
|
end
|
135
134
|
|
136
135
|
def call_method_and_compare(method, args)
|
137
|
-
res_1 = @
|
136
|
+
res_1 = @profile_types.public_send(method, *args)
|
138
137
|
if @skip_error
|
139
138
|
return res_1
|
140
139
|
end
|