graphql 1.12.17 → 1.12.21
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/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)
|