graphql 2.5.4 → 2.5.11

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/query_complexity.rb +2 -2
  3. data/lib/graphql/dataloader/active_record_association_source.rb +14 -2
  4. data/lib/graphql/dataloader/null_dataloader.rb +7 -0
  5. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  6. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -1
  7. data/lib/graphql/execution/interpreter/runtime.rb +136 -49
  8. data/lib/graphql/execution/interpreter.rb +1 -1
  9. data/lib/graphql/language/lexer.rb +9 -2
  10. data/lib/graphql/language/nodes.rb +5 -1
  11. data/lib/graphql/language/parser.rb +1 -0
  12. data/lib/graphql/query/context.rb +1 -1
  13. data/lib/graphql/query/partial.rb +179 -0
  14. data/lib/graphql/query.rb +55 -43
  15. data/lib/graphql/schema/addition.rb +3 -1
  16. data/lib/graphql/schema/argument.rb +5 -0
  17. data/lib/graphql/schema/build_from_definition.rb +5 -1
  18. data/lib/graphql/schema/directive.rb +33 -1
  19. data/lib/graphql/schema/field.rb +8 -0
  20. data/lib/graphql/schema/input_object.rb +28 -21
  21. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  22. data/lib/graphql/schema/member/has_interfaces.rb +2 -2
  23. data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
  24. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  25. data/lib/graphql/schema/scalar.rb +1 -6
  26. data/lib/graphql/schema/timeout.rb +19 -2
  27. data/lib/graphql/schema/validator/required_validator.rb +15 -6
  28. data/lib/graphql/schema/visibility/migration.rb +2 -2
  29. data/lib/graphql/schema/visibility/profile.rb +74 -10
  30. data/lib/graphql/schema/visibility.rb +30 -17
  31. data/lib/graphql/schema/warden.rb +13 -5
  32. data/lib/graphql/schema.rb +53 -22
  33. data/lib/graphql/static_validation/all_rules.rb +1 -1
  34. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  35. data/lib/graphql/tracing/notifications_trace.rb +3 -1
  36. data/lib/graphql/tracing/null_trace.rb +1 -1
  37. data/lib/graphql/tracing/perfetto_trace.rb +1 -1
  38. data/lib/graphql/type_kinds.rb +1 -0
  39. data/lib/graphql/version.rb +1 -1
  40. data/lib/graphql.rb +1 -1
  41. metadata +19 -3
data/lib/graphql/query.rb CHANGED
@@ -10,12 +10,47 @@ module GraphQL
10
10
  autoload :Context, "graphql/query/context"
11
11
  autoload :Fingerprint, "graphql/query/fingerprint"
12
12
  autoload :NullContext, "graphql/query/null_context"
13
+ autoload :Partial, "graphql/query/partial"
13
14
  autoload :Result, "graphql/query/result"
14
15
  autoload :Variables, "graphql/query/variables"
15
16
  autoload :InputValidationResult, "graphql/query/input_validation_result"
16
17
  autoload :VariableValidationError, "graphql/query/variable_validation_error"
17
18
  autoload :ValidationPipeline, "graphql/query/validation_pipeline"
18
19
 
20
+ # Code shared with {Partial}
21
+ module Runnable
22
+ def after_lazy(value, &block)
23
+ if !defined?(@runtime_instance)
24
+ @runtime_instance = context.namespace(:interpreter_runtime)[:runtime]
25
+ end
26
+
27
+ if @runtime_instance
28
+ @runtime_instance.minimal_after_lazy(value, &block)
29
+ else
30
+ @schema.after_lazy(value, &block)
31
+ end
32
+ end
33
+
34
+ # Node-level cache for calculating arguments. Used during execution and query analysis.
35
+ # @param ast_node [GraphQL::Language::Nodes::AbstractNode]
36
+ # @param definition [GraphQL::Schema::Field]
37
+ # @param parent_object [GraphQL::Schema::Object]
38
+ # @return [Hash{Symbol => Object}]
39
+ def arguments_for(ast_node, definition, parent_object: nil)
40
+ arguments_cache.fetch(ast_node, definition, parent_object)
41
+ end
42
+
43
+ def arguments_cache
44
+ @arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self)
45
+ end
46
+
47
+ # @api private
48
+ def handle_or_reraise(err)
49
+ @schema.handle_or_reraise(context, err)
50
+ end
51
+ end
52
+
53
+ include Runnable
19
54
  class OperationNameMissingError < GraphQL::ExecutionError
20
55
  def initialize(name)
21
56
  msg = if name.nil?
@@ -198,19 +233,10 @@ module GraphQL
198
233
  # @return [GraphQL::Execution::Lookahead]
199
234
  def lookahead
200
235
  @lookahead ||= begin
201
- ast_node = selected_operation
202
- if ast_node.nil?
236
+ if selected_operation.nil?
203
237
  GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
204
238
  else
205
- root_type = case ast_node.operation_type
206
- when nil, "query"
207
- types.query_root # rubocop:disable Development/ContextIsPassedCop
208
- when "mutation"
209
- types.mutation_root # rubocop:disable Development/ContextIsPassedCop
210
- when "subscription"
211
- types.subscription_root # rubocop:disable Development/ContextIsPassedCop
212
- end
213
- GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
239
+ GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [selected_operation])
214
240
  end
215
241
  end
216
242
  end
@@ -236,6 +262,18 @@ module GraphQL
236
262
  with_prepared_ast { @operations }
237
263
  end
238
264
 
265
+ # Run subtree partials of this query and return their results.
266
+ # Each partial is identified with a `path:` and `object:`
267
+ # where the path references a field in the AST and the object will be treated
268
+ # as the return value from that field. Subfields of the field named by `path`
269
+ # will be executed with `object` as the starting point
270
+ # @param partials_hashes [Array<Hash{Symbol => Object}>] Hashes with `path:` and `object:` keys
271
+ # @return [Array<GraphQL::Query::Result>]
272
+ def run_partials(partials_hashes)
273
+ partials = partials_hashes.map { |partial_options| Partial.new(query: self, **partial_options) }
274
+ Execution::Interpreter.run_all(@schema, partials, context: @context)
275
+ end
276
+
239
277
  # Get the result for this query, executing it once
240
278
  # @return [GraphQL::Query::Result] A Hash-like GraphQL response, with `"data"` and/or `"errors"` keys
241
279
  def result
@@ -278,19 +316,6 @@ module GraphQL
278
316
  end
279
317
  end
280
318
 
281
- # Node-level cache for calculating arguments. Used during execution and query analysis.
282
- # @param ast_node [GraphQL::Language::Nodes::AbstractNode]
283
- # @param definition [GraphQL::Schema::Field]
284
- # @param parent_object [GraphQL::Schema::Object]
285
- # @return [Hash{Symbol => Object}]
286
- def arguments_for(ast_node, definition, parent_object: nil)
287
- arguments_cache.fetch(ast_node, definition, parent_object)
288
- end
289
-
290
- def arguments_cache
291
- @arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self)
292
- end
293
-
294
319
  # A version of the given query string, with:
295
320
  # - Variables inlined to the query
296
321
  # - Strings replaced with `<REDACTED>`
@@ -357,17 +382,21 @@ module GraphQL
357
382
 
358
383
  def root_type_for_operation(op_type)
359
384
  case op_type
360
- when "query"
385
+ when "query", nil
361
386
  types.query_root # rubocop:disable Development/ContextIsPassedCop
362
387
  when "mutation"
363
388
  types.mutation_root # rubocop:disable Development/ContextIsPassedCop
364
389
  when "subscription"
365
390
  types.subscription_root # rubocop:disable Development/ContextIsPassedCop
366
391
  else
367
- raise ArgumentError, "unexpected root type name: #{op_type.inspect}; expected 'query', 'mutation', or 'subscription'"
392
+ raise ArgumentError, "unexpected root type name: #{op_type.inspect}; expected nil, 'query', 'mutation', or 'subscription'"
368
393
  end
369
394
  end
370
395
 
396
+ def root_type
397
+ root_type_for_operation(selected_operation.operation_type)
398
+ end
399
+
371
400
  def types
372
401
  @visibility_profile || warden.visibility_profile
373
402
  end
@@ -400,23 +429,6 @@ module GraphQL
400
429
  with_prepared_ast { @subscription }
401
430
  end
402
431
 
403
- # @api private
404
- def handle_or_reraise(err)
405
- schema.handle_or_reraise(context, err)
406
- end
407
-
408
- def after_lazy(value, &block)
409
- if !defined?(@runtime_instance)
410
- @runtime_instance = context.namespace(:interpreter_runtime)[:runtime]
411
- end
412
-
413
- if @runtime_instance
414
- @runtime_instance.minimal_after_lazy(value, &block)
415
- else
416
- @schema.after_lazy(value, &block)
417
- end
418
- end
419
-
420
432
  attr_reader :logger
421
433
 
422
434
  private
@@ -258,7 +258,9 @@ module GraphQL
258
258
  # We can get these now; we'll have to get late-bound types later
259
259
  if interface_type.is_a?(Module) && type.is_a?(Class)
260
260
  implementers = @possible_types[interface_type] ||= []
261
- implementers << type
261
+ if !implementers.include?(type)
262
+ implementers << type
263
+ end
262
264
  end
263
265
  when String, Schema::LateBoundType
264
266
  interface_type = interface_type_membership
@@ -212,6 +212,11 @@ module GraphQL
212
212
  @statically_coercible = !requires_parent_object
213
213
  end
214
214
 
215
+ def freeze
216
+ statically_coercible?
217
+ super
218
+ end
219
+
215
220
  # Apply the {prepare} configuration to `value`, using methods from `obj`.
216
221
  # Used by the runtime.
217
222
  # @api private
@@ -187,6 +187,10 @@ module GraphQL
187
187
 
188
188
  object_types.each do |t|
189
189
  t.interfaces.each do |int_t|
190
+ if int_t.is_a?(LateBoundType)
191
+ int_t = types[int_t.graphql_name]
192
+ t.implements(int_t)
193
+ end
190
194
  int_t.orphan_types(t)
191
195
  end
192
196
  end
@@ -519,7 +523,7 @@ module GraphQL
519
523
 
520
524
  def define_field_resolve_method(owner, method_name, field_name)
521
525
  owner.define_method(method_name) { |**args|
522
- field_instance = self.class.get_field(field_name)
526
+ field_instance = context.types.field(owner, field_name)
523
527
  context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
524
528
  }
525
529
  end
@@ -9,6 +9,7 @@ module GraphQL
9
9
  class Directive < GraphQL::Schema::Member
10
10
  extend GraphQL::Schema::Member::HasArguments
11
11
  extend GraphQL::Schema::Member::HasArguments::HasDirectiveArguments
12
+ extend GraphQL::Schema::Member::HasValidators
12
13
 
13
14
  class << self
14
15
  # Directives aren't types, they don't have kinds.
@@ -75,6 +76,10 @@ module GraphQL
75
76
  yield
76
77
  end
77
78
 
79
+ def validate!(arguments, context)
80
+ Schema::Validator.validate!(validators, self, context, arguments)
81
+ end
82
+
78
83
  def on_field?
79
84
  locations.include?(FIELD)
80
85
  end
@@ -111,6 +116,9 @@ module GraphQL
111
116
  # @return [GraphQL::Interpreter::Arguments]
112
117
  attr_reader :arguments
113
118
 
119
+ class InvalidArgumentError < GraphQL::Error
120
+ end
121
+
114
122
  def initialize(owner, **arguments)
115
123
  @owner = owner
116
124
  assert_valid_owner
@@ -119,7 +127,31 @@ module GraphQL
119
127
  # - lazy resolution
120
128
  # Probably, those won't be needed here, since these are configuration arguments,
121
129
  # not runtime arguments.
122
- @arguments = self.class.coerce_arguments(nil, arguments, Query::NullContext.instance)
130
+ context = Query::NullContext.instance
131
+ self.class.all_argument_definitions.each do |arg_defn|
132
+ if arguments.key?(arg_defn.keyword)
133
+ value = arguments[arg_defn.keyword]
134
+ # This is a Ruby-land value; convert it to graphql for validation
135
+ graphql_value = begin
136
+ arg_defn.type.unwrap.coerce_isolated_result(value)
137
+ rescue GraphQL::Schema::Enum::UnresolvedValueError
138
+ # Let validation handle this
139
+ value
140
+ end
141
+ else
142
+ value = graphql_value = nil
143
+ end
144
+
145
+ result = arg_defn.type.validate_input(graphql_value, context)
146
+ if !result.valid?
147
+ raise InvalidArgumentError, "@#{graphql_name}.#{arg_defn.graphql_name} on #{owner.path} is invalid (#{value.inspect}): #{result.problems.first["explanation"]}"
148
+ end
149
+ end
150
+ self.class.validate!(arguments, context)
151
+ @arguments = self.class.coerce_arguments(nil, arguments, context)
152
+ if @arguments.is_a?(GraphQL::ExecutionError)
153
+ raise @arguments
154
+ end
123
155
  end
124
156
 
125
157
  def graphql_name
@@ -616,6 +616,14 @@ module GraphQL
616
616
  end
617
617
  end
618
618
 
619
+ def freeze
620
+ type
621
+ owner_type
622
+ arguments_statically_coercible?
623
+ connection?
624
+ super
625
+ end
626
+
619
627
  class MissingReturnTypeError < GraphQL::Error; end
620
628
  attr_writer :type
621
629
 
@@ -176,30 +176,37 @@ module GraphQL
176
176
  return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
177
177
  end
178
178
 
179
- # Inject missing required arguments
180
- missing_required_inputs = ctx.types.arguments(self).reduce({}) do |m, (argument)|
181
- if !input.key?(argument.graphql_name) && argument.type.non_null? && !argument.default_value? && types.argument(self, argument.graphql_name)
182
- m[argument.graphql_name] = nil
183
- end
184
-
185
- m
186
- end
187
179
 
188
180
  result = nil
189
- [input, missing_required_inputs].each do |args_to_validate|
190
- args_to_validate.each do |argument_name, value|
191
- argument = types.argument(self, argument_name)
192
- # Items in the input that are unexpected
193
- if argument.nil?
194
- result ||= Query::InputValidationResult.new
195
- result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
196
- else
197
- # Items in the input that are expected, but have invalid values
198
- argument_result = argument.type.validate_input(value, ctx)
181
+
182
+
183
+ input.each do |argument_name, value|
184
+ argument = types.argument(self, argument_name)
185
+ if argument.nil? && ctx.is_a?(Query::NullContext) && argument_name.is_a?(Symbol)
186
+ # Validating definition directive arguments which come in as Symbols
187
+ argument = types.arguments(self).find { |arg| arg.keyword == argument_name }
188
+ end
189
+ # Items in the input that are unexpected
190
+ if argument.nil?
191
+ result ||= Query::InputValidationResult.new
192
+ result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
193
+ else
194
+ # Items in the input that are expected, but have invalid values
195
+ argument_result = argument.type.validate_input(value, ctx)
196
+ if !argument_result.valid?
199
197
  result ||= Query::InputValidationResult.new
200
- if !argument_result.valid?
201
- result.merge_result!(argument_name, argument_result)
202
- end
198
+ result.merge_result!(argument_name, argument_result)
199
+ end
200
+ end
201
+ end
202
+
203
+ # Check for missing non-null arguments
204
+ ctx.types.arguments(self).each do |argument|
205
+ if !input.key?(argument.graphql_name) && argument.type.non_null? && !argument.default_value?
206
+ result ||= Query::InputValidationResult.new
207
+ argument_result = argument.type.validate_input(nil, ctx)
208
+ if !argument_result.valid?
209
+ result.merge_result!(argument.graphql_name, argument_result)
203
210
  end
204
211
  end
205
212
  end
@@ -370,8 +370,8 @@ module GraphQL
370
370
  end
371
371
 
372
372
  passes_possible_types_check = if context.types.loadable?(arg_loads_type, context)
373
- if arg_loads_type.kind.union?
374
- # This union is used in `loads:` but not otherwise visible to this query
373
+ if arg_loads_type.kind.abstract?
374
+ # This union/interface is used in `loads:` but not otherwise visible to this query
375
375
  context.types.loadable_possible_types(arg_loads_type, context).include?(application_object_type)
376
376
  else
377
377
  true
@@ -8,8 +8,8 @@ module GraphQL
8
8
  new_memberships = []
9
9
  new_interfaces.each do |int|
10
10
  if int.is_a?(Module)
11
- unless int.include?(GraphQL::Schema::Interface)
12
- raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
11
+ unless int.include?(GraphQL::Schema::Interface) && !int.is_a?(Class)
12
+ raise "#{int.respond_to?(:graphql_name) ? "#{int.graphql_name} (#{int})" : int.inspect} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
13
13
  end
14
14
 
15
15
  new_memberships << int.type_membership_class.new(int, self, **options)
@@ -12,12 +12,26 @@ module GraphQL
12
12
 
13
13
  # @return [Schema::NonNull] Make a non-null-type representation of this type
14
14
  def to_non_null_type
15
- @to_non_null_type ||= GraphQL::Schema::NonNull.new(self)
15
+ @to_non_null_type || begin
16
+ t = GraphQL::Schema::NonNull.new(self)
17
+ if frozen?
18
+ t
19
+ else
20
+ @to_non_null_type = t
21
+ end
22
+ end
16
23
  end
17
24
 
18
25
  # @return [Schema::List] Make a list-type representation of this type
19
26
  def to_list_type
20
- @to_list_type ||= GraphQL::Schema::List.new(self)
27
+ @to_list_type || begin
28
+ t = GraphQL::Schema::List.new(self)
29
+ if frozen?
30
+ t
31
+ else
32
+ @to_list_type = t
33
+ end
34
+ end
21
35
  end
22
36
 
23
37
  # @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable.
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ module RactorShareable
5
+ def self.extended(schema_class)
6
+ schema_class.extend(SchemaExtension)
7
+ schema_class.freeze_schema
8
+ end
9
+
10
+ module SchemaExtension
11
+
12
+ def freeze_error_handlers(handlers)
13
+ handlers[:subclass_handlers].default_proc = nil
14
+ handlers[:subclass_handlers].each do |_class, subclass_handlers|
15
+ freeze_error_handlers(subclass_handlers)
16
+ end
17
+ Ractor.make_shareable(handlers)
18
+ end
19
+
20
+ def freeze_schema
21
+ # warm some ivars:
22
+ default_analysis_engine
23
+ default_execution_strategy
24
+ GraphQL.default_parser
25
+ default_logger
26
+ freeze_error_handlers(error_handlers)
27
+ # TODO: this freezes errors of parent classes which could cause trouble
28
+ parent_class = superclass
29
+ while parent_class.respond_to?(:error_handlers)
30
+ freeze_error_handlers(parent_class.error_handlers)
31
+ parent_class = parent_class.superclass
32
+ end
33
+
34
+ own_tracers.freeze
35
+ @frozen_tracers = tracers.freeze
36
+ own_trace_modes.each do |m|
37
+ trace_options_for(m)
38
+ build_trace_mode(m)
39
+ end
40
+ build_trace_mode(:default)
41
+ Ractor.make_shareable(@trace_options_for_mode)
42
+ Ractor.make_shareable(own_trace_modes)
43
+ Ractor.make_shareable(own_multiplex_analyzers)
44
+ @frozen_multiplex_analyzers = Ractor.make_shareable(multiplex_analyzers)
45
+ Ractor.make_shareable(own_query_analyzers)
46
+ @frozen_query_analyzers = Ractor.make_shareable(query_analyzers)
47
+ Ractor.make_shareable(own_plugins)
48
+ own_plugins.each do |(plugin, options)|
49
+ Ractor.make_shareable(plugin)
50
+ Ractor.make_shareable(options)
51
+ end
52
+ @frozen_plugins = Ractor.make_shareable(plugins)
53
+ Ractor.make_shareable(own_references_to)
54
+ @frozen_directives = Ractor.make_shareable(directives)
55
+
56
+ Ractor.make_shareable(visibility)
57
+ Ractor.make_shareable(introspection_system)
58
+ extend(FrozenMethods)
59
+
60
+ Ractor.make_shareable(self)
61
+ superclass.respond_to?(:freeze_schema) && superclass.freeze_schema
62
+ end
63
+
64
+ module FrozenMethods
65
+ def tracers; @frozen_tracers; end
66
+ def multiplex_analyzers; @frozen_multiplex_analyzers; end
67
+ def query_analyzers; @frozen_query_analyzers; end
68
+ def plugins; @frozen_plugins; end
69
+ def directives; @frozen_directives; end
70
+
71
+ # This actually accumulates info during execution...
72
+ # How to support it?
73
+ def lazy?(_obj); false; end
74
+ def sync_lazy(obj); obj; end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -50,12 +50,7 @@ module GraphQL
50
50
  end
51
51
 
52
52
  if coerced_result.nil?
53
- str_value = if value == Float::INFINITY
54
- ""
55
- else
56
- " #{GraphQL::Language.serialize(value)}"
57
- end
58
- Query::InputValidationResult.from_problem("Could not coerce value#{str_value} to #{graphql_name}")
53
+ Query::InputValidationResult.from_problem("Could not coerce value #{GraphQL::Language.serialize(value)} to #{graphql_name}")
59
54
  elsif coerced_result.is_a?(GraphQL::CoercionError)
60
55
  Query::InputValidationResult.from_problem(coerced_result.message, message: coerced_result.message, extensions: coerced_result.extensions)
61
56
  else
@@ -71,15 +71,23 @@ module GraphQL
71
71
  def execute_field(query:, field:, **_rest)
72
72
  timeout_state = query.context.namespace(@timeout).fetch(:state)
73
73
  # If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
74
- if timeout_state != false && Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
74
+ if timeout_state == false
75
+ super
76
+ elsif Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
75
77
  error = GraphQL::Schema::Timeout::TimeoutError.new(field)
76
78
  # Only invoke the timeout callback for the first timeout
77
79
  if !timeout_state[:timed_out]
78
80
  timeout_state[:timed_out] = true
79
81
  @timeout.handle_timeout(error, query)
82
+ timeout_state = query.context.namespace(@timeout).fetch(:state)
80
83
  end
81
84
 
82
- error
85
+ # `handle_timeout` may have set this to be `false`
86
+ if timeout_state != false
87
+ error
88
+ else
89
+ super
90
+ end
83
91
  else
84
92
  super
85
93
  end
@@ -102,6 +110,15 @@ module GraphQL
102
110
  # override to do something interesting
103
111
  end
104
112
 
113
+ # Call this method (eg, from {#handle_timeout}) to disable timeout tracking
114
+ # for the given query.
115
+ # @param query [GraphQL::Query]
116
+ # @return [void]
117
+ def disable_timeout(query)
118
+ query.context.namespace(self)[:state] = false
119
+ nil
120
+ end
121
+
105
122
  # This error is raised when a query exceeds `max_seconds`.
106
123
  # Since it's a child of {GraphQL::ExecutionError},
107
124
  # its message will be added to the response's `errors` key.
@@ -96,26 +96,35 @@ module GraphQL
96
96
  end
97
97
 
98
98
  def build_message(context)
99
- argument_definitions = @validated.arguments(context).values
99
+ argument_definitions = context.types.arguments(@validated)
100
+
100
101
  required_names = @one_of.map do |arg_keyword|
101
102
  if arg_keyword.is_a?(Array)
102
- names = arg_keyword.map { |arg| arg_keyword_to_grapqhl_name(argument_definitions, arg) }
103
+ names = arg_keyword.map { |arg| arg_keyword_to_graphql_name(argument_definitions, arg) }
104
+ names.compact! # hidden arguments are `nil`
103
105
  "(" + names.join(" and ") + ")"
104
106
  else
105
- arg_keyword_to_grapqhl_name(argument_definitions, arg_keyword)
107
+ arg_keyword_to_graphql_name(argument_definitions, arg_keyword)
106
108
  end
107
109
  end
110
+ required_names.compact! # remove entries for hidden arguments
111
+
108
112
 
109
- if required_names.size == 1
113
+ case required_names.size
114
+ when 0
115
+ # The required definitions were hidden from the client.
116
+ # Another option here would be to raise an error in the application....
117
+ "%{validated} is missing a required argument."
118
+ when 1
110
119
  "%{validated} must include the following argument: #{required_names.first}."
111
120
  else
112
121
  "%{validated} must include exactly one of the following arguments: #{required_names.join(", ")}."
113
122
  end
114
123
  end
115
124
 
116
- def arg_keyword_to_grapqhl_name(argument_definitions, arg_keyword)
125
+ def arg_keyword_to_graphql_name(argument_definitions, arg_keyword)
117
126
  argument_definition = argument_definitions.find { |defn| defn.keyword == arg_keyword }
118
- argument_definition.graphql_name
127
+ argument_definition&.graphql_name
119
128
  end
120
129
  end
121
130
  end
@@ -76,10 +76,10 @@ module GraphQL
76
76
  end
77
77
  end
78
78
 
79
- def initialize(context:, schema:, name: nil)
79
+ def initialize(context:, schema:, name: nil, visibility:)
80
80
  @name = name
81
81
  @skip_error = context[:skip_visibility_migration_error] || context.is_a?(Query::NullContext) || context.is_a?(Hash)
82
- @profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema)
82
+ @profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema, visibility: visibility)
83
83
  if !@skip_error
84
84
  context[:visibility_migration_running] = true
85
85
  warden_ctx_vals = context.to_h.dup