graphql 1.12.17 → 1.12.21
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/graphql/object_generator.rb +2 -1
- data/lib/generators/graphql/relay.rb +19 -11
- data/lib/generators/graphql/templates/schema.erb +14 -2
- data/lib/graphql/analysis/ast/field_usage.rb +1 -1
- data/lib/graphql/dataloader/source.rb +32 -2
- data/lib/graphql/dataloader.rb +13 -0
- data/lib/graphql/deprecated_dsl.rb +11 -3
- data/lib/graphql/deprecation.rb +1 -5
- data/lib/graphql/integer_encoding_error.rb +18 -2
- data/lib/graphql/pagination/connections.rb +35 -16
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- 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 +90 -44
- data/lib/graphql/schema/resolver.rb +20 -57
- data/lib/graphql/schema/subscription.rb +4 -4
- data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
- data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/format_validator.rb +4 -1
- data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/length_validator.rb +5 -3
- data/lib/graphql/schema/validator/numericality_validator.rb +7 -1
- data/lib/graphql/schema/validator.rb +36 -25
- data/lib/graphql/schema.rb +18 -5
- data/lib/graphql/static_validation/base_visitor.rb +3 -0
- data/lib/graphql/static_validation/error.rb +3 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +40 -21
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
- data/lib/graphql/static_validation/validation_context.rb +8 -2
- data/lib/graphql/static_validation/validator.rb +15 -12
- data/lib/graphql/string_encoding_error.rb +13 -3
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +7 -1
- data/lib/graphql/subscriptions/event.rb +47 -2
- data/lib/graphql/subscriptions/serialize.rb +1 -1
- data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
- data/lib/graphql/types/int.rb +1 -1
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- metadata +5 -3
@@ -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)
|
@@ -214,11 +254,17 @@ module GraphQL
|
|
214
254
|
if authed
|
215
255
|
application_object
|
216
256
|
else
|
217
|
-
|
257
|
+
err = GraphQL::UnauthorizedError.new(
|
218
258
|
object: application_object,
|
219
259
|
type: class_based_type,
|
220
260
|
context: context,
|
221
261
|
)
|
262
|
+
if self.respond_to?(:unauthorized_object)
|
263
|
+
err.set_backtrace(caller)
|
264
|
+
unauthorized_object(err)
|
265
|
+
else
|
266
|
+
raise err
|
267
|
+
end
|
222
268
|
end
|
223
269
|
end
|
224
270
|
else
|
@@ -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
|
@@ -161,6 +160,16 @@ module GraphQL
|
|
161
160
|
end
|
162
161
|
end
|
163
162
|
|
163
|
+
# Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
|
164
|
+
#
|
165
|
+
# By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
|
166
|
+
#
|
167
|
+
# Any value returned here will be used _instead of_ of the loaded object.
|
168
|
+
# @param err [GraphQL::UnauthorizedError]
|
169
|
+
def unauthorized_object(err)
|
170
|
+
raise err
|
171
|
+
end
|
172
|
+
|
164
173
|
private
|
165
174
|
|
166
175
|
def load_arguments(args)
|
@@ -170,18 +179,14 @@ module GraphQL
|
|
170
179
|
args.each do |key, value|
|
171
180
|
arg_defn = @arguments_by_keyword[key]
|
172
181
|
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
|
182
|
+
prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context)
|
183
|
+
if context.schema.lazy?(prepped_value)
|
184
|
+
prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
|
185
|
+
prepared_args[key] = finished_prepped_value
|
181
186
|
end
|
182
187
|
end
|
183
188
|
else
|
184
|
-
#
|
189
|
+
# these are `extras:`
|
185
190
|
prepared_args[key] = value
|
186
191
|
end
|
187
192
|
end
|
@@ -194,8 +199,8 @@ module GraphQL
|
|
194
199
|
end
|
195
200
|
end
|
196
201
|
|
197
|
-
def
|
198
|
-
|
202
|
+
def get_argument(name)
|
203
|
+
self.class.get_argument(name)
|
199
204
|
end
|
200
205
|
|
201
206
|
class << self
|
@@ -334,47 +339,9 @@ module GraphQL
|
|
334
339
|
# also add some preparation hook methods which will be used for this argument
|
335
340
|
# @see {GraphQL::Schema::Argument#initialize} for the signature
|
336
341
|
def argument(*args, **kwargs, &block)
|
337
|
-
loads = kwargs[:loads]
|
338
342
|
# Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation
|
339
343
|
# 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)
|
344
|
+
super(*args, from_resolver: true, **kwargs)
|
378
345
|
end
|
379
346
|
|
380
347
|
# Registers new extension
|
@@ -410,10 +377,6 @@ module GraphQL
|
|
410
377
|
def own_extensions
|
411
378
|
@own_extensions
|
412
379
|
end
|
413
|
-
|
414
|
-
def own_arguments_loads_as_type
|
415
|
-
@own_arguments_loads_as_type ||= {}
|
416
|
-
end
|
417
380
|
end
|
418
381
|
end
|
419
382
|
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.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Validator
|
6
|
+
# Use this to specifically reject values that respond to `.blank?` and respond truthy for that method.
|
7
|
+
#
|
8
|
+
# @example Require a non-empty string for an argument
|
9
|
+
# argument :name, String, required: true, validate: { allow_blank: false }
|
10
|
+
class AllowBlankValidator < Validator
|
11
|
+
def initialize(allow_blank_positional, allow_blank: nil, message: "%{validated} can't be blank", **default_options)
|
12
|
+
@message = message
|
13
|
+
super(**default_options)
|
14
|
+
@allow_blank = allow_blank.nil? ? allow_blank_positional : allow_blank
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate(_object, _context, value)
|
18
|
+
if value.respond_to?(:blank?) && value.blank?
|
19
|
+
if (value.nil? && @allow_null) || @allow_blank
|
20
|
+
# pass
|
21
|
+
else
|
22
|
+
@message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Validator
|
6
|
+
# Use this to specifically reject or permit `nil` values (given as `null` from GraphQL).
|
7
|
+
#
|
8
|
+
# @example require a non-null value for an argument if it is provided
|
9
|
+
# argument :name, String, required: false, validates: { allow_null: false }
|
10
|
+
class AllowNullValidator < Validator
|
11
|
+
MESSAGE = "%{validated} can't be null"
|
12
|
+
def initialize(allow_null_positional, allow_null: nil, message: MESSAGE, **default_options)
|
13
|
+
@message = message
|
14
|
+
super(**default_options)
|
15
|
+
@allow_null = allow_null.nil? ? allow_null_positional : allow_null
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate(_object, _context, value)
|
19
|
+
if value.nil? && !@allow_null
|
20
|
+
@message
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -38,7 +38,10 @@ module GraphQL
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def validate(_object, _context, value)
|
41
|
-
if (
|
41
|
+
if permitted_empty_value?(value)
|
42
|
+
# Do nothing
|
43
|
+
elsif value.nil? ||
|
44
|
+
(@with_pattern && !value.match?(@with_pattern)) ||
|
42
45
|
(@without_pattern && value.match?(@without_pattern))
|
43
46
|
@message
|
44
47
|
end
|
@@ -43,11 +43,13 @@ module GraphQL
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def validate(_object, _context, value)
|
46
|
-
if
|
46
|
+
return if permitted_empty_value?(value) # pass in this case
|
47
|
+
length = value.nil? ? 0 : value.length
|
48
|
+
if @maximum && length > @maximum
|
47
49
|
partial_format(@too_long, { count: @maximum })
|
48
|
-
elsif @minimum &&
|
50
|
+
elsif @minimum && length < @minimum
|
49
51
|
partial_format(@too_short, { count: @minimum })
|
50
|
-
elsif @is &&
|
52
|
+
elsif @is && length != @is
|
51
53
|
partial_format(@wrong_length, { count: @is })
|
52
54
|
end
|
53
55
|
end
|
@@ -32,6 +32,7 @@ module GraphQL
|
|
32
32
|
equal_to: nil, other_than: nil,
|
33
33
|
odd: nil, even: nil, within: nil,
|
34
34
|
message: "%{validated} must be %{comparison} %{target}",
|
35
|
+
null_message: Validator::AllowNullValidator::MESSAGE,
|
35
36
|
**default_options
|
36
37
|
)
|
37
38
|
|
@@ -45,11 +46,16 @@ module GraphQL
|
|
45
46
|
@even = even
|
46
47
|
@within = within
|
47
48
|
@message = message
|
49
|
+
@null_message = null_message
|
48
50
|
super(**default_options)
|
49
51
|
end
|
50
52
|
|
51
53
|
def validate(object, context, value)
|
52
|
-
if
|
54
|
+
if permitted_empty_value?(value)
|
55
|
+
# pass in this case
|
56
|
+
elsif value.nil? # @allow_null is handled in the parent class
|
57
|
+
@null_message
|
58
|
+
elsif @greater_than && value <= @greater_than
|
53
59
|
partial_format(@message, { comparison: "greater than", target: @greater_than })
|
54
60
|
elsif @greater_than_or_equal_to && value < @greater_than_or_equal_to
|
55
61
|
partial_format(@message, { comparison: "greater than or equal to", target: @greater_than_or_equal_to })
|
@@ -7,7 +7,6 @@ module GraphQL
|
|
7
7
|
# @return [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class<GraphQL::Schema::InputObject>]
|
8
8
|
attr_reader :validated
|
9
9
|
|
10
|
-
# TODO should this implement `if:` and `unless:` ?
|
11
10
|
# @param validated [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class<GraphQL::Schema::InputObject>] The argument or argument owner this validator is attached to
|
12
11
|
# @param allow_blank [Boolean] if `true`, then objects that respond to `.blank?` and return true for `.blank?` will skip this validation
|
13
12
|
# @param allow_null [Boolean] if `true`, then incoming `null`s will skip this validation
|
@@ -25,26 +24,6 @@ module GraphQL
|
|
25
24
|
raise GraphQL::RequiredImplementationMissingError, "Validator classes should implement #validate"
|
26
25
|
end
|
27
26
|
|
28
|
-
# This is called by the validation system and eventually calls {#validate}.
|
29
|
-
# @api private
|
30
|
-
def apply(object, context, value)
|
31
|
-
if value.nil?
|
32
|
-
if @allow_null
|
33
|
-
nil # skip this
|
34
|
-
else
|
35
|
-
"%{validated} can't be null"
|
36
|
-
end
|
37
|
-
elsif value.respond_to?(:blank?) && value.blank?
|
38
|
-
if @allow_blank
|
39
|
-
nil # skip this
|
40
|
-
else
|
41
|
-
"%{validated} can't be blank"
|
42
|
-
end
|
43
|
-
else
|
44
|
-
validate(object, context, value)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
27
|
# This is like `String#%`, but it supports the case that only some of `string`'s
|
49
28
|
# values are present in `substitutions`
|
50
29
|
def partial_format(string, substitutions)
|
@@ -55,6 +34,12 @@ module GraphQL
|
|
55
34
|
string
|
56
35
|
end
|
57
36
|
|
37
|
+
# @return [Boolean] `true` if `value` is `nil` and this validator has `allow_null: true` or if value is `.blank?` and this validator has `allow_blank: true`
|
38
|
+
def permitted_empty_value?(value)
|
39
|
+
(value.nil? && @allow_null) ||
|
40
|
+
(@allow_blank && value.respond_to?(:blank?) && value.blank?)
|
41
|
+
end
|
42
|
+
|
58
43
|
# @param schema_member [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class<GraphQL::Schema::InputObject>]
|
59
44
|
# @param validates_hash [Hash{Symbol => Hash}, Hash{Class => Hash} nil] A configuration passed as `validates:`
|
60
45
|
# @return [Array<Validator>]
|
@@ -62,6 +47,24 @@ module GraphQL
|
|
62
47
|
if validates_hash.nil? || validates_hash.empty?
|
63
48
|
EMPTY_ARRAY
|
64
49
|
else
|
50
|
+
validates_hash = validates_hash.dup
|
51
|
+
allow_null = validates_hash.delete(:allow_null)
|
52
|
+
allow_blank = validates_hash.delete(:allow_blank)
|
53
|
+
|
54
|
+
# This could be {...}.compact on Ruby 2.4+
|
55
|
+
default_options = {}
|
56
|
+
if !allow_null.nil?
|
57
|
+
default_options[:allow_null] = allow_null
|
58
|
+
end
|
59
|
+
if !allow_blank.nil?
|
60
|
+
default_options[:allow_blank] = allow_blank
|
61
|
+
end
|
62
|
+
|
63
|
+
# allow_nil or allow_blank are the _only_ validations:
|
64
|
+
if validates_hash.empty?
|
65
|
+
validates_hash = default_options
|
66
|
+
end
|
67
|
+
|
65
68
|
validates_hash.map do |validator_name, options|
|
66
69
|
validator_class = case validator_name
|
67
70
|
when Class
|
@@ -69,7 +72,11 @@ module GraphQL
|
|
69
72
|
else
|
70
73
|
all_validators[validator_name] || raise(ArgumentError, "unknown validation: #{validator_name.inspect}")
|
71
74
|
end
|
72
|
-
|
75
|
+
if options.is_a?(Hash)
|
76
|
+
validator_class.new(validated: schema_member, **(default_options.merge(options)))
|
77
|
+
else
|
78
|
+
validator_class.new(options, validated: schema_member, **default_options)
|
79
|
+
end
|
73
80
|
end
|
74
81
|
end
|
75
82
|
end
|
@@ -122,10 +129,10 @@ module GraphQL
|
|
122
129
|
|
123
130
|
validators.each do |validator|
|
124
131
|
validated = as || validator.validated
|
125
|
-
errors = validator.
|
132
|
+
errors = validator.validate(object, context, value)
|
126
133
|
if errors &&
|
127
|
-
|
128
|
-
|
134
|
+
(errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
|
135
|
+
(errors.is_a?(String))
|
129
136
|
if all_errors.frozen? # It's empty
|
130
137
|
all_errors = []
|
131
138
|
end
|
@@ -161,3 +168,7 @@ require "graphql/schema/validator/exclusion_validator"
|
|
161
168
|
GraphQL::Schema::Validator.install(:exclusion, GraphQL::Schema::Validator::ExclusionValidator)
|
162
169
|
require "graphql/schema/validator/required_validator"
|
163
170
|
GraphQL::Schema::Validator.install(:required, GraphQL::Schema::Validator::RequiredValidator)
|
171
|
+
require "graphql/schema/validator/allow_null_validator"
|
172
|
+
GraphQL::Schema::Validator.install(:allow_null, GraphQL::Schema::Validator::AllowNullValidator)
|
173
|
+
require "graphql/schema/validator/allow_blank_validator"
|
174
|
+
GraphQL::Schema::Validator.install(:allow_blank, GraphQL::Schema::Validator::AllowBlankValidator)
|
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)
|