graphql 2.3.5 → 2.3.8
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/analysis/analyzer.rb +89 -0
- data/lib/graphql/analysis/field_usage.rb +82 -0
- data/lib/graphql/analysis/max_query_complexity.rb +20 -0
- data/lib/graphql/analysis/max_query_depth.rb +20 -0
- data/lib/graphql/analysis/query_complexity.rb +183 -0
- data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
- data/lib/graphql/analysis/visitor.rb +283 -0
- data/lib/graphql/analysis.rb +92 -1
- data/lib/graphql/dataloader/async_dataloader.rb +2 -0
- data/lib/graphql/execution/interpreter/runtime.rb +6 -6
- data/lib/graphql/execution/interpreter.rb +1 -1
- data/lib/graphql/execution/lookahead.rb +10 -10
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +1 -1
- data/lib/graphql/introspection/schema_type.rb +13 -3
- data/lib/graphql/introspection/type_type.rb +5 -5
- data/lib/graphql/language/document_from_schema_definition.rb +19 -26
- data/lib/graphql/language/lexer.rb +0 -3
- data/lib/graphql/language/sanitized_printer.rb +1 -1
- data/lib/graphql/language.rb +0 -1
- data/lib/graphql/query/context.rb +4 -0
- data/lib/graphql/query/null_context.rb +4 -0
- data/lib/graphql/query/validation_pipeline.rb +2 -2
- data/lib/graphql/query.rb +26 -3
- data/lib/graphql/schema/always_visible.rb +1 -0
- data/lib/graphql/schema/argument.rb +19 -5
- data/lib/graphql/schema/directive.rb +2 -0
- data/lib/graphql/schema/enum.rb +4 -4
- data/lib/graphql/schema/field.rb +13 -1
- data/lib/graphql/schema/has_single_input_argument.rb +2 -1
- data/lib/graphql/schema/input_object.rb +8 -7
- data/lib/graphql/schema/introspection_system.rb +2 -14
- data/lib/graphql/schema/member/has_arguments.rb +7 -6
- data/lib/graphql/schema/member/has_fields.rb +6 -4
- data/lib/graphql/schema/resolver.rb +5 -5
- data/lib/graphql/schema/subset.rb +397 -0
- data/lib/graphql/schema/type_expression.rb +2 -2
- data/lib/graphql/schema/validator/all_validator.rb +60 -0
- data/lib/graphql/schema/validator.rb +2 -0
- data/lib/graphql/schema/warden.rb +88 -1
- data/lib/graphql/schema.rb +44 -15
- data/lib/graphql/static_validation/base_visitor.rb +6 -5
- data/lib/graphql/static_validation/literal_validator.rb +4 -4
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +7 -7
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -3
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/validation_context.rb +2 -2
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
- data/lib/graphql/subscriptions/event.rb +1 -1
- data/lib/graphql/subscriptions.rb +3 -3
- data/lib/graphql/testing/helpers.rb +2 -2
- data/lib/graphql/types/relay/connection_behaviors.rb +10 -0
- data/lib/graphql/types/relay/edge_behaviors.rb +10 -0
- data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
- data/lib/graphql/version.rb +1 -1
- metadata +12 -13
- data/lib/graphql/analysis/ast/analyzer.rb +0 -91
- data/lib/graphql/analysis/ast/field_usage.rb +0 -84
- data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
- data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
- data/lib/graphql/analysis/ast/query_complexity.rb +0 -185
- data/lib/graphql/analysis/ast/visitor.rb +0 -284
- data/lib/graphql/analysis/ast.rb +0 -94
- data/lib/graphql/language/token.rb +0 -34
- data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -0,0 +1,397 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Subset
|
6
|
+
def initialize(query)
|
7
|
+
@query = query
|
8
|
+
@context = query.context
|
9
|
+
@schema = query.schema
|
10
|
+
@all_types = {}
|
11
|
+
@all_types_loaded = false
|
12
|
+
@unvisited_types = []
|
13
|
+
@referenced_types = Hash.new { |h, type_defn| h[type_defn] = [] }.compare_by_identity
|
14
|
+
@cached_possible_types = nil
|
15
|
+
@cached_visible = Hash.new { |h, member|
|
16
|
+
h[member] = @schema.visible?(member, @context)
|
17
|
+
}.compare_by_identity
|
18
|
+
|
19
|
+
@cached_visible_fields = Hash.new { |h, owner|
|
20
|
+
h[owner] = Hash.new do |h2, field|
|
21
|
+
h2[field] = if @cached_visible[field] &&
|
22
|
+
(ret_type = field.type.unwrap) &&
|
23
|
+
@cached_visible[ret_type] &&
|
24
|
+
reachable_type?(ret_type.graphql_name) &&
|
25
|
+
(owner == field.owner || (!owner.kind.object?) || field_on_visible_interface?(field, owner))
|
26
|
+
|
27
|
+
if !field.introspection?
|
28
|
+
# The problem is that some introspection fields may have references
|
29
|
+
# to non-custom introspection types.
|
30
|
+
# If those were added here, they'd cause a DuplicateNamesError.
|
31
|
+
# This is basically a bug -- those fields _should_ reference the custom types.
|
32
|
+
add_type(ret_type, field)
|
33
|
+
end
|
34
|
+
true
|
35
|
+
else
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end.compare_by_identity
|
39
|
+
}.compare_by_identity
|
40
|
+
|
41
|
+
@cached_visible_arguments = Hash.new do |h, arg|
|
42
|
+
h[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type]
|
43
|
+
add_type(arg_type, arg)
|
44
|
+
true
|
45
|
+
else
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end.compare_by_identity
|
49
|
+
|
50
|
+
@unfiltered_pt = Hash.new do |hash, type|
|
51
|
+
hash[type] = @schema.possible_types(type)
|
52
|
+
end.compare_by_identity
|
53
|
+
end
|
54
|
+
|
55
|
+
def field_on_visible_interface?(field, owner)
|
56
|
+
ints = owner.interface_type_memberships.map(&:abstract_type)
|
57
|
+
field_name = field.graphql_name
|
58
|
+
filtered_ints = interfaces(owner)
|
59
|
+
any_interface_has_field = false
|
60
|
+
any_interface_has_visible_field = false
|
61
|
+
ints.each do |int_t|
|
62
|
+
if (_int_f_defn = int_t.get_field(field_name, @context))
|
63
|
+
any_interface_has_field = true
|
64
|
+
|
65
|
+
if filtered_ints.include?(int_t) # TODO cycles, or maybe not necessary since previously checked? && @cached_visible_fields[owner][field]
|
66
|
+
any_interface_has_visible_field = true
|
67
|
+
break
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if any_interface_has_field
|
73
|
+
any_interface_has_visible_field
|
74
|
+
else
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def type(type_name)
|
80
|
+
t = if (loaded_t = @all_types[type_name])
|
81
|
+
loaded_t
|
82
|
+
elsif !@all_types_loaded
|
83
|
+
load_all_types
|
84
|
+
@all_types[type_name]
|
85
|
+
end
|
86
|
+
|
87
|
+
if t
|
88
|
+
if t.is_a?(Array)
|
89
|
+
vis_t = nil
|
90
|
+
t.each do |t_defn|
|
91
|
+
if @cached_visible[t_defn]
|
92
|
+
if vis_t.nil?
|
93
|
+
vis_t = t_defn
|
94
|
+
else
|
95
|
+
raise_duplicate_definition(vis_t, t_defn)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
vis_t
|
100
|
+
else
|
101
|
+
if t && @cached_visible[t]
|
102
|
+
t
|
103
|
+
else
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def field(owner, field_name)
|
111
|
+
f = if owner.kind.fields? && (field = owner.get_field(field_name, @context))
|
112
|
+
field
|
113
|
+
elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
|
114
|
+
entry_point_field
|
115
|
+
elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
|
116
|
+
dynamic_field
|
117
|
+
else
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
if f.is_a?(Array)
|
121
|
+
visible_f = nil
|
122
|
+
f.each do |f_defn|
|
123
|
+
if @cached_visible_fields[owner][f_defn]
|
124
|
+
|
125
|
+
if visible_f.nil?
|
126
|
+
visible_f = f_defn
|
127
|
+
else
|
128
|
+
raise_duplicate_definition(visible_f, f_defn)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
visible_f
|
133
|
+
else
|
134
|
+
if f && @cached_visible_fields[owner][f]
|
135
|
+
f
|
136
|
+
else
|
137
|
+
nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def fields(owner)
|
143
|
+
non_duplicate_items(owner.all_field_definitions, @cached_visible_fields[owner])
|
144
|
+
end
|
145
|
+
|
146
|
+
def arguments(owner)
|
147
|
+
non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments)
|
148
|
+
end
|
149
|
+
|
150
|
+
def argument(owner, arg_name)
|
151
|
+
# TODO this makes a Warden.visible_entry call down the stack
|
152
|
+
# I need a non-Warden implementation
|
153
|
+
arg = owner.get_argument(arg_name, @context)
|
154
|
+
if arg.is_a?(Array)
|
155
|
+
visible_arg = nil
|
156
|
+
arg.each do |arg_defn|
|
157
|
+
if @cached_visible_arguments[arg_defn]
|
158
|
+
if arg_defn&.loads
|
159
|
+
add_type(arg_defn.loads, arg_defn)
|
160
|
+
end
|
161
|
+
if visible_arg.nil?
|
162
|
+
visible_arg = arg_defn
|
163
|
+
else
|
164
|
+
raise_duplicate_definition(visible_arg, arg_defn)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
visible_arg
|
169
|
+
else
|
170
|
+
if arg && @cached_visible_arguments[arg]
|
171
|
+
if arg&.loads
|
172
|
+
add_type(arg.loads, arg)
|
173
|
+
end
|
174
|
+
arg
|
175
|
+
else
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def possible_types(type)
|
182
|
+
@cached_possible_types ||= Hash.new do |h, type|
|
183
|
+
pt = case type.kind.name
|
184
|
+
when "INTERFACE"
|
185
|
+
# TODO this requires the global map
|
186
|
+
@unfiltered_pt[type]
|
187
|
+
when "UNION"
|
188
|
+
type.type_memberships.select { |tm| @cached_visible[tm] && @cached_visible[tm.object_type] }.map!(&:object_type)
|
189
|
+
else
|
190
|
+
[type]
|
191
|
+
end
|
192
|
+
|
193
|
+
# TODO use `select!` when possible, skip it for `[type]`
|
194
|
+
h[type] = pt.select { |t|
|
195
|
+
@cached_visible[t] && referenced?(t)
|
196
|
+
}
|
197
|
+
end.compare_by_identity
|
198
|
+
@cached_possible_types[type]
|
199
|
+
end
|
200
|
+
|
201
|
+
def interfaces(obj_or_int_type)
|
202
|
+
ints = obj_or_int_type.interface_type_memberships
|
203
|
+
.select { |itm| @cached_visible[itm] && @cached_visible[itm.abstract_type] }
|
204
|
+
.map!(&:abstract_type)
|
205
|
+
ints.uniq! # Remove any duplicate interfaces implemented via other interfaces
|
206
|
+
ints
|
207
|
+
end
|
208
|
+
|
209
|
+
def query_root
|
210
|
+
add_if_visible(@schema.query)
|
211
|
+
end
|
212
|
+
|
213
|
+
def mutation_root
|
214
|
+
add_if_visible(@schema.mutation)
|
215
|
+
end
|
216
|
+
|
217
|
+
def subscription_root
|
218
|
+
add_if_visible(@schema.subscription)
|
219
|
+
end
|
220
|
+
|
221
|
+
def all_types
|
222
|
+
@all_types_filtered ||= begin
|
223
|
+
load_all_types
|
224
|
+
at = []
|
225
|
+
@all_types.each do |_name, type_defn|
|
226
|
+
if possible_types(type_defn).any? || referenced?(type_defn)
|
227
|
+
at << type_defn
|
228
|
+
end
|
229
|
+
end
|
230
|
+
at
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def enum_values(owner)
|
235
|
+
values = non_duplicate_items(owner.all_enum_value_definitions, @cached_visible)
|
236
|
+
if values.size == 0
|
237
|
+
raise GraphQL::Schema::Enum::MissingValuesError.new(owner)
|
238
|
+
end
|
239
|
+
values
|
240
|
+
end
|
241
|
+
|
242
|
+
def directive_exists?(dir_name)
|
243
|
+
dir = @schema.directives[dir_name]
|
244
|
+
dir && @cached_visible[dir]
|
245
|
+
end
|
246
|
+
|
247
|
+
def directives
|
248
|
+
@schema.directives.each_value.select { |d| @cached_visible[d] }
|
249
|
+
end
|
250
|
+
|
251
|
+
def loadable?(t, _ctx)
|
252
|
+
!@all_types[t.graphql_name] # TODO make sure t is not reachable but t is visible
|
253
|
+
end
|
254
|
+
|
255
|
+
# TODO rename this to indicate that it is called with a typename
|
256
|
+
def reachable_type?(type_name)
|
257
|
+
load_all_types
|
258
|
+
!!((t = @all_types[type_name]) && referenced?(t))
|
259
|
+
end
|
260
|
+
|
261
|
+
def loaded_types
|
262
|
+
@all_types.values
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
|
267
|
+
def add_if_visible(t)
|
268
|
+
(t && @cached_visible[t]) ? (add_type(t, true); t) : nil
|
269
|
+
end
|
270
|
+
|
271
|
+
def add_type(t, by_member)
|
272
|
+
if t && @cached_visible[t]
|
273
|
+
n = t.graphql_name
|
274
|
+
if (prev_t = @all_types[n])
|
275
|
+
if !prev_t.equal?(t)
|
276
|
+
raise_duplicate_definition(prev_t, t)
|
277
|
+
end
|
278
|
+
false
|
279
|
+
else
|
280
|
+
@referenced_types[t] << by_member
|
281
|
+
@all_types[n] = t
|
282
|
+
@unvisited_types << t
|
283
|
+
true
|
284
|
+
end
|
285
|
+
else
|
286
|
+
false
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def non_duplicate_items(definitions, visibility_cache)
|
291
|
+
non_dups = []
|
292
|
+
definitions.each do |defn|
|
293
|
+
if visibility_cache[defn]
|
294
|
+
if (dup_defn = non_dups.find { |d| d.graphql_name == defn.graphql_name })
|
295
|
+
raise_duplicate_definition(dup_defn, defn)
|
296
|
+
end
|
297
|
+
non_dups << defn
|
298
|
+
end
|
299
|
+
end
|
300
|
+
non_dups
|
301
|
+
end
|
302
|
+
|
303
|
+
def raise_duplicate_definition(first_defn, second_defn)
|
304
|
+
raise DuplicateNamesError.new(duplicated_name: first_defn.path, duplicated_definition_1: first_defn.inspect, duplicated_definition_2: second_defn.inspect)
|
305
|
+
end
|
306
|
+
|
307
|
+
def referenced?(t)
|
308
|
+
load_all_types
|
309
|
+
res = if @referenced_types[t].any? { |member| (member == true) || @cached_visible[member] }
|
310
|
+
if t.kind.abstract?
|
311
|
+
possible_types(t).any?
|
312
|
+
else
|
313
|
+
true
|
314
|
+
end
|
315
|
+
end
|
316
|
+
res
|
317
|
+
end
|
318
|
+
|
319
|
+
def load_all_types
|
320
|
+
return if @all_types_loaded
|
321
|
+
@all_types_loaded = true
|
322
|
+
schema_types = [
|
323
|
+
query_root,
|
324
|
+
mutation_root,
|
325
|
+
subscription_root,
|
326
|
+
*@schema.introspection_system.types.values,
|
327
|
+
]
|
328
|
+
|
329
|
+
# Don't include any orphan_types whose interfaces aren't visible.
|
330
|
+
@schema.orphan_types.each do |orphan_type|
|
331
|
+
if @cached_visible[orphan_type] &&
|
332
|
+
orphan_type.interface_type_memberships.any? { |tm| @cached_visible[tm] && @cached_visible[tm.abstract_type] }
|
333
|
+
schema_types << orphan_type
|
334
|
+
end
|
335
|
+
end
|
336
|
+
schema_types.compact! # TODO why is this necessary?!
|
337
|
+
schema_types.flatten! # handle multiple defns
|
338
|
+
schema_types.each { |t| add_type(t, true) }
|
339
|
+
|
340
|
+
while t = @unvisited_types.pop
|
341
|
+
# These have already been checked for `.visible?`
|
342
|
+
visit_type(t)
|
343
|
+
end
|
344
|
+
|
345
|
+
@all_types.delete_if { |type_name, type_defn| !referenced?(type_defn) }
|
346
|
+
nil
|
347
|
+
end
|
348
|
+
|
349
|
+
def visit_type(type)
|
350
|
+
if type.kind.input_object?
|
351
|
+
# recurse into visible arguments
|
352
|
+
arguments(type).each do |argument|
|
353
|
+
add_type(argument.type.unwrap, argument)
|
354
|
+
end
|
355
|
+
elsif type.kind.union?
|
356
|
+
# recurse into visible possible types
|
357
|
+
type.type_memberships.each do |tm|
|
358
|
+
if @cached_visible[tm] && @cached_visible[tm.object_type]
|
359
|
+
add_type(tm.object_type, tm)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
elsif type.kind.fields?
|
363
|
+
if type.kind.object?
|
364
|
+
# recurse into visible implemented interfaces
|
365
|
+
interfaces(type).each do |interface|
|
366
|
+
add_type(interface, type)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# recurse into visible fields
|
371
|
+
t_f = type.all_field_definitions
|
372
|
+
t_f.each do |field|
|
373
|
+
if @cached_visible[field]
|
374
|
+
field_type = field.type.unwrap
|
375
|
+
if field_type.kind.interface?
|
376
|
+
pt = @unfiltered_pt[field_type]
|
377
|
+
pt.each do |obj_type|
|
378
|
+
if @cached_visible[obj_type] &&
|
379
|
+
(tm = obj_type.interface_type_memberships.find { |tm| tm.abstract_type == field_type }) &&
|
380
|
+
@cached_visible[tm]
|
381
|
+
add_type(obj_type, tm)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
add_type(field_type, field)
|
386
|
+
|
387
|
+
# recurse into visible arguments
|
388
|
+
arguments(field).each do |argument|
|
389
|
+
add_type(argument.type.unwrap, argument)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
@@ -5,13 +5,13 @@ module GraphQL
|
|
5
5
|
module TypeExpression
|
6
6
|
# Fetch a type from a type map by its AST specification.
|
7
7
|
# Return `nil` if not found.
|
8
|
-
# @param type_owner [#
|
8
|
+
# @param type_owner [#type] A thing for looking up types by name
|
9
9
|
# @param ast_node [GraphQL::Language::Nodes::AbstractNode]
|
10
10
|
# @return [Class, GraphQL::Schema::NonNull, GraphQL::Schema:List]
|
11
11
|
def self.build_type(type_owner, ast_node)
|
12
12
|
case ast_node
|
13
13
|
when GraphQL::Language::Nodes::TypeName
|
14
|
-
type_owner.
|
14
|
+
type_owner.type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware
|
15
15
|
when GraphQL::Language::Nodes::NonNullType
|
16
16
|
ast_inner_type = ast_node.of_type
|
17
17
|
inner_type = build_type(type_owner, ast_inner_type)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Validator
|
6
|
+
# Use this to validate each member of an array value.
|
7
|
+
#
|
8
|
+
# @example validate format of all strings in an array
|
9
|
+
#
|
10
|
+
# argument :handles, [String],
|
11
|
+
# validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ } } }
|
12
|
+
#
|
13
|
+
# @example multiple validators can be combined
|
14
|
+
#
|
15
|
+
# argument :handles, [String],
|
16
|
+
# validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ }, length: { maximum: 32 } } }
|
17
|
+
#
|
18
|
+
# @example any type can be used
|
19
|
+
#
|
20
|
+
# argument :choices, [Integer],
|
21
|
+
# validates: { all: { inclusion: { in: 1..12 } } }
|
22
|
+
#
|
23
|
+
class AllValidator < Validator
|
24
|
+
def initialize(validated:, allow_blank: false, allow_null: false, **validators)
|
25
|
+
super(validated: validated, allow_blank: allow_blank, allow_null: allow_null)
|
26
|
+
|
27
|
+
@validators = Validator.from_config(validated, validators)
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate(object, context, value)
|
31
|
+
all_errors = EMPTY_ARRAY
|
32
|
+
|
33
|
+
value.each do |subvalue|
|
34
|
+
@validators.each do |validator|
|
35
|
+
errors = validator.validate(object, context, subvalue)
|
36
|
+
if errors &&
|
37
|
+
(errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
|
38
|
+
(errors.is_a?(String))
|
39
|
+
if all_errors.frozen? # It's empty
|
40
|
+
all_errors = []
|
41
|
+
end
|
42
|
+
if errors.is_a?(String)
|
43
|
+
all_errors << errors
|
44
|
+
else
|
45
|
+
all_errors.concat(errors)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
unless all_errors.frozen?
|
52
|
+
all_errors.uniq!
|
53
|
+
end
|
54
|
+
|
55
|
+
all_errors
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -169,3 +169,5 @@ require "graphql/schema/validator/allow_null_validator"
|
|
169
169
|
GraphQL::Schema::Validator.install(:allow_null, GraphQL::Schema::Validator::AllowNullValidator)
|
170
170
|
require "graphql/schema/validator/allow_blank_validator"
|
171
171
|
GraphQL::Schema::Validator.install(:allow_blank, GraphQL::Schema::Validator::AllowBlankValidator)
|
172
|
+
require "graphql/schema/validator/all_validator"
|
173
|
+
GraphQL::Schema::Validator.install(:all, GraphQL::Schema::Validator::AllValidator)
|
@@ -61,14 +61,27 @@ module GraphQL
|
|
61
61
|
def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
|
62
62
|
def arguments(owner, ctx); owner.arguments(ctx); end
|
63
63
|
def loadable?(type, ctx); type.visible?(ctx); end
|
64
|
+
def schema_subset
|
65
|
+
@schema_subset ||= Warden::SchemaSubset.new(self)
|
66
|
+
end
|
64
67
|
end
|
65
68
|
end
|
66
69
|
|
67
70
|
class NullWarden
|
68
71
|
def initialize(_filter = nil, context:, schema:)
|
69
72
|
@schema = schema
|
73
|
+
@schema_subset = Warden::SchemaSubset.new(self)
|
74
|
+
end
|
75
|
+
|
76
|
+
# @api private
|
77
|
+
module NullSubset
|
78
|
+
def self.new(query)
|
79
|
+
NullWarden.new(context: query.context, schema: query.schema).schema_subset
|
80
|
+
end
|
70
81
|
end
|
71
82
|
|
83
|
+
attr_reader :schema_subset
|
84
|
+
|
72
85
|
def visible_field?(field_defn, _ctx = nil, owner = nil); true; end
|
73
86
|
def visible_argument?(arg_defn, _ctx = nil); true; end
|
74
87
|
def visible_type?(type_defn, _ctx = nil); true; end
|
@@ -91,6 +104,80 @@ module GraphQL
|
|
91
104
|
def interfaces(obj_type); obj_type.interfaces; end
|
92
105
|
end
|
93
106
|
|
107
|
+
def schema_subset
|
108
|
+
@schema_subset ||= SchemaSubset.new(self)
|
109
|
+
end
|
110
|
+
|
111
|
+
class SchemaSubset
|
112
|
+
def initialize(warden)
|
113
|
+
@warden = warden
|
114
|
+
end
|
115
|
+
|
116
|
+
def directives
|
117
|
+
@warden.directives
|
118
|
+
end
|
119
|
+
|
120
|
+
def directive_exists?(dir_name)
|
121
|
+
@warden.directives.any? { |d| d.graphql_name == dir_name }
|
122
|
+
end
|
123
|
+
|
124
|
+
def type(name)
|
125
|
+
@warden.get_type(name)
|
126
|
+
end
|
127
|
+
|
128
|
+
def field(owner, field_name)
|
129
|
+
@warden.get_field(owner, field_name)
|
130
|
+
end
|
131
|
+
|
132
|
+
def argument(owner, arg_name)
|
133
|
+
@warden.get_argument(owner, arg_name)
|
134
|
+
end
|
135
|
+
|
136
|
+
def query_root
|
137
|
+
@warden.root_type_for_operation("query")
|
138
|
+
end
|
139
|
+
|
140
|
+
def mutation_root
|
141
|
+
@warden.root_type_for_operation("mutation")
|
142
|
+
end
|
143
|
+
|
144
|
+
def subscription_root
|
145
|
+
@warden.root_type_for_operation("subscription")
|
146
|
+
end
|
147
|
+
|
148
|
+
def arguments(owner)
|
149
|
+
@warden.arguments(owner)
|
150
|
+
end
|
151
|
+
|
152
|
+
def fields(owner)
|
153
|
+
@warden.fields(owner)
|
154
|
+
end
|
155
|
+
|
156
|
+
def possible_types(type)
|
157
|
+
@warden.possible_types(type)
|
158
|
+
end
|
159
|
+
|
160
|
+
def enum_values(enum_type)
|
161
|
+
@warden.enum_values(enum_type)
|
162
|
+
end
|
163
|
+
|
164
|
+
def all_types
|
165
|
+
@warden.reachable_types
|
166
|
+
end
|
167
|
+
|
168
|
+
def interfaces(obj_type)
|
169
|
+
@warden.interfaces(obj_type)
|
170
|
+
end
|
171
|
+
|
172
|
+
def loadable?(t, ctx) # TODO remove ctx here?
|
173
|
+
@warden.loadable?(t, ctx)
|
174
|
+
end
|
175
|
+
|
176
|
+
def reachable_type?(type_name)
|
177
|
+
@warden.reachable_type?(type_name)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
94
181
|
# @param context [GraphQL::Query::Context]
|
95
182
|
# @param schema [GraphQL::Schema]
|
96
183
|
def initialize(context:, schema:)
|
@@ -107,7 +194,7 @@ module GraphQL
|
|
107
194
|
@visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
|
108
195
|
@visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
|
109
196
|
@visible_and_reachable_type = @unions = @unfiltered_interfaces =
|
110
|
-
@reachable_type_set =
|
197
|
+
@reachable_type_set = @schema_subset =
|
111
198
|
nil
|
112
199
|
end
|
113
200
|
|
data/lib/graphql/schema.rb
CHANGED
@@ -5,7 +5,6 @@ require "graphql/schema/always_visible"
|
|
5
5
|
require "graphql/schema/base_64_encoder"
|
6
6
|
require "graphql/schema/find_inherited_value"
|
7
7
|
require "graphql/schema/finder"
|
8
|
-
require "graphql/schema/invalid_type_error"
|
9
8
|
require "graphql/schema/introspection_system"
|
10
9
|
require "graphql/schema/late_bound_type"
|
11
10
|
require "graphql/schema/null_mask"
|
@@ -46,6 +45,7 @@ require "graphql/schema/mutation"
|
|
46
45
|
require "graphql/schema/has_single_input_argument"
|
47
46
|
require "graphql/schema/relay_classic_mutation"
|
48
47
|
require "graphql/schema/subscription"
|
48
|
+
require "graphql/schema/subset"
|
49
49
|
|
50
50
|
module GraphQL
|
51
51
|
# A GraphQL schema which may be queried with {GraphQL::Query}.
|
@@ -370,20 +370,24 @@ module GraphQL
|
|
370
370
|
when nil
|
371
371
|
nil
|
372
372
|
when Array
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
373
|
+
if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
|
374
|
+
local_entry
|
375
|
+
else
|
376
|
+
visible_t = nil
|
377
|
+
warden = Warden.from_context(context)
|
378
|
+
local_entry.each do |t|
|
379
|
+
if warden.visible_type?(t, context)
|
380
|
+
if visible_t.nil?
|
381
|
+
visible_t = t
|
382
|
+
else
|
383
|
+
raise DuplicateNamesError.new(
|
384
|
+
duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
|
385
|
+
)
|
386
|
+
end
|
383
387
|
end
|
384
388
|
end
|
389
|
+
visible_t
|
385
390
|
end
|
386
|
-
visible_t
|
387
391
|
when Module
|
388
392
|
local_entry
|
389
393
|
else
|
@@ -497,6 +501,28 @@ module GraphQL
|
|
497
501
|
|
498
502
|
attr_writer :warden_class
|
499
503
|
|
504
|
+
def subset_class
|
505
|
+
if defined?(@subset_class)
|
506
|
+
@subset_class
|
507
|
+
elsif superclass.respond_to?(:subset_class)
|
508
|
+
superclass.subset_class
|
509
|
+
else
|
510
|
+
GraphQL::Schema::Subset
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
attr_writer :subset_class, :use_schema_subset
|
515
|
+
|
516
|
+
def use_schema_subset?
|
517
|
+
if defined?(@use_schema_subset)
|
518
|
+
@use_schema_subset
|
519
|
+
elsif superclass.respond_to?(:use_schema_subset?)
|
520
|
+
superclass.use_schema_subset?
|
521
|
+
else
|
522
|
+
false
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
500
526
|
# @param type [Module] The type definition whose possible types you want to see
|
501
527
|
# @return [Hash<String, Module>] All possible types, if no `type` is given.
|
502
528
|
# @return [Array<Module>] Possible types for `type`, if it's given.
|
@@ -573,9 +599,8 @@ module GraphQL
|
|
573
599
|
end
|
574
600
|
end
|
575
601
|
|
576
|
-
def type_from_ast(ast_node, context:
|
577
|
-
|
578
|
-
GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node)
|
602
|
+
def type_from_ast(ast_node, context: self.query_class.new(self, "{ __typename }").context)
|
603
|
+
GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
|
579
604
|
end
|
580
605
|
|
581
606
|
def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance)
|
@@ -1053,6 +1078,10 @@ module GraphQL
|
|
1053
1078
|
Member::HasDirectives.get_directives(self, @own_schema_directives, :schema_directives)
|
1054
1079
|
end
|
1055
1080
|
|
1081
|
+
# Called when a type is needed by name at runtime
|
1082
|
+
def load_type(type_name, ctx)
|
1083
|
+
get_type(type_name, ctx)
|
1084
|
+
end
|
1056
1085
|
# This hook is called when an object fails an `authorized?` check.
|
1057
1086
|
# You might report to your bug tracker here, so you can correct
|
1058
1087
|
# the field resolvers not to return unauthorized objects.
|