graphql 1.12.17 → 1.12.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/object_generator.rb +2 -1
  3. data/lib/generators/graphql/relay.rb +19 -11
  4. data/lib/generators/graphql/templates/schema.erb +14 -2
  5. data/lib/graphql/analysis/ast/field_usage.rb +1 -1
  6. data/lib/graphql/dataloader/source.rb +32 -2
  7. data/lib/graphql/dataloader.rb +13 -0
  8. data/lib/graphql/deprecated_dsl.rb +11 -3
  9. data/lib/graphql/deprecation.rb +1 -5
  10. data/lib/graphql/integer_encoding_error.rb +18 -2
  11. data/lib/graphql/pagination/connections.rb +35 -16
  12. data/lib/graphql/query/validation_pipeline.rb +1 -1
  13. data/lib/graphql/schema/argument.rb +55 -26
  14. data/lib/graphql/schema/field.rb +14 -4
  15. data/lib/graphql/schema/input_object.rb +1 -5
  16. data/lib/graphql/schema/member/has_arguments.rb +90 -44
  17. data/lib/graphql/schema/resolver.rb +20 -57
  18. data/lib/graphql/schema/subscription.rb +4 -4
  19. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  20. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  21. data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
  22. data/lib/graphql/schema/validator/format_validator.rb +4 -1
  23. data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
  24. data/lib/graphql/schema/validator/length_validator.rb +5 -3
  25. data/lib/graphql/schema/validator/numericality_validator.rb +7 -1
  26. data/lib/graphql/schema/validator.rb +36 -25
  27. data/lib/graphql/schema.rb +18 -5
  28. data/lib/graphql/static_validation/base_visitor.rb +3 -0
  29. data/lib/graphql/static_validation/error.rb +3 -1
  30. data/lib/graphql/static_validation/rules/fields_will_merge.rb +40 -21
  31. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  32. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  33. data/lib/graphql/static_validation/validation_context.rb +8 -2
  34. data/lib/graphql/static_validation/validator.rb +15 -12
  35. data/lib/graphql/string_encoding_error.rb +13 -3
  36. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +7 -1
  37. data/lib/graphql/subscriptions/event.rb +47 -2
  38. data/lib/graphql/subscriptions/serialize.rb +1 -1
  39. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  40. data/lib/graphql/types/int.rb +1 -1
  41. data/lib/graphql/types/string.rb +1 -1
  42. data/lib/graphql/unauthorized_error.rb +1 -1
  43. data/lib/graphql/version.rb +1 -1
  44. 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
- if total_args_count == 0
98
- final_args = GraphQL::Execution::Interpreter::Arguments::EMPTY
99
- if block_given?
100
- block.call(final_args)
101
- nil
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
- final_args
104
- end
105
- else
106
- finished_args = nil
107
- argument_values = {}
108
- resolved_args_count = 0
109
- raised_error = false
110
- arg_defns.each do |arg_name, arg_defn|
111
- context.dataloader.append_job do
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
- if block_given?
132
- block.call(finished_args)
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
- if block_given?
139
- nil
140
- else
141
- # This API returns eagerly, gotta run it now
142
- context.dataloader.run
143
- finished_args
144
- end
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, lookup_as_type, id, context)
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
- loaded_application_object = object_from_id(lookup_as_type, id, context)
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(lookup_as_type, application_object, context)
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(lookup_as_type)
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
- raise GraphQL::UnauthorizedError.new(
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] the initialize object, pass to {Query.initialize} as `root_value`
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
- nil
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
- if value.nil?
174
- prepared_args[key] = value
175
- else
176
- prepped_value = prepared_args[key] = load_argument(key, value)
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
- # These are `extras: [...]`
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 load_argument(name, value)
198
- public_send("load_#{name}", value)
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
- arg_defn = super(*args, from_resolver: true, **kwargs)
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 == :no_update
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 `:no_update` if you want to
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 `:no_update` when an
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
@@ -21,7 +21,9 @@ module GraphQL
21
21
  end
22
22
 
23
23
  def validate(_object, _context, value)
24
- if @in_list.include?(value)
24
+ if permitted_empty_value?(value)
25
+ # pass
26
+ elsif @in_list.include?(value)
25
27
  @message
26
28
  end
27
29
  end
@@ -38,7 +38,10 @@ module GraphQL
38
38
  end
39
39
 
40
40
  def validate(_object, _context, value)
41
- if (@with_pattern && !value.match?(@with_pattern)) ||
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
@@ -23,7 +23,9 @@ module GraphQL
23
23
  end
24
24
 
25
25
  def validate(_object, _context, value)
26
- if !@in_list.include?(value)
26
+ if permitted_empty_value?(value)
27
+ # pass
28
+ elsif !@in_list.include?(value)
27
29
  @message
28
30
  end
29
31
  end
@@ -43,11 +43,13 @@ module GraphQL
43
43
  end
44
44
 
45
45
  def validate(_object, _context, value)
46
- if @maximum && value.length > @maximum
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 && value.length < @minimum
50
+ elsif @minimum && length < @minimum
49
51
  partial_format(@too_short, { count: @minimum })
50
- elsif @is && value.length != @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 @greater_than && value <= @greater_than
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
- validator_class.new(validated: schema_member, **options)
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.apply(object, context, value)
132
+ errors = validator.validate(object, context, value)
126
133
  if errors &&
127
- (errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
128
- (errors.is_a?(String))
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)
@@ -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] Resticted access to root types
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] Resticted access to root types
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)