graphql 2.6.13 → 2.6.14
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/analysis/max_query_complexity.rb +1 -1
- data/lib/graphql/execution/field_resolve_step.rb +243 -157
- data/lib/graphql/execution/input_values.rb +3 -1
- data/lib/graphql/execution/prepare_object_step.rb +37 -13
- data/lib/graphql/language/lexer.rb +1 -1
- data/lib/graphql/language/parser.rb +5 -1
- data/lib/graphql/pagination/relation_connection.rb +21 -10
- data/lib/graphql/query/partial.rb +1 -2
- data/lib/graphql/schema/addition.rb +55 -48
- data/lib/graphql/schema/argument.rb +14 -3
- data/lib/graphql/schema/build_from_definition.rb +435 -173
- data/lib/graphql/schema/directive.rb +16 -0
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +1 -1
- data/lib/graphql/schema/member/build_type.rb +5 -2
- data/lib/graphql/schema/member/has_arguments.rb +0 -4
- data/lib/graphql/schema/member/has_directives.rb +7 -1
- data/lib/graphql/schema/member/has_interfaces.rb +0 -2
- data/lib/graphql/schema/object.rb +11 -3
- data/lib/graphql/schema.rb +4 -2
- data/lib/graphql/version.rb +1 -1
- metadata +2 -2
|
@@ -7,6 +7,10 @@ module GraphQL
|
|
|
7
7
|
module Pagination
|
|
8
8
|
# A generic class for working with database query objects.
|
|
9
9
|
class RelationConnection < Pagination::Connection
|
|
10
|
+
# Database limits and offsets must fit in a signed 64-bit integer.
|
|
11
|
+
MAX_OFFSET = (2**63) - 1
|
|
12
|
+
private_constant :MAX_OFFSET
|
|
13
|
+
|
|
10
14
|
def nodes
|
|
11
15
|
load_nodes
|
|
12
16
|
@nodes
|
|
@@ -87,7 +91,11 @@ module GraphQL
|
|
|
87
91
|
# :call-seq:
|
|
88
92
|
# relation_larger_than(Object relation, Integer _initial_offset, Integer size) -> bool
|
|
89
93
|
def relation_larger_than(relation, _initial_offset, size)
|
|
90
|
-
|
|
94
|
+
if (limit = relation_limit(relation)) && limit <= size
|
|
95
|
+
false
|
|
96
|
+
else
|
|
97
|
+
relation_count(set_limit(relation, size + 1)) == size + 1
|
|
98
|
+
end
|
|
91
99
|
end
|
|
92
100
|
|
|
93
101
|
# **Parameters**
|
|
@@ -154,7 +162,8 @@ module GraphQL
|
|
|
154
162
|
# offset_from_cursor(cursor) -> Integer
|
|
155
163
|
def offset_from_cursor(cursor)
|
|
156
164
|
offset = Integer(decode(cursor), 10, exception: false) #: Integer?
|
|
157
|
-
if offset.nil? || offset <= 0
|
|
165
|
+
if offset.nil? || offset <= 0 || offset > MAX_OFFSET ||
|
|
166
|
+
offset + (relation_offset(items) || 0) > MAX_OFFSET
|
|
158
167
|
raise GraphQL::ExecutionError, "Invalid cursor: #{cursor.inspect}"
|
|
159
168
|
end
|
|
160
169
|
offset
|
|
@@ -191,20 +200,21 @@ module GraphQL
|
|
|
191
200
|
|
|
192
201
|
if after_offset
|
|
193
202
|
next_offset += after_offset
|
|
203
|
+
relation_limit = [relation_limit - after_offset, 0].max if relation_limit
|
|
194
204
|
end
|
|
195
205
|
|
|
196
206
|
if before_offset && after_offset
|
|
197
207
|
if after_offset < before_offset
|
|
198
208
|
# Get the number of items between the two cursors
|
|
199
209
|
space_between = before_offset - after_offset - 1
|
|
200
|
-
relation_limit = space_between
|
|
210
|
+
relation_limit = [relation_limit, space_between].compact.min
|
|
201
211
|
else
|
|
202
212
|
# The cursors overextend one another to an empty set
|
|
203
213
|
@sliced_nodes_null_relation = true
|
|
204
214
|
end
|
|
205
215
|
elsif before_offset
|
|
206
216
|
# Use limit to cut off the tail of the relation
|
|
207
|
-
relation_limit = before_offset - 1
|
|
217
|
+
relation_limit = [relation_limit, before_offset - 1].compact.min
|
|
208
218
|
end
|
|
209
219
|
|
|
210
220
|
@sliced_nodes_limit = relation_limit
|
|
@@ -222,13 +232,13 @@ module GraphQL
|
|
|
222
232
|
if @sliced_nodes_null_relation
|
|
223
233
|
paginated_nodes = null_relation(paginated_nodes)
|
|
224
234
|
else
|
|
225
|
-
if @sliced_nodes_limit
|
|
226
|
-
paginated_nodes = set_limit(paginated_nodes, @sliced_nodes_limit)
|
|
227
|
-
end
|
|
228
|
-
|
|
229
235
|
if @sliced_nodes_offset
|
|
230
236
|
paginated_nodes = set_offset(paginated_nodes, @sliced_nodes_offset)
|
|
231
237
|
end
|
|
238
|
+
|
|
239
|
+
if @sliced_nodes_limit
|
|
240
|
+
paginated_nodes = set_limit(paginated_nodes, @sliced_nodes_limit)
|
|
241
|
+
end
|
|
232
242
|
end
|
|
233
243
|
|
|
234
244
|
paginated_nodes
|
|
@@ -330,10 +340,11 @@ module GraphQL
|
|
|
330
340
|
end
|
|
331
341
|
|
|
332
342
|
# Fetch one row past `first` so `has_next_page` needs no extra query.
|
|
333
|
-
# Skips arrays (no query to save) and
|
|
343
|
+
# Skips arrays (no query to save) and slices limited to `first` or fewer rows (no next page).
|
|
334
344
|
def load_page
|
|
335
345
|
page = limited_nodes
|
|
336
|
-
return page.to_a if page.is_a?(Array) || !peek_next_page? || relation_limit(page) != first
|
|
346
|
+
return page.to_a if page.is_a?(Array) || !peek_next_page? || relation_limit(page) != first ||
|
|
347
|
+
(@sliced_nodes_limit && @sliced_nodes_limit <= first)
|
|
337
348
|
|
|
338
349
|
rows = set_limit(page, first + 1).to_a
|
|
339
350
|
@has_next_page = rows.size > first
|
|
@@ -179,8 +179,7 @@ module GraphQL
|
|
|
179
179
|
next_selections << sel
|
|
180
180
|
end
|
|
181
181
|
else
|
|
182
|
-
raise "Unexpected selection in partial path: #{sel.class}, #{sel.inspect}"
|
|
183
|
-
sel #: absurd
|
|
182
|
+
raise "Unexpected selection in partial path: #{sel.class}, #{sel.inspect}" #: absurd(sel)
|
|
184
183
|
end
|
|
185
184
|
end
|
|
186
185
|
end
|
|
@@ -11,9 +11,10 @@ module GraphQL
|
|
|
11
11
|
@own_types = own_types
|
|
12
12
|
@directives = Set.new
|
|
13
13
|
@possible_types = {}
|
|
14
|
+
@possible_types_sets = {}
|
|
14
15
|
@types = {}
|
|
15
16
|
@union_memberships = {}
|
|
16
|
-
@references =
|
|
17
|
+
@references = {}.compare_by_identity
|
|
17
18
|
@arguments_with_default_values = []
|
|
18
19
|
add_type_and_traverse(new_types)
|
|
19
20
|
end
|
|
@@ -21,7 +22,8 @@ module GraphQL
|
|
|
21
22
|
private
|
|
22
23
|
|
|
23
24
|
def references_to(thing, from:)
|
|
24
|
-
@references[thing].
|
|
25
|
+
refs = @references[thing] ||= {}.compare_by_identity
|
|
26
|
+
refs[from] = true
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def get_type(name)
|
|
@@ -42,19 +44,25 @@ module GraphQL
|
|
|
42
44
|
|
|
43
45
|
def add_directives_from(owner)
|
|
44
46
|
if !(dir_instances = owner.directives).empty?
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
new_dirs = nil #: Array[singleton(GraphQL::Schema::Directive)]?
|
|
48
|
+
dir_instances.each do |dir|
|
|
49
|
+
dir_class = dir.class
|
|
50
|
+
if !@directives.include?(dir_class)
|
|
51
|
+
new_dirs ||= []
|
|
52
|
+
new_dirs << dir_class
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
if new_dirs
|
|
56
|
+
@directives.merge(new_dirs)
|
|
57
|
+
add_type_and_traverse(new_dirs)
|
|
58
|
+
end
|
|
48
59
|
end
|
|
49
60
|
end
|
|
50
61
|
|
|
51
62
|
def add_type_and_traverse(new_types)
|
|
52
63
|
late_types = []
|
|
53
|
-
path = []
|
|
54
64
|
new_types.each do |t|
|
|
55
|
-
|
|
56
|
-
add_type(t, owner: nil, late_types: late_types, path: path)
|
|
57
|
-
path.pop
|
|
65
|
+
add_type(t, owner: nil, late_types: late_types)
|
|
58
66
|
end
|
|
59
67
|
missed_late_types = 0
|
|
60
68
|
while (late_type_vals = late_types.shift)
|
|
@@ -64,13 +72,13 @@ module GraphQL
|
|
|
64
72
|
# Reset the counter, since we might succeed next go-round
|
|
65
73
|
missed_late_types = 0
|
|
66
74
|
update_type_owner(type_owner, type)
|
|
67
|
-
add_type(type, owner: type_owner, late_types: late_types
|
|
75
|
+
add_type(type, owner: type_owner, late_types: late_types)
|
|
68
76
|
elsif lt.is_a?(LateBoundType)
|
|
69
77
|
if (type = get_type(lt.name))
|
|
70
78
|
# Reset the counter, since we might succeed next go-round
|
|
71
79
|
missed_late_types = 0
|
|
72
80
|
update_type_owner(type_owner, type)
|
|
73
|
-
add_type(type, owner: type_owner, late_types: late_types
|
|
81
|
+
add_type(type, owner: type_owner, late_types: late_types)
|
|
74
82
|
else
|
|
75
83
|
missed_late_types += 1
|
|
76
84
|
# Add it back to the list, maybe we'll be able to resolve it later.
|
|
@@ -89,6 +97,15 @@ module GraphQL
|
|
|
89
97
|
nil
|
|
90
98
|
end
|
|
91
99
|
|
|
100
|
+
# The Set makes this fast for interfaces with many possible types. The Array keeps their order.
|
|
101
|
+
def add_possible_type(interface_type, object_type)
|
|
102
|
+
types_set = @possible_types_sets[interface_type] ||= Set.new
|
|
103
|
+
if types_set.add?(object_type)
|
|
104
|
+
pt = @possible_types[interface_type] ||= []
|
|
105
|
+
pt << object_type
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
92
109
|
def update_type_owner(owner, type)
|
|
93
110
|
case owner
|
|
94
111
|
when Module
|
|
@@ -114,9 +131,8 @@ module GraphQL
|
|
|
114
131
|
end
|
|
115
132
|
implementing_type.implements(*new_interfaces)
|
|
116
133
|
new_interfaces.each do |int|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
pt << owner
|
|
134
|
+
if owner.is_a?(Class)
|
|
135
|
+
add_possible_type(int, owner)
|
|
120
136
|
end
|
|
121
137
|
int.interfaces.each do |indirect_int|
|
|
122
138
|
if indirect_int.is_a?(LateBoundType)
|
|
@@ -125,10 +141,7 @@ module GraphQL
|
|
|
125
141
|
end
|
|
126
142
|
elsif owner.is_a?(Class)
|
|
127
143
|
# `implements` added this one, too, because `int` implements it
|
|
128
|
-
|
|
129
|
-
if !indirect_pt.include?(owner)
|
|
130
|
-
indirect_pt << owner
|
|
131
|
-
end
|
|
144
|
+
add_possible_type(indirect_int, owner)
|
|
132
145
|
end
|
|
133
146
|
end
|
|
134
147
|
end
|
|
@@ -161,7 +174,7 @@ module GraphQL
|
|
|
161
174
|
end
|
|
162
175
|
end
|
|
163
176
|
|
|
164
|
-
def add_type(type, owner:, late_types
|
|
177
|
+
def add_type(type, owner:, late_types:)
|
|
165
178
|
if type.is_a?(String) || type.is_a?(GraphQL::Schema::LateBoundType)
|
|
166
179
|
late_types << [owner, type]
|
|
167
180
|
return
|
|
@@ -181,9 +194,7 @@ module GraphQL
|
|
|
181
194
|
if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
|
|
182
195
|
references_to(arg_type, from: arg)
|
|
183
196
|
end
|
|
184
|
-
|
|
185
|
-
add_type(arg_type, owner: arg, late_types: late_types, path: path)
|
|
186
|
-
path.pop
|
|
197
|
+
add_type(arg_type, owner: arg, late_types: late_types)
|
|
187
198
|
if arg.default_value?
|
|
188
199
|
@arguments_with_default_values << arg
|
|
189
200
|
end
|
|
@@ -202,40 +213,45 @@ module GraphQL
|
|
|
202
213
|
if type.kind.fields?
|
|
203
214
|
type.all_field_definitions.each do |field|
|
|
204
215
|
field.ensure_loaded
|
|
205
|
-
name = field.graphql_name
|
|
206
216
|
field_type = field.type.unwrap
|
|
207
|
-
if
|
|
217
|
+
if field_type.is_a?(GraphQL::Schema::LateBoundType)
|
|
218
|
+
add_type(field_type, owner: field, late_types: late_types)
|
|
219
|
+
else
|
|
208
220
|
references_to(field_type, from: field)
|
|
221
|
+
if !@types[field_type.graphql_name].equal?(field_type)
|
|
222
|
+
add_type(field_type, owner: field, late_types: late_types)
|
|
223
|
+
end
|
|
209
224
|
end
|
|
210
|
-
path.push(name)
|
|
211
|
-
add_type(field_type, owner: field, late_types: late_types, path: path)
|
|
212
225
|
add_directives_from(field)
|
|
213
226
|
field.all_argument_definitions.each do |arg|
|
|
214
227
|
add_directives_from(arg)
|
|
215
228
|
arg_type = arg.type.unwrap
|
|
216
|
-
if
|
|
229
|
+
if arg_type.is_a?(GraphQL::Schema::LateBoundType)
|
|
230
|
+
add_type(arg_type, owner: arg, late_types: late_types)
|
|
231
|
+
else
|
|
217
232
|
references_to(arg_type, from: arg)
|
|
233
|
+
if !@types[arg_type.graphql_name].equal?(arg_type)
|
|
234
|
+
add_type(arg_type, owner: arg, late_types: late_types)
|
|
235
|
+
end
|
|
218
236
|
end
|
|
219
|
-
path.push(arg.graphql_name)
|
|
220
|
-
add_type(arg_type, owner: arg, late_types: late_types, path: path)
|
|
221
|
-
path.pop
|
|
222
237
|
if arg.default_value?
|
|
223
238
|
@arguments_with_default_values << arg
|
|
224
239
|
end
|
|
225
240
|
end
|
|
226
|
-
path.pop
|
|
227
241
|
end
|
|
228
242
|
end
|
|
229
243
|
if type.kind.input_object?
|
|
230
244
|
type.all_argument_definitions.each do |arg|
|
|
231
245
|
add_directives_from(arg)
|
|
232
246
|
arg_type = arg.type.unwrap
|
|
233
|
-
if
|
|
247
|
+
if arg_type.is_a?(GraphQL::Schema::LateBoundType)
|
|
248
|
+
add_type(arg_type, owner: arg, late_types: late_types)
|
|
249
|
+
else
|
|
234
250
|
references_to(arg_type, from: arg)
|
|
251
|
+
if !@types[arg_type.graphql_name].equal?(arg_type)
|
|
252
|
+
add_type(arg_type, owner: arg, late_types: late_types)
|
|
253
|
+
end
|
|
235
254
|
end
|
|
236
|
-
path.push(arg.graphql_name)
|
|
237
|
-
add_type(arg_type, owner: arg, late_types: late_types, path: path)
|
|
238
|
-
path.pop
|
|
239
255
|
if arg.default_value?
|
|
240
256
|
@arguments_with_default_values << arg
|
|
241
257
|
end
|
|
@@ -243,18 +259,14 @@ module GraphQL
|
|
|
243
259
|
end
|
|
244
260
|
if type.kind.union?
|
|
245
261
|
@possible_types[type] = type.all_possible_types
|
|
246
|
-
path.push("possible_types")
|
|
247
262
|
type.all_possible_types.each do |t|
|
|
248
|
-
add_type(t, owner: type, late_types: late_types
|
|
263
|
+
add_type(t, owner: type, late_types: late_types)
|
|
249
264
|
end
|
|
250
|
-
path.pop
|
|
251
265
|
end
|
|
252
266
|
if type.kind.interface?
|
|
253
|
-
path.push("orphan_types")
|
|
254
267
|
type.orphan_types.each do |t|
|
|
255
|
-
add_type(t, owner: type, late_types: late_types
|
|
268
|
+
add_type(t, owner: type, late_types: late_types)
|
|
256
269
|
end
|
|
257
|
-
path.pop
|
|
258
270
|
end
|
|
259
271
|
if type.kind.object?
|
|
260
272
|
possible_types_for_this_name = @possible_types[type] ||= []
|
|
@@ -262,26 +274,21 @@ module GraphQL
|
|
|
262
274
|
end
|
|
263
275
|
|
|
264
276
|
if type.kind.object? || type.kind.interface?
|
|
265
|
-
path.push("implements")
|
|
266
277
|
type.interface_type_memberships.each do |interface_type_membership|
|
|
267
278
|
case interface_type_membership
|
|
268
279
|
when Schema::TypeMembership
|
|
269
280
|
interface_type = interface_type_membership.abstract_type
|
|
270
281
|
# We can get these now; we'll have to get late-bound types later
|
|
271
282
|
if interface_type.is_a?(Module) && type.is_a?(Class)
|
|
272
|
-
|
|
273
|
-
if !implementers.include?(type)
|
|
274
|
-
implementers << type
|
|
275
|
-
end
|
|
283
|
+
add_possible_type(interface_type, type)
|
|
276
284
|
end
|
|
277
285
|
when String, Schema::LateBoundType
|
|
278
286
|
interface_type = interface_type_membership
|
|
279
287
|
else
|
|
280
288
|
raise ArgumentError, "Invariant: unexpected type membership for #{type.graphql_name}: #{interface_type_membership.class} (#{interface_type_membership.inspect})"
|
|
281
289
|
end
|
|
282
|
-
add_type(interface_type, owner: type, late_types: late_types
|
|
290
|
+
add_type(interface_type, owner: type, late_types: late_types)
|
|
283
291
|
end
|
|
284
|
-
path.pop
|
|
285
292
|
end
|
|
286
293
|
|
|
287
294
|
if type.kind.enum?
|
|
@@ -515,7 +515,7 @@ module GraphQL
|
|
|
515
515
|
coerced_value = begin
|
|
516
516
|
type.coerce_input(value, context)
|
|
517
517
|
rescue StandardError => err
|
|
518
|
-
|
|
518
|
+
handle_or_reraise_arg_error(context, err)
|
|
519
519
|
end
|
|
520
520
|
|
|
521
521
|
# If this isn't lazy, then the block returns eagerly and assigns the result here
|
|
@@ -525,14 +525,14 @@ module GraphQL
|
|
|
525
525
|
prepared_value = begin
|
|
526
526
|
prepare_value(parent_object, resolved_coerced_value, context: context)
|
|
527
527
|
rescue StandardError => err
|
|
528
|
-
|
|
528
|
+
handle_or_reraise_arg_error(context, err)
|
|
529
529
|
end
|
|
530
530
|
|
|
531
531
|
if loads && !from_resolver?
|
|
532
532
|
loaded_value = begin
|
|
533
533
|
load_and_authorize_value(owner, prepared_value, context)
|
|
534
534
|
rescue StandardError => err
|
|
535
|
-
|
|
535
|
+
handle_or_reraise_arg_error(context, err)
|
|
536
536
|
end
|
|
537
537
|
end
|
|
538
538
|
|
|
@@ -549,6 +549,17 @@ module GraphQL
|
|
|
549
549
|
end
|
|
550
550
|
end
|
|
551
551
|
|
|
552
|
+
# When a `rescue_from` handler *returns* an error object instead of raising,
|
|
553
|
+
# it must not be used as this argument's value — raise it so that
|
|
554
|
+
# `coerce_arguments` short-circuits resolution with it.
|
|
555
|
+
def handle_or_reraise_arg_error(context, err) # :nodoc:
|
|
556
|
+
handled_err = context.schema.handle_or_reraise(context, err)
|
|
557
|
+
if handled_err.is_a?(GraphQL::ExecutionError) || handled_err.is_a?(GraphQL::UnauthorizedError)
|
|
558
|
+
raise handled_err
|
|
559
|
+
end
|
|
560
|
+
handled_err
|
|
561
|
+
end
|
|
562
|
+
|
|
552
563
|
def load_and_authorize_value(load_method_owner, coerced_value, context)
|
|
553
564
|
if coerced_value.nil?
|
|
554
565
|
return nil
|