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.

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)