graphql 1.12.17 → 1.12.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql/analysis/ast/field_usage.rb +1 -1
- data/lib/graphql/dataloader/source.rb +2 -0
- data/lib/graphql/dataloader.rb +13 -0
- data/lib/graphql/deprecated_dsl.rb +11 -3
- data/lib/graphql/schema/argument.rb +55 -26
- data/lib/graphql/schema/field.rb +14 -4
- data/lib/graphql/schema/input_object.rb +1 -5
- data/lib/graphql/schema/member/has_arguments.rb +83 -43
- data/lib/graphql/schema/resolver.rb +10 -57
- data/lib/graphql/schema/subscription.rb +4 -4
- data/lib/graphql/schema.rb +18 -5
- data/lib/graphql/static_validation/base_visitor.rb +3 -0
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +4 -4
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
- data/lib/graphql/static_validation/validation_context.rb +6 -1
- data/lib/graphql/static_validation/validator.rb +15 -12
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +7 -1
- data/lib/graphql/subscriptions/event.rb +2 -1
- data/lib/graphql/subscriptions/serialize.rb +1 -1
- data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
- 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: 5d1cfa93dc97adf13e461a45a00899f5ecc3e27d66c54c33d1a319b027665b78
|
4
|
+
data.tar.gz: 5d8cf2b669619dd15058400e15342ff225ba02716c5d8c9959c74260d4f51b32
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7ff04a88e0ae883c382aa6559b52c38dbbc7d513537aaf506e89dd7880c227b6b032f7329c1c9e7b0fb9692b3e0e6fdbaac9df9fc82e536fb1e12b9cff2f65c
|
7
|
+
data.tar.gz: 3276674be3f8b0eb05d5c8904cad3ef20697bf5b9efcb133bd7c283d7bd704d52cd75f87a46f6c1fd988faf6c439c796d186280f4e397f8dc5184ae7d17c333b
|
@@ -37,7 +37,7 @@ module GraphQL
|
|
37
37
|
|
38
38
|
if argument.definition.type.kind.input_object?
|
39
39
|
extract_deprecated_arguments(argument.value.arguments.argument_values)
|
40
|
-
elsif argument.definition.type.list?
|
40
|
+
elsif argument.definition.type.list? && !argument.value.nil?
|
41
41
|
argument
|
42
42
|
.value
|
43
43
|
.select { |value| value.respond_to?(:arguments) }
|
data/lib/graphql/dataloader.rb
CHANGED
@@ -90,6 +90,16 @@ module GraphQL
|
|
90
90
|
# Use a self-contained queue for the work in the block.
|
91
91
|
def run_isolated
|
92
92
|
prev_queue = @pending_jobs
|
93
|
+
prev_pending_keys = {}
|
94
|
+
@source_cache.each do |source_class, batched_sources|
|
95
|
+
batched_sources.each do |batch_args, batched_source_instance|
|
96
|
+
if batched_source_instance.pending?
|
97
|
+
prev_pending_keys[batched_source_instance] = batched_source_instance.pending_keys.dup
|
98
|
+
batched_source_instance.pending_keys.clear
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
93
103
|
@pending_jobs = []
|
94
104
|
res = nil
|
95
105
|
# Make sure the block is inside a Fiber, so it can `Fiber.yield`
|
@@ -100,6 +110,9 @@ module GraphQL
|
|
100
110
|
res
|
101
111
|
ensure
|
102
112
|
@pending_jobs = prev_queue
|
113
|
+
prev_pending_keys.each do |source_instance, pending_keys|
|
114
|
+
source_instance.pending_keys.concat(pending_keys)
|
115
|
+
end
|
103
116
|
end
|
104
117
|
|
105
118
|
# @api private Move along, move along
|
@@ -38,9 +38,17 @@ module GraphQL
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
if defined?(::Refinement) && Refinement.private_method_defined?(:import_methods)
|
42
|
+
TYPE_CLASSES.each do |type_class|
|
43
|
+
refine type_class.singleton_class do
|
44
|
+
import_methods Methods
|
45
|
+
end
|
46
|
+
end
|
47
|
+
else
|
48
|
+
TYPE_CLASSES.each do |type_class|
|
49
|
+
refine type_class.singleton_class do
|
50
|
+
include Methods
|
51
|
+
end
|
44
52
|
end
|
45
53
|
end
|
46
54
|
end
|
@@ -260,38 +260,67 @@ module GraphQL
|
|
260
260
|
type.coerce_input(value, context)
|
261
261
|
end
|
262
262
|
|
263
|
-
#
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
context.
|
268
|
-
|
269
|
-
context.query.with_error_handling do
|
270
|
-
owner.load_application_object(self, loads, coerced_value, context)
|
263
|
+
# If this isn't lazy, then the block returns eagerly and assigns the result here
|
264
|
+
# If it _is_ lazy, then we write the lazy to the hash, then update it later
|
265
|
+
argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
|
266
|
+
if loads && !from_resolver?
|
267
|
+
loaded_value = context.query.with_error_handling do
|
268
|
+
load_and_authorize_value(owner, coerced_value, context)
|
271
269
|
end
|
272
270
|
end
|
273
|
-
end
|
274
271
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
272
|
+
maybe_loaded_value = loaded_value || resolved_coerced_value
|
273
|
+
context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
|
274
|
+
owner.validate_directive_argument(self, resolved_loaded_value)
|
275
|
+
prepared_value = context.schema.error_handler.with_error_handling(context) do
|
276
|
+
prepare_value(parent_object, resolved_loaded_value, context: context)
|
277
|
+
end
|
280
278
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
279
|
+
# TODO code smell to access such a deeply-nested constant in a distant module
|
280
|
+
argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
|
281
|
+
value: prepared_value,
|
282
|
+
definition: self,
|
283
|
+
default_used: default_used,
|
284
|
+
)
|
287
285
|
end
|
286
|
+
end
|
287
|
+
end
|
288
288
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
289
|
+
def load_and_authorize_value(load_method_owner, coerced_value, context)
|
290
|
+
if coerced_value.nil?
|
291
|
+
return nil
|
292
|
+
end
|
293
|
+
arg_load_method = "load_#{keyword}"
|
294
|
+
if load_method_owner.respond_to?(arg_load_method)
|
295
|
+
custom_loaded_value = if load_method_owner.is_a?(Class)
|
296
|
+
load_method_owner.public_send(arg_load_method, coerced_value, context)
|
297
|
+
else
|
298
|
+
load_method_owner.public_send(arg_load_method, coerced_value)
|
299
|
+
end
|
300
|
+
context.schema.after_lazy(custom_loaded_value) do |custom_value|
|
301
|
+
if loads
|
302
|
+
if type.list?
|
303
|
+
loaded_values = custom_value.each_with_index.map { |custom_val, idx|
|
304
|
+
id = coerced_value[idx]
|
305
|
+
load_method_owner.authorize_application_object(self, id, context, custom_val)
|
306
|
+
}
|
307
|
+
context.schema.after_any_lazies(loaded_values, &:itself)
|
308
|
+
else
|
309
|
+
load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value)
|
310
|
+
end
|
311
|
+
else
|
312
|
+
custom_value
|
313
|
+
end
|
314
|
+
end
|
315
|
+
elsif loads
|
316
|
+
if type.list?
|
317
|
+
loaded_values = coerced_value.map { |val| load_method_owner.load_and_authorize_application_object(self, val, context) }
|
318
|
+
context.schema.after_any_lazies(loaded_values, &:itself)
|
319
|
+
else
|
320
|
+
load_method_owner.load_and_authorize_application_object(self, coerced_value, context)
|
321
|
+
end
|
322
|
+
else
|
323
|
+
coerced_value
|
295
324
|
end
|
296
325
|
end
|
297
326
|
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -122,6 +122,9 @@ module GraphQL
|
|
122
122
|
else
|
123
123
|
kwargs[:type] = type
|
124
124
|
end
|
125
|
+
if type.is_a?(Class) && type < GraphQL::Schema::Mutation
|
126
|
+
raise ArgumentError, "Use `field #{name.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead"
|
127
|
+
end
|
125
128
|
end
|
126
129
|
new(**kwargs, &block)
|
127
130
|
end
|
@@ -510,6 +513,7 @@ module GraphQL
|
|
510
513
|
field_defn
|
511
514
|
end
|
512
515
|
|
516
|
+
class MissingReturnTypeError < GraphQL::Error; end
|
513
517
|
attr_writer :type
|
514
518
|
|
515
519
|
def type
|
@@ -517,14 +521,21 @@ module GraphQL
|
|
517
521
|
Member::BuildType.parse_type(@function.type, null: false)
|
518
522
|
elsif @field
|
519
523
|
Member::BuildType.parse_type(@field.type, null: false)
|
524
|
+
elsif @return_type_expr.nil?
|
525
|
+
# Not enough info to determine type
|
526
|
+
message = "Can't determine the return type for #{self.path}"
|
527
|
+
if @resolver_class
|
528
|
+
message += " (it has `resolver: #{@resolver_class}`, consider configuration a `type ...` for that class)"
|
529
|
+
end
|
530
|
+
raise MissingReturnTypeError, message
|
520
531
|
else
|
521
532
|
Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
|
522
533
|
end
|
523
|
-
rescue GraphQL::Schema::InvalidDocumentError => err
|
534
|
+
rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
|
524
535
|
# Let this propagate up
|
525
536
|
raise err
|
526
537
|
rescue StandardError => err
|
527
|
-
raise
|
538
|
+
raise MissingReturnTypeError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace
|
528
539
|
end
|
529
540
|
|
530
541
|
def visible?(context)
|
@@ -608,8 +619,7 @@ module GraphQL
|
|
608
619
|
if is_authorized
|
609
620
|
public_send_field(object, args, ctx)
|
610
621
|
else
|
611
|
-
|
612
|
-
ctx.schema.unauthorized_field(err)
|
622
|
+
raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
|
613
623
|
end
|
614
624
|
end
|
615
625
|
rescue GraphQL::UnauthorizedFieldError => err
|
@@ -40,11 +40,7 @@ module GraphQL
|
|
40
40
|
# With the interpreter, it's done during `coerce_arguments`
|
41
41
|
if loads && !arg_defn.from_resolver? && !context.interpreter?
|
42
42
|
value = @ruby_style_hash[ruby_kwargs_key]
|
43
|
-
loaded_value =
|
44
|
-
value.map { |val| load_application_object(arg_defn, loads, val, context) }
|
45
|
-
else
|
46
|
-
load_application_object(arg_defn, loads, value, context)
|
47
|
-
end
|
43
|
+
loaded_value = arg_defn.load_and_authorize_value(self, value, context)
|
48
44
|
maybe_lazies << context.schema.after_lazy(loaded_value) do |loaded_value|
|
49
45
|
overwrite_argument(ruby_kwargs_key, loaded_value)
|
50
46
|
end
|
@@ -37,6 +37,40 @@ module GraphQL
|
|
37
37
|
end
|
38
38
|
arg_defn = self.argument_class.new(*args, **kwargs, &block)
|
39
39
|
add_argument(arg_defn)
|
40
|
+
|
41
|
+
if self.is_a?(Class) && !method_defined?(:"load_#{arg_defn.keyword}")
|
42
|
+
method_owner = if self < GraphQL::Schema::InputObject || self < GraphQL::Schema::Directive
|
43
|
+
"self."
|
44
|
+
elsif self < GraphQL::Schema::Resolver
|
45
|
+
""
|
46
|
+
else
|
47
|
+
raise "Unexpected argument owner: #{self}"
|
48
|
+
end
|
49
|
+
if loads && arg_defn.type.list?
|
50
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
51
|
+
def #{method_owner}load_#{arg_defn.keyword}(values, context = nil)
|
52
|
+
argument = get_argument("#{arg_defn.graphql_name}")
|
53
|
+
(context || self.context).schema.after_lazy(values) do |values2|
|
54
|
+
GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, value, context || self.context) })
|
55
|
+
end
|
56
|
+
end
|
57
|
+
RUBY
|
58
|
+
elsif loads
|
59
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
60
|
+
def #{method_owner}load_#{arg_defn.keyword}(value, context = nil)
|
61
|
+
argument = get_argument("#{arg_defn.graphql_name}")
|
62
|
+
load_application_object(argument, value, context || self.context)
|
63
|
+
end
|
64
|
+
RUBY
|
65
|
+
else
|
66
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
67
|
+
def #{method_owner}load_#{arg_defn.keyword}(value, _context = nil)
|
68
|
+
value
|
69
|
+
end
|
70
|
+
RUBY
|
71
|
+
end
|
72
|
+
end
|
73
|
+
arg_defn
|
40
74
|
end
|
41
75
|
|
42
76
|
# Register this argument with the class.
|
@@ -94,54 +128,52 @@ module GraphQL
|
|
94
128
|
arg_defns = self.arguments
|
95
129
|
total_args_count = arg_defns.size
|
96
130
|
|
97
|
-
|
98
|
-
|
99
|
-
if
|
100
|
-
|
101
|
-
|
131
|
+
finished_args = nil
|
132
|
+
prepare_finished_args = -> {
|
133
|
+
if total_args_count == 0
|
134
|
+
finished_args = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
135
|
+
if block_given?
|
136
|
+
block.call(finished_args)
|
137
|
+
end
|
102
138
|
else
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
begin
|
113
|
-
arg_defn.coerce_into_values(parent_object, values, context, argument_values)
|
114
|
-
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
115
|
-
raised_error = true
|
116
|
-
if block_given?
|
117
|
-
block.call(err)
|
118
|
-
else
|
139
|
+
argument_values = {}
|
140
|
+
resolved_args_count = 0
|
141
|
+
raised_error = false
|
142
|
+
arg_defns.each do |arg_name, arg_defn|
|
143
|
+
context.dataloader.append_job do
|
144
|
+
begin
|
145
|
+
arg_defn.coerce_into_values(parent_object, values, context, argument_values)
|
146
|
+
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
147
|
+
raised_error = true
|
119
148
|
finished_args = err
|
149
|
+
if block_given?
|
150
|
+
block.call(finished_args)
|
151
|
+
end
|
120
152
|
end
|
121
|
-
end
|
122
|
-
|
123
|
-
resolved_args_count += 1
|
124
|
-
if resolved_args_count == total_args_count && !raised_error
|
125
|
-
finished_args = context.schema.after_any_lazies(argument_values.values) {
|
126
|
-
GraphQL::Execution::Interpreter::Arguments.new(
|
127
|
-
argument_values: argument_values,
|
128
|
-
)
|
129
|
-
}
|
130
153
|
|
131
|
-
|
132
|
-
|
154
|
+
resolved_args_count += 1
|
155
|
+
if resolved_args_count == total_args_count && !raised_error
|
156
|
+
finished_args = context.schema.after_any_lazies(argument_values.values) {
|
157
|
+
GraphQL::Execution::Interpreter::Arguments.new(
|
158
|
+
argument_values: argument_values,
|
159
|
+
)
|
160
|
+
}
|
161
|
+
if block_given?
|
162
|
+
block.call(finished_args)
|
163
|
+
end
|
133
164
|
end
|
134
165
|
end
|
135
166
|
end
|
136
167
|
end
|
168
|
+
}
|
137
169
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
170
|
+
if block_given?
|
171
|
+
prepare_finished_args.call
|
172
|
+
nil
|
173
|
+
else
|
174
|
+
# This API returns eagerly, gotta run it now
|
175
|
+
context.dataloader.run_isolated(&prepare_finished_args)
|
176
|
+
finished_args
|
145
177
|
end
|
146
178
|
end
|
147
179
|
|
@@ -186,12 +218,20 @@ module GraphQL
|
|
186
218
|
context.schema.object_from_id(id, context)
|
187
219
|
end
|
188
220
|
|
189
|
-
def load_application_object(argument,
|
221
|
+
def load_application_object(argument, id, context)
|
190
222
|
# See if any object can be found for this ID
|
191
223
|
if id.nil?
|
192
224
|
return nil
|
193
225
|
end
|
194
|
-
|
226
|
+
object_from_id(argument.loads, id, context)
|
227
|
+
end
|
228
|
+
|
229
|
+
def load_and_authorize_application_object(argument, id, context)
|
230
|
+
loaded_application_object = load_application_object(argument, id, context)
|
231
|
+
authorize_application_object(argument, id, context, loaded_application_object)
|
232
|
+
end
|
233
|
+
|
234
|
+
def authorize_application_object(argument, id, context, loaded_application_object)
|
195
235
|
context.schema.after_lazy(loaded_application_object) do |application_object|
|
196
236
|
if application_object.nil?
|
197
237
|
err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
|
@@ -199,9 +239,9 @@ module GraphQL
|
|
199
239
|
end
|
200
240
|
# Double-check that the located object is actually of this type
|
201
241
|
# (Don't want to allow arbitrary access to objects this way)
|
202
|
-
resolved_application_object_type = context.schema.resolve_type(
|
242
|
+
resolved_application_object_type = context.schema.resolve_type(argument.loads, application_object, context)
|
203
243
|
context.schema.after_lazy(resolved_application_object_type) do |application_object_type|
|
204
|
-
possible_object_types = context.warden.possible_types(
|
244
|
+
possible_object_types = context.warden.possible_types(argument.loads)
|
205
245
|
if !possible_object_types.include?(application_object_type)
|
206
246
|
err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
|
207
247
|
load_application_object_failed(err)
|
@@ -28,7 +28,7 @@ module GraphQL
|
|
28
28
|
include Schema::Member::HasPath
|
29
29
|
extend Schema::Member::HasPath
|
30
30
|
|
31
|
-
# @param object [Object]
|
31
|
+
# @param object [Object] The application object that this field is being resolved on
|
32
32
|
# @param context [GraphQL::Query::Context]
|
33
33
|
# @param field [GraphQL::Schema::Field]
|
34
34
|
def initialize(object:, context:, field:)
|
@@ -40,7 +40,6 @@ module GraphQL
|
|
40
40
|
self.class.arguments.each do |name, arg|
|
41
41
|
@arguments_by_keyword[arg.keyword] = arg
|
42
42
|
end
|
43
|
-
@arguments_loads_as_type = self.class.arguments_loads_as_type
|
44
43
|
@prepared_arguments = nil
|
45
44
|
end
|
46
45
|
|
@@ -110,7 +109,7 @@ module GraphQL
|
|
110
109
|
public_send(self.class.resolve_method)
|
111
110
|
end
|
112
111
|
else
|
113
|
-
|
112
|
+
raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
|
114
113
|
end
|
115
114
|
end
|
116
115
|
end
|
@@ -170,18 +169,14 @@ module GraphQL
|
|
170
169
|
args.each do |key, value|
|
171
170
|
arg_defn = @arguments_by_keyword[key]
|
172
171
|
if arg_defn
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
if context.schema.lazy?(prepped_value)
|
178
|
-
prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
|
179
|
-
prepared_args[key] = finished_prepped_value
|
180
|
-
end
|
172
|
+
prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context)
|
173
|
+
if context.schema.lazy?(prepped_value)
|
174
|
+
prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
|
175
|
+
prepared_args[key] = finished_prepped_value
|
181
176
|
end
|
182
177
|
end
|
183
178
|
else
|
184
|
-
#
|
179
|
+
# these are `extras:`
|
185
180
|
prepared_args[key] = value
|
186
181
|
end
|
187
182
|
end
|
@@ -194,8 +189,8 @@ module GraphQL
|
|
194
189
|
end
|
195
190
|
end
|
196
191
|
|
197
|
-
def
|
198
|
-
|
192
|
+
def get_argument(name)
|
193
|
+
self.class.get_argument(name)
|
199
194
|
end
|
200
195
|
|
201
196
|
class << self
|
@@ -334,47 +329,9 @@ module GraphQL
|
|
334
329
|
# also add some preparation hook methods which will be used for this argument
|
335
330
|
# @see {GraphQL::Schema::Argument#initialize} for the signature
|
336
331
|
def argument(*args, **kwargs, &block)
|
337
|
-
loads = kwargs[:loads]
|
338
332
|
# Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation
|
339
333
|
# so that we can support `#load_{x}` methods below.
|
340
|
-
|
341
|
-
own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
|
342
|
-
|
343
|
-
if !method_defined?(:"load_#{arg_defn.keyword}")
|
344
|
-
if loads && arg_defn.type.list?
|
345
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
346
|
-
def load_#{arg_defn.keyword}(values)
|
347
|
-
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
348
|
-
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
349
|
-
context.schema.after_lazy(values) do |values2|
|
350
|
-
GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value, context) })
|
351
|
-
end
|
352
|
-
end
|
353
|
-
RUBY
|
354
|
-
elsif loads
|
355
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
356
|
-
def load_#{arg_defn.keyword}(value)
|
357
|
-
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
358
|
-
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
359
|
-
load_application_object(argument, lookup_as_type, value, context)
|
360
|
-
end
|
361
|
-
RUBY
|
362
|
-
else
|
363
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
364
|
-
def load_#{arg_defn.keyword}(value)
|
365
|
-
value
|
366
|
-
end
|
367
|
-
RUBY
|
368
|
-
end
|
369
|
-
end
|
370
|
-
|
371
|
-
arg_defn
|
372
|
-
end
|
373
|
-
|
374
|
-
# @api private
|
375
|
-
def arguments_loads_as_type
|
376
|
-
inherited_lookups = superclass.respond_to?(:arguments_loads_as_type) ? superclass.arguments_loads_as_type : {}
|
377
|
-
inherited_lookups.merge(own_arguments_loads_as_type)
|
334
|
+
super(*args, from_resolver: true, **kwargs)
|
378
335
|
end
|
379
336
|
|
380
337
|
# Registers new extension
|
@@ -410,10 +367,6 @@ module GraphQL
|
|
410
367
|
def own_extensions
|
411
368
|
@own_extensions
|
412
369
|
end
|
413
|
-
|
414
|
-
def own_arguments_loads_as_type
|
415
|
-
@own_arguments_loads_as_type ||= {}
|
416
|
-
end
|
417
370
|
end
|
418
371
|
end
|
419
372
|
end
|
@@ -14,7 +14,7 @@ module GraphQL
|
|
14
14
|
class Subscription < GraphQL::Schema::Resolver
|
15
15
|
extend GraphQL::Schema::Resolver::HasPayloadType
|
16
16
|
extend GraphQL::Schema::Member::HasFields
|
17
|
-
|
17
|
+
NO_UPDATE = :no_update
|
18
18
|
# The generated payload type is required; If there's no payload,
|
19
19
|
# propagate null.
|
20
20
|
null false
|
@@ -68,7 +68,7 @@ module GraphQL
|
|
68
68
|
# Wrap the user-provided `#update` hook
|
69
69
|
def resolve_update(**args)
|
70
70
|
ret_val = args.any? ? update(**args) : update
|
71
|
-
if ret_val ==
|
71
|
+
if ret_val == NO_UPDATE
|
72
72
|
context.namespace(:subscriptions)[:no_update] = true
|
73
73
|
context.skip
|
74
74
|
else
|
@@ -77,7 +77,7 @@ module GraphQL
|
|
77
77
|
end
|
78
78
|
|
79
79
|
# The default implementation returns the root object.
|
80
|
-
# Override it to return
|
80
|
+
# Override it to return {NO_UPDATE} if you want to
|
81
81
|
# skip updates sometimes. Or override it to return a different object.
|
82
82
|
def update(args = {})
|
83
83
|
object
|
@@ -122,7 +122,7 @@ module GraphQL
|
|
122
122
|
# In that implementation, only `.trigger` calls with _exact matches_ result in updates to subscribers.
|
123
123
|
#
|
124
124
|
# To implement a filtered stream-type subscription flow, override this method to return a string with field name and subscription scope.
|
125
|
-
# Then, implement {#update} to compare its arguments to the current `object` and return
|
125
|
+
# Then, implement {#update} to compare its arguments to the current `object` and return {NO_UPDATE} when an
|
126
126
|
# update should be filtered out.
|
127
127
|
#
|
128
128
|
# @see {#update} for how to skip updates when an event comes with a matching topic.
|
data/lib/graphql/schema.rb
CHANGED
@@ -161,7 +161,7 @@ module GraphQL
|
|
161
161
|
|
162
162
|
accepts_definitions \
|
163
163
|
:query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
|
164
|
-
:validate_timeout, :max_depth, :max_complexity, :default_max_page_size,
|
164
|
+
:validate_timeout, :validate_max_errors, :max_depth, :max_complexity, :default_max_page_size,
|
165
165
|
:orphan_types, :resolve_type, :type_error, :parse_error,
|
166
166
|
:error_bubbling,
|
167
167
|
:raise_definition_error,
|
@@ -200,7 +200,7 @@ module GraphQL
|
|
200
200
|
attr_accessor \
|
201
201
|
:query, :mutation, :subscription,
|
202
202
|
:query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
|
203
|
-
:validate_timeout, :max_depth, :max_complexity, :default_max_page_size,
|
203
|
+
:validate_timeout, :validate_max_errors, :max_depth, :max_complexity, :default_max_page_size,
|
204
204
|
:orphan_types, :directives,
|
205
205
|
:query_analyzers, :multiplex_analyzers, :instrumenters, :lazy_methods,
|
206
206
|
:cursor_encoder,
|
@@ -552,7 +552,7 @@ module GraphQL
|
|
552
552
|
end
|
553
553
|
end
|
554
554
|
|
555
|
-
# @see [GraphQL::Schema::Warden]
|
555
|
+
# @see [GraphQL::Schema::Warden] Restricted access to root types
|
556
556
|
# @return [GraphQL::ObjectType, nil]
|
557
557
|
def root_type_for_operation(operation)
|
558
558
|
case operation
|
@@ -934,6 +934,7 @@ module GraphQL
|
|
934
934
|
schema_defn.mutation = mutation && mutation.graphql_definition
|
935
935
|
schema_defn.subscription = subscription && subscription.graphql_definition
|
936
936
|
schema_defn.validate_timeout = validate_timeout
|
937
|
+
schema_defn.validate_max_errors = validate_max_errors
|
937
938
|
schema_defn.max_complexity = max_complexity
|
938
939
|
schema_defn.error_bubbling = error_bubbling
|
939
940
|
schema_defn.max_depth = max_depth
|
@@ -1073,7 +1074,7 @@ module GraphQL
|
|
1073
1074
|
end
|
1074
1075
|
end
|
1075
1076
|
|
1076
|
-
# @see [GraphQL::Schema::Warden]
|
1077
|
+
# @see [GraphQL::Schema::Warden] Restricted access to root types
|
1077
1078
|
# @return [GraphQL::ObjectType, nil]
|
1078
1079
|
def root_type_for_operation(operation)
|
1079
1080
|
case operation
|
@@ -1290,10 +1291,22 @@ module GraphQL
|
|
1290
1291
|
validator_opts = { schema: self }
|
1291
1292
|
rules && (validator_opts[:rules] = rules)
|
1292
1293
|
validator = GraphQL::StaticValidation::Validator.new(**validator_opts)
|
1293
|
-
res = validator.validate(query, timeout: validate_timeout)
|
1294
|
+
res = validator.validate(query, timeout: validate_timeout, max_errors: validate_max_errors)
|
1294
1295
|
res[:errors]
|
1295
1296
|
end
|
1296
1297
|
|
1298
|
+
attr_writer :validate_max_errors
|
1299
|
+
|
1300
|
+
def validate_max_errors(new_validate_max_errors = nil)
|
1301
|
+
if new_validate_max_errors
|
1302
|
+
@validate_max_errors = new_validate_max_errors
|
1303
|
+
elsif defined?(@validate_max_errors)
|
1304
|
+
@validate_max_errors
|
1305
|
+
else
|
1306
|
+
find_inherited_value(:validate_max_errors)
|
1307
|
+
end
|
1308
|
+
end
|
1309
|
+
|
1297
1310
|
attr_writer :max_complexity
|
1298
1311
|
|
1299
1312
|
def max_complexity(max_complexity = nil)
|
@@ -193,26 +193,26 @@ module GraphQL
|
|
193
193
|
if node1.name != node2.name
|
194
194
|
errored_nodes = [node1.name, node2.name].sort.join(" or ")
|
195
195
|
msg = "Field '#{response_key}' has a field conflict: #{errored_nodes}?"
|
196
|
-
|
196
|
+
add_error(GraphQL::StaticValidation::FieldsWillMergeError.new(
|
197
197
|
msg,
|
198
198
|
nodes: [node1, node2],
|
199
199
|
path: [],
|
200
200
|
field_name: response_key,
|
201
201
|
conflicts: errored_nodes
|
202
|
-
)
|
202
|
+
))
|
203
203
|
end
|
204
204
|
|
205
205
|
if !same_arguments?(node1, node2)
|
206
206
|
args = [serialize_field_args(node1), serialize_field_args(node2)]
|
207
207
|
conflicts = args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ")
|
208
208
|
msg = "Field '#{response_key}' has an argument conflict: #{conflicts}?"
|
209
|
-
|
209
|
+
add_error(GraphQL::StaticValidation::FieldsWillMergeError.new(
|
210
210
|
msg,
|
211
211
|
nodes: [node1, node2],
|
212
212
|
path: [],
|
213
213
|
field_name: response_key,
|
214
214
|
conflicts: conflicts
|
215
|
-
)
|
215
|
+
))
|
216
216
|
end
|
217
217
|
end
|
218
218
|
|
@@ -7,12 +7,12 @@ module GraphQL
|
|
7
7
|
dependency_map = context.dependencies
|
8
8
|
dependency_map.cyclical_definitions.each do |defn|
|
9
9
|
if defn.node.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
|
10
|
-
|
10
|
+
add_error(GraphQL::StaticValidation::FragmentsAreFiniteError.new(
|
11
11
|
"Fragment #{defn.name} contains an infinite loop",
|
12
12
|
nodes: defn.node,
|
13
13
|
path: defn.path,
|
14
14
|
name: defn.name
|
15
|
-
)
|
15
|
+
))
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -19,10 +19,11 @@ module GraphQL
|
|
19
19
|
|
20
20
|
def_delegators :@query, :schema, :document, :fragments, :operations, :warden
|
21
21
|
|
22
|
-
def initialize(query, visitor_class)
|
22
|
+
def initialize(query, visitor_class, max_errors)
|
23
23
|
@query = query
|
24
24
|
@literal_validator = LiteralValidator.new(context: query.context)
|
25
25
|
@errors = []
|
26
|
+
@max_errors = max_errors || Float::INFINITY
|
26
27
|
@on_dependency_resolve_handlers = []
|
27
28
|
@visitor = visitor_class.new(document, self)
|
28
29
|
end
|
@@ -38,6 +39,10 @@ module GraphQL
|
|
38
39
|
def validate_literal(ast_value, type)
|
39
40
|
@literal_validator.validate(ast_value, type)
|
40
41
|
end
|
42
|
+
|
43
|
+
def too_many_errors?
|
44
|
+
@errors.length >= @max_errors
|
45
|
+
end
|
41
46
|
end
|
42
47
|
end
|
43
48
|
end
|
@@ -24,8 +24,9 @@ module GraphQL
|
|
24
24
|
# @param query [GraphQL::Query]
|
25
25
|
# @param validate [Boolean]
|
26
26
|
# @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds.
|
27
|
+
# @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit.
|
27
28
|
# @return [Array<Hash>]
|
28
|
-
def validate(query, validate: true, timeout: nil)
|
29
|
+
def validate(query, validate: true, timeout: nil, max_errors: nil)
|
29
30
|
query.trace("validate", { validate: validate, query: query }) do
|
30
31
|
can_skip_rewrite = query.context.interpreter? && query.schema.using_ast_analysis? && query.schema.is_a?(Class)
|
31
32
|
errors = if validate == false && can_skip_rewrite
|
@@ -34,25 +35,27 @@ module GraphQL
|
|
34
35
|
rules_to_use = validate ? @rules : []
|
35
36
|
visitor_class = BaseVisitor.including_rules(rules_to_use, rewrite: !can_skip_rewrite)
|
36
37
|
|
37
|
-
context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class)
|
38
|
+
context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class, max_errors)
|
38
39
|
|
39
40
|
begin
|
40
41
|
# CAUTION: Usage of the timeout module makes the assumption that validation rules are stateless Ruby code that requires no cleanup if process was interrupted. This means no blocking IO calls, native gems, locks, or `rescue` clauses that must be reached.
|
41
42
|
# A timeout value of 0 or nil will execute the block without any timeout.
|
42
43
|
Timeout::timeout(timeout) do
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
legacy_rules
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
44
|
+
catch(:too_many_validation_errors) do
|
45
|
+
# Attach legacy-style rules.
|
46
|
+
# Only loop through rules if it has legacy-style rules
|
47
|
+
unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
|
48
|
+
legacy_rules.each do |rule_class_or_module|
|
49
|
+
if rule_class_or_module.method_defined?(:validate)
|
50
|
+
GraphQL::Deprecation.warn "Legacy validator rules will be removed from GraphQL-Ruby 2.0, use a module instead (see the built-in rules: https://github.com/rmosolgo/graphql-ruby/tree/master/lib/graphql/static_validation/rules)"
|
51
|
+
GraphQL::Deprecation.warn " -> Legacy validator: #{rule_class_or_module}"
|
52
|
+
rule_class_or_module.new.validate(context)
|
53
|
+
end
|
51
54
|
end
|
52
55
|
end
|
53
|
-
end
|
54
56
|
|
55
|
-
|
57
|
+
context.visitor.visit
|
58
|
+
end
|
56
59
|
end
|
57
60
|
rescue Timeout::Error
|
58
61
|
handle_timeout(query, context)
|
@@ -127,7 +127,13 @@ module GraphQL
|
|
127
127
|
# It will receive notifications when events come in
|
128
128
|
# and re-evaluate the query locally.
|
129
129
|
def write_subscription(query, events)
|
130
|
-
channel = query.context
|
130
|
+
unless (channel = query.context[:channel])
|
131
|
+
raise GraphQL::Error, "This GraphQL Subscription client does not support the transport protocol expected"\
|
132
|
+
"by the backend Subscription Server implementation (graphql-ruby ActionCableSubscriptions in this case)."\
|
133
|
+
"Some official client implementation including Apollo (https://graphql-ruby.org/javascript_client/apollo_subscriptions.html), "\
|
134
|
+
"Relay Modern (https://graphql-ruby.org/javascript_client/relay_subscriptions.html#actioncable)."\
|
135
|
+
"GraphiQL via `graphiql-rails` may not work out of box (#1051)."
|
136
|
+
end
|
131
137
|
subscription_id = query.context[:subscription_id] ||= build_id
|
132
138
|
stream = stream_subscription_name(subscription_id)
|
133
139
|
channel.stream_from(stream)
|
@@ -54,6 +54,7 @@ module GraphQL
|
|
54
54
|
class << self
|
55
55
|
private
|
56
56
|
def stringify_args(arg_owner, args)
|
57
|
+
arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers
|
57
58
|
case args
|
58
59
|
when Hash
|
59
60
|
next_args = {}
|
@@ -64,11 +65,11 @@ module GraphQL
|
|
64
65
|
|
65
66
|
if arg_defn
|
66
67
|
normalized_arg_name = camelized_arg_name
|
68
|
+
next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
|
67
69
|
else
|
68
70
|
normalized_arg_name = arg_name
|
69
71
|
arg_defn = get_arg_definition(arg_owner, normalized_arg_name)
|
70
72
|
end
|
71
|
-
|
72
73
|
next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
|
73
74
|
end
|
74
75
|
# Make sure they're deeply sorted
|
@@ -9,7 +9,7 @@ module GraphQL
|
|
9
9
|
SYMBOL_KEY = "__sym__"
|
10
10
|
SYMBOL_KEYS_KEY = "__sym_keys__"
|
11
11
|
TIMESTAMP_KEY = "__timestamp__"
|
12
|
-
TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%
|
12
|
+
TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%z" # eg '2020-01-01 23:59:59.123456789+05:00'
|
13
13
|
OPEN_STRUCT_KEY = "__ostruct__"
|
14
14
|
|
15
15
|
module_function
|
@@ -14,7 +14,22 @@ module GraphQL
|
|
14
14
|
"execute_query_lazy" => "execute.graphql",
|
15
15
|
}
|
16
16
|
|
17
|
+
# @param set_action_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
|
18
|
+
# This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
|
19
|
+
# It can also be specified per-query with `context[:set_appsignal_action_name]`.
|
20
|
+
def initialize(options = {})
|
21
|
+
@set_action_name = options.fetch(:set_action_name, false)
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
17
25
|
def platform_trace(platform_key, key, data)
|
26
|
+
if key == "execute_query"
|
27
|
+
set_this_txn_name = data[:query].context[:set_appsignal_action_name]
|
28
|
+
if set_this_txn_name == true || (set_this_txn_name.nil? && @set_action_name)
|
29
|
+
Appsignal::Transaction.current.set_action(transaction_name(data[:query]))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
18
33
|
Appsignal.instrument(platform_key) do
|
19
34
|
yield
|
20
35
|
end
|
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: 1.12.
|
4
|
+
version: 1.12.18
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-11-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|
@@ -700,7 +700,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
700
700
|
- !ruby/object:Gem::Version
|
701
701
|
version: '0'
|
702
702
|
requirements: []
|
703
|
-
rubygems_version: 3.2.
|
703
|
+
rubygems_version: 3.2.22
|
704
704
|
signing_key:
|
705
705
|
specification_version: 4
|
706
706
|
summary: A GraphQL language and runtime for Ruby
|