graphql 2.3.7 → 2.4.0

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +46 -0
  3. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  4. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  5. data/lib/generators/graphql/type_generator.rb +1 -1
  6. data/lib/graphql/analysis/field_usage.rb +1 -1
  7. data/lib/graphql/analysis/query_complexity.rb +3 -3
  8. data/lib/graphql/analysis/visitor.rb +7 -6
  9. data/lib/graphql/analysis.rb +1 -1
  10. data/lib/graphql/current.rb +52 -0
  11. data/lib/graphql/dataloader/async_dataloader.rb +3 -2
  12. data/lib/graphql/dataloader/source.rb +6 -3
  13. data/lib/graphql/dataloader.rb +35 -11
  14. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  15. data/lib/graphql/execution/interpreter/resolve.rb +10 -6
  16. data/lib/graphql/execution/interpreter/runtime.rb +29 -25
  17. data/lib/graphql/execution/interpreter.rb +2 -0
  18. data/lib/graphql/execution/lookahead.rb +10 -10
  19. data/lib/graphql/introspection/directive_type.rb +1 -1
  20. data/lib/graphql/introspection/entry_points.rb +2 -2
  21. data/lib/graphql/introspection/field_type.rb +1 -1
  22. data/lib/graphql/introspection/schema_type.rb +6 -11
  23. data/lib/graphql/introspection/type_type.rb +5 -5
  24. data/lib/graphql/invalid_null_error.rb +1 -1
  25. data/lib/graphql/language/comment.rb +18 -0
  26. data/lib/graphql/language/document_from_schema_definition.rb +55 -28
  27. data/lib/graphql/language/lexer.rb +15 -15
  28. data/lib/graphql/language/nodes.rb +24 -16
  29. data/lib/graphql/language/parser.rb +14 -1
  30. data/lib/graphql/language/printer.rb +23 -7
  31. data/lib/graphql/language/sanitized_printer.rb +1 -1
  32. data/lib/graphql/language.rb +6 -6
  33. data/lib/graphql/query/context.rb +7 -1
  34. data/lib/graphql/query/null_context.rb +2 -2
  35. data/lib/graphql/query.rb +60 -8
  36. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  37. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  38. data/lib/graphql/rubocop.rb +2 -0
  39. data/lib/graphql/schema/addition.rb +1 -0
  40. data/lib/graphql/schema/always_visible.rb +6 -2
  41. data/lib/graphql/schema/argument.rb +14 -1
  42. data/lib/graphql/schema/build_from_definition.rb +9 -1
  43. data/lib/graphql/schema/directive/flagged.rb +1 -1
  44. data/lib/graphql/schema/enum.rb +54 -20
  45. data/lib/graphql/schema/enum_value.rb +9 -1
  46. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  47. data/lib/graphql/schema/field.rb +92 -37
  48. data/lib/graphql/schema/has_single_input_argument.rb +2 -1
  49. data/lib/graphql/schema/input_object.rb +27 -13
  50. data/lib/graphql/schema/interface.rb +21 -4
  51. data/lib/graphql/schema/introspection_system.rb +5 -16
  52. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  53. data/lib/graphql/schema/member/has_arguments.rb +14 -9
  54. data/lib/graphql/schema/member/has_fields.rb +8 -6
  55. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  56. data/lib/graphql/schema/printer.rb +1 -0
  57. data/lib/graphql/schema/resolver.rb +7 -9
  58. data/lib/graphql/schema/type_expression.rb +2 -2
  59. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  60. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  61. data/lib/graphql/schema/validator.rb +2 -0
  62. data/lib/graphql/schema/visibility/migration.rb +186 -0
  63. data/lib/graphql/schema/visibility/profile.rb +523 -0
  64. data/lib/graphql/schema/visibility.rb +75 -0
  65. data/lib/graphql/schema/warden.rb +156 -10
  66. data/lib/graphql/schema.rb +283 -85
  67. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  68. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  69. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  70. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  71. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  72. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  73. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +3 -2
  74. data/lib/graphql/static_validation/rules/fields_will_merge.rb +8 -7
  75. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  76. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  77. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  78. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  79. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  80. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -3
  81. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  82. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  83. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  84. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  85. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  86. data/lib/graphql/static_validation/validation_context.rb +17 -2
  87. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
  88. data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
  89. data/lib/graphql/subscriptions/event.rb +1 -1
  90. data/lib/graphql/subscriptions.rb +5 -3
  91. data/lib/graphql/testing/helpers.rb +9 -5
  92. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  93. data/lib/graphql/types/relay/connection_behaviors.rb +10 -0
  94. data/lib/graphql/types/relay/edge_behaviors.rb +10 -0
  95. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  96. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  97. data/lib/graphql/version.rb +1 -1
  98. data/lib/graphql.rb +3 -0
  99. metadata +29 -8
  100. data/lib/graphql/language/token.rb +0 -34
  101. data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -5,7 +5,6 @@ require "graphql/schema/always_visible"
5
5
  require "graphql/schema/base_64_encoder"
6
6
  require "graphql/schema/find_inherited_value"
7
7
  require "graphql/schema/finder"
8
- require "graphql/schema/invalid_type_error"
9
8
  require "graphql/schema/introspection_system"
10
9
  require "graphql/schema/late_bound_type"
11
10
  require "graphql/schema/null_mask"
@@ -46,6 +45,7 @@ require "graphql/schema/mutation"
46
45
  require "graphql/schema/has_single_input_argument"
47
46
  require "graphql/schema/relay_classic_mutation"
48
47
  require "graphql/schema/subscription"
48
+ require "graphql/schema/visibility"
49
49
 
50
50
  module GraphQL
51
51
  # A GraphQL schema which may be queried with {GraphQL::Query}.
@@ -162,6 +162,7 @@ module GraphQL
162
162
  # re-apply them here
163
163
  mods = trace_modules_for(:default)
164
164
  mods.each { |mod| new_class.include(mod) }
165
+ new_class.include(DefaultTraceClass)
165
166
  trace_mode(:default, new_class)
166
167
  backtrace_class = Class.new(new_class)
167
168
  backtrace_class.include(GraphQL::Backtrace::Trace)
@@ -204,24 +205,19 @@ module GraphQL
204
205
  @own_trace_modes ||= {}
205
206
  end
206
207
 
207
- module DefaultTraceClass
208
- end
209
-
210
- private_constant :DefaultTraceClass
211
-
212
208
  def build_trace_mode(mode)
213
209
  case mode
214
210
  when :default
215
211
  # Use the superclass's default mode if it has one, or else start an inheritance chain at the built-in base class.
216
- base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || GraphQL::Tracing::Trace
217
- Class.new(base_class) do
212
+ base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode, build: true)) || GraphQL::Tracing::Trace
213
+ const_set(:DefaultTrace, Class.new(base_class) do
218
214
  include DefaultTraceClass
219
- end
215
+ end)
220
216
  when :default_backtrace
221
217
  schema_base_class = trace_class_for(:default, build: true)
222
- Class.new(schema_base_class) do
218
+ const_set(:DefaultTraceBacktrace, Class.new(schema_base_class) do
223
219
  include(GraphQL::Backtrace::Trace)
224
- end
220
+ end)
225
221
  else
226
222
  # First, see if the superclass has a custom-defined class for this.
227
223
  # Then, if it doesn't, use this class's default trace
@@ -321,6 +317,9 @@ module GraphQL
321
317
  GraphQL::StaticValidation::Validator.new(schema: self)
322
318
  end
323
319
 
320
+ # Add `plugin` to this schema
321
+ # @param plugin [#use] A Schema plugin
322
+ # @return void
324
323
  def use(plugin, **kwargs)
325
324
  if kwargs.any?
326
325
  plugin.use(self, **kwargs)
@@ -338,6 +337,10 @@ module GraphQL
338
337
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
339
338
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
340
339
  def types(context = GraphQL::Query::NullContext.instance)
340
+ if use_visibility_profile?
341
+ types = Visibility::Profile.from_context(context, self)
342
+ return types.all_types_h
343
+ end
341
344
  all_types = non_introspection_types.merge(introspection_system.types)
342
345
  visible_types = {}
343
346
  all_types.each do |k, v|
@@ -363,27 +366,36 @@ module GraphQL
363
366
  end
364
367
 
365
368
  # @param type_name [String]
369
+ # @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
370
+ # @param use_visibility_profile Private, for migration to {Schema::Visibility}
366
371
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
367
- def get_type(type_name, context = GraphQL::Query::NullContext.instance)
372
+ def get_type(type_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
373
+ if use_visibility_profile
374
+ return Visibility::Profile.from_context(context, self).type(type_name)
375
+ end
368
376
  local_entry = own_types[type_name]
369
377
  type_defn = case local_entry
370
378
  when nil
371
379
  nil
372
380
  when Array
373
- visible_t = nil
374
- warden = Warden.from_context(context)
375
- local_entry.each do |t|
376
- if warden.visible_type?(t, context)
377
- if visible_t.nil?
378
- visible_t = t
379
- else
380
- raise DuplicateNamesError.new(
381
- duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
382
- )
381
+ if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
382
+ local_entry
383
+ else
384
+ visible_t = nil
385
+ warden = Warden.from_context(context)
386
+ local_entry.each do |t|
387
+ if warden.visible_type?(t, context)
388
+ if visible_t.nil?
389
+ visible_t = t
390
+ else
391
+ raise DuplicateNamesError.new(
392
+ duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
393
+ )
394
+ end
383
395
  end
384
396
  end
397
+ visible_t
385
398
  end
386
- visible_t
387
399
  when Module
388
400
  local_entry
389
401
  else
@@ -392,7 +404,7 @@ module GraphQL
392
404
 
393
405
  type_defn ||
394
406
  introspection_system.types[type_name] || # todo context-specific introspection?
395
- (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context) : nil)
407
+ (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context, use_visibility_profile) : nil)
396
408
  end
397
409
 
398
410
  # @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility?
@@ -419,55 +431,92 @@ module GraphQL
419
431
  end
420
432
  end
421
433
 
422
- def new_connections?
423
- !!connections
424
- end
425
-
426
- def query(new_query_object = nil)
427
- if new_query_object
434
+ # Get or set the root `query { ... }` object for this schema.
435
+ #
436
+ # @example Using `Types::Query` as the entry-point
437
+ # query { Types::Query }
438
+ #
439
+ # @param new_query_object [Class<GraphQL::Schema::Object>] The root type to use for queries
440
+ # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root query type.
441
+ # @return [Class<GraphQL::Schema::Object>, nil] The configured query root type, if there is one.
442
+ def query(new_query_object = nil, &lazy_load_block)
443
+ if new_query_object || block_given?
428
444
  if @query_object
429
- raise GraphQL::Error, "Second definition of `query(...)` (#{new_query_object.inspect}) is invalid, already configured with #{@query_object.inspect}"
445
+ dup_defn = new_query_object || yield
446
+ raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
447
+ elsif use_visibility_profile?
448
+ @query_object = block_given? ? lazy_load_block : new_query_object
430
449
  else
431
- @query_object = new_query_object
432
- add_type_and_traverse(new_query_object, root: true)
433
- nil
450
+ @query_object = new_query_object || lazy_load_block.call
451
+ add_type_and_traverse(@query_object, root: true)
434
452
  end
453
+ nil
454
+ elsif @query_object.is_a?(Proc)
455
+ @query_object = @query_object.call
435
456
  else
436
457
  @query_object || find_inherited_value(:query)
437
458
  end
438
459
  end
439
460
 
440
- def mutation(new_mutation_object = nil)
441
- if new_mutation_object
461
+ # Get or set the root `mutation { ... }` object for this schema.
462
+ #
463
+ # @example Using `Types::Mutation` as the entry-point
464
+ # mutation { Types::Mutation }
465
+ #
466
+ # @param new_mutation_object [Class<GraphQL::Schema::Object>] The root type to use for mutations
467
+ # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root mutation type.
468
+ # @return [Class<GraphQL::Schema::Object>, nil] The configured mutation root type, if there is one.
469
+ def mutation(new_mutation_object = nil, &lazy_load_block)
470
+ if new_mutation_object || block_given?
442
471
  if @mutation_object
443
- raise GraphQL::Error, "Second definition of `mutation(...)` (#{new_mutation_object.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
472
+ dup_defn = new_mutation_object || yield
473
+ raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
474
+ elsif use_visibility_profile?
475
+ @mutation_object = block_given? ? lazy_load_block : new_mutation_object
444
476
  else
445
- @mutation_object = new_mutation_object
446
- add_type_and_traverse(new_mutation_object, root: true)
447
- nil
477
+ @mutation_object = new_mutation_object || lazy_load_block.call
478
+ add_type_and_traverse(@mutation_object, root: true)
448
479
  end
480
+ nil
481
+ elsif @mutation_object.is_a?(Proc)
482
+ @mutation_object = @mutation_object.call
449
483
  else
450
484
  @mutation_object || find_inherited_value(:mutation)
451
485
  end
452
486
  end
453
487
 
454
- def subscription(new_subscription_object = nil)
455
- if new_subscription_object
488
+ # Get or set the root `subscription { ... }` object for this schema.
489
+ #
490
+ # @example Using `Types::Subscription` as the entry-point
491
+ # subscription { Types::Subscription }
492
+ #
493
+ # @param new_subscription_object [Class<GraphQL::Schema::Object>] The root type to use for subscriptions
494
+ # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root subscription type.
495
+ # @return [Class<GraphQL::Schema::Object>, nil] The configured subscription root type, if there is one.
496
+ def subscription(new_subscription_object = nil, &lazy_load_block)
497
+ if new_subscription_object || block_given?
456
498
  if @subscription_object
457
- raise GraphQL::Error, "Second definition of `subscription(...)` (#{new_subscription_object.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
499
+ dup_defn = new_subscription_object || yield
500
+ raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
501
+ elsif use_visibility_profile?
502
+ @subscription_object = block_given? ? lazy_load_block : new_subscription_object
503
+ add_subscription_extension_if_necessary
458
504
  else
459
- @subscription_object = new_subscription_object
505
+ @subscription_object = new_subscription_object || lazy_load_block.call
460
506
  add_subscription_extension_if_necessary
461
- add_type_and_traverse(new_subscription_object, root: true)
462
- nil
507
+ add_type_and_traverse(@subscription_object, root: true)
463
508
  end
509
+ nil
510
+ elsif @subscription_object.is_a?(Proc)
511
+ @subscription_object = @subscription_object.call
512
+ add_subscription_extension_if_necessary
513
+ @subscription_object
464
514
  else
465
515
  @subscription_object || find_inherited_value(:subscription)
466
516
  end
467
517
  end
468
518
 
469
- # @see [GraphQL::Schema::Warden] Restricted access to root types
470
- # @return [GraphQL::ObjectType, nil]
519
+ # @api private
471
520
  def root_type_for_operation(operation)
472
521
  case operation
473
522
  when "query"
@@ -481,10 +530,16 @@ module GraphQL
481
530
  end
482
531
  end
483
532
 
533
+ # @return [Array<Class>] The root types (query, mutation, subscription) defined for this schema
484
534
  def root_types
485
- @root_types
535
+ if use_visibility_profile?
536
+ [query, mutation, subscription].compact
537
+ else
538
+ @root_types
539
+ end
486
540
  end
487
541
 
542
+ # @api private
488
543
  def warden_class
489
544
  if defined?(@warden_class)
490
545
  @warden_class
@@ -495,12 +550,48 @@ module GraphQL
495
550
  end
496
551
  end
497
552
 
553
+ # @api private
498
554
  attr_writer :warden_class
499
555
 
556
+ # @api private
557
+ def visibility_profile_class
558
+ if defined?(@visibility_profile_class)
559
+ @visibility_profile_class
560
+ elsif superclass.respond_to?(:visibility_profile_class)
561
+ superclass.visibility_profile_class
562
+ else
563
+ GraphQL::Schema::Visibility::Profile
564
+ end
565
+ end
566
+
567
+ # @api private
568
+ attr_writer :visibility_profile_class, :use_visibility_profile
569
+ # @api private
570
+ attr_accessor :visibility
571
+ # @api private
572
+ def use_visibility_profile?
573
+ if defined?(@use_visibility_profile)
574
+ @use_visibility_profile
575
+ elsif superclass.respond_to?(:use_visibility_profile?)
576
+ superclass.use_visibility_profile?
577
+ else
578
+ false
579
+ end
580
+ end
581
+
500
582
  # @param type [Module] The type definition whose possible types you want to see
583
+ # @param context [GraphQL::Query::Context] used for filtering visible possible types at runtime
584
+ # @param use_visibility_profile Private, for migration to {Schema::Visibility}
501
585
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
502
586
  # @return [Array<Module>] Possible types for `type`, if it's given.
503
- def possible_types(type = nil, context = GraphQL::Query::NullContext.instance)
587
+ def possible_types(type = nil, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
588
+ if use_visibility_profile
589
+ if type
590
+ return Visibility::Profile.from_context(context, self).possible_types(type)
591
+ else
592
+ raise "Schema.possible_types is not implemented for `use_visibility_profile?`"
593
+ end
594
+ end
504
595
  if type
505
596
  # TODO duck-typing `.possible_types` would probably be nicer here
506
597
  if type.kind.union?
@@ -518,7 +609,7 @@ module GraphQL
518
609
  introspection_system.possible_types[type] ||
519
610
  (
520
611
  superclass.respond_to?(:possible_types) ?
521
- superclass.possible_types(type, context) :
612
+ superclass.possible_types(type, context, use_visibility_profile) :
522
613
  EMPTY_ARRAY
523
614
  )
524
615
  end
@@ -573,9 +664,8 @@ module GraphQL
573
664
  end
574
665
  end
575
666
 
576
- def type_from_ast(ast_node, context: nil)
577
- type_owner = context ? context.warden : self
578
- GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node)
667
+ def type_from_ast(ast_node, context: self.query_class.new(self, "{ __typename }").context)
668
+ GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
579
669
  end
580
670
 
581
671
  def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance)
@@ -722,6 +812,7 @@ module GraphQL
722
812
  res[:errors]
723
813
  end
724
814
 
815
+ # @param new_query_class [Class<GraphQL::Query>] A subclass to use when executing queries
725
816
  def query_class(new_query_class = NOT_CONFIGURED)
726
817
  if NOT_CONFIGURED.equal?(new_query_class)
727
818
  @query_class || (superclass.respond_to?(:query_class) ? superclass.query_class : GraphQL::Query)
@@ -732,13 +823,11 @@ module GraphQL
732
823
 
733
824
  attr_writer :validate_max_errors
734
825
 
735
- def validate_max_errors(new_validate_max_errors = nil)
736
- if new_validate_max_errors
737
- @validate_max_errors = new_validate_max_errors
738
- elsif defined?(@validate_max_errors)
739
- @validate_max_errors
826
+ def validate_max_errors(new_validate_max_errors = NOT_CONFIGURED)
827
+ if NOT_CONFIGURED.equal?(new_validate_max_errors)
828
+ defined?(@validate_max_errors) ? @validate_max_errors : find_inherited_value(:validate_max_errors)
740
829
  else
741
- find_inherited_value(:validate_max_errors)
830
+ @validate_max_errors = new_validate_max_errors
742
831
  end
743
832
  end
744
833
 
@@ -769,16 +858,6 @@ module GraphQL
769
858
  @analysis_engine || find_inherited_value(:analysis_engine, self.default_analysis_engine)
770
859
  end
771
860
 
772
- def using_ast_analysis?
773
- true
774
- end
775
-
776
- def interpreter?
777
- true
778
- end
779
-
780
- attr_writer :interpreter
781
-
782
861
  def error_bubbling(new_error_bubbling = nil)
783
862
  if !new_error_bubbling.nil?
784
863
  warn("error_bubbling(#{new_error_bubbling.inspect}) is deprecated; the default value of `false` will be the only option in GraphQL-Ruby 3.0")
@@ -887,7 +966,7 @@ module GraphQL
887
966
  To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
888
967
  ERR
889
968
  end
890
- add_type_and_traverse(new_orphan_types, root: false)
969
+ add_type_and_traverse(new_orphan_types, root: false) unless use_visibility_profile?
891
970
  own_orphan_types.concat(new_orphan_types.flatten)
892
971
  end
893
972
 
@@ -919,6 +998,8 @@ module GraphQL
919
998
  end
920
999
  end
921
1000
 
1001
+
1002
+ # @param new_default_logger [#log] Something to use for logging messages
922
1003
  def default_logger(new_default_logger = NOT_CONFIGURED)
923
1004
  if NOT_CONFIGURED.equal?(new_default_logger)
924
1005
  if defined?(@default_logger)
@@ -939,6 +1020,7 @@ module GraphQL
939
1020
  end
940
1021
  end
941
1022
 
1023
+ # @param new_context_class [Class<GraphQL::Query::Context>] A subclass to use when executing queries
942
1024
  def context_class(new_context_class = nil)
943
1025
  if new_context_class
944
1026
  @context_class = new_context_class
@@ -947,6 +1029,20 @@ module GraphQL
947
1029
  end
948
1030
  end
949
1031
 
1032
+ # Register a handler for errors raised during execution. The handlers can return a new value or raise a new error.
1033
+ #
1034
+ # @example Handling "not found" with a client-facing error
1035
+ # rescue_from(ActiveRecord::NotFound) { raise GraphQL::ExecutionError, "An object could not be found" }
1036
+ #
1037
+ # @param err_classes [Array<StandardError>] Classes which should be rescued by `handler_block`
1038
+ # @param handler_block The code to run when one of those errors is raised during execution
1039
+ # @yieldparam error [StandardError] An instance of one of the configured `err_classes`
1040
+ # @yieldparam object [Object] The current application object in the query when the error was raised
1041
+ # @yieldparam arguments [GraphQL::Query::Arguments] The current field arguments when the error was raised
1042
+ # @yieldparam context [GraphQL::Query::Context] The context for the currently-running operation
1043
+ # @yieldreturn [Object] Some object to use in the place where this error was raised
1044
+ # @raise [GraphQL::ExecutionError] In the handler, raise to add a client-facing error to the response
1045
+ # @raise [StandardError] In the handler, raise to crash the query with a developer-facing error
950
1046
  def rescue_from(*err_classes, &handler_block)
951
1047
  err_classes.each do |err_class|
952
1048
  Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block)
@@ -1013,8 +1109,24 @@ module GraphQL
1013
1109
  end
1014
1110
  end
1015
1111
 
1016
- def resolve_type(type, obj, ctx)
1017
- raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(type, obj, ctx) must be implemented to use Union types, Interface types, or `loads:` (tried to resolve: #{type.name})"
1112
+ # GraphQL-Ruby calls this method during execution when it needs the application to determine the type to use for an object.
1113
+ #
1114
+ # Usually, this object was returned from a field whose return type is an {GraphQL::Schema::Interface} or a {GraphQL::Schema::Union}.
1115
+ # But this method is called in other cases, too -- for example, when {GraphQL::Schema::Argument.loads} cases an object to be directly loaded from the database.
1116
+ #
1117
+ # @example Returning a GraphQL type based on the object's class name
1118
+ # class MySchema < GraphQL::Schema
1119
+ # def resolve_type(_abs_type, object, _context)
1120
+ # graphql_type_name = "Types::#{object.class.name}Type"
1121
+ # graphql_type_name.constantize # If this raises a NameError, then come implement special cases in this method
1122
+ # end
1123
+ # end
1124
+ # @param abstract_type [Class, Module, nil] The Interface or Union type which is being resolved, if there is one
1125
+ # @param application_object [Object] The object returned from a field whose type must be determined
1126
+ # @param context [GraphQL::Query::Context] The query context for the currently-executing query
1127
+ # @return [Class<GraphQL::Schema::Object] The Object type definition to use for `obj`
1128
+ def resolve_type(abstract_type, application_object, context)
1129
+ raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(abstract_type, application_object, context) must be implemented to use Union types, Interface types, or `loads:` (tried to resolve: #{abstract_type.name})"
1018
1130
  end
1019
1131
  # rubocop:enable Lint/DuplicateMethods
1020
1132
 
@@ -1029,15 +1141,45 @@ module GraphQL
1029
1141
  child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
1030
1142
  end
1031
1143
  child_class.singleton_class.prepend(ResolveTypeWithType)
1032
- super
1033
- end
1034
1144
 
1035
- def object_from_id(node_id, ctx)
1036
- raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(node_id, ctx) must be implemented to load by ID (tried to load from id `#{node_id}`)"
1145
+ if use_visibility_profile?
1146
+ vis = self.visibility
1147
+ child_class.visibility = vis.dup_for(child_class)
1148
+ end
1149
+ super
1037
1150
  end
1038
1151
 
1039
- def id_from_object(object, type, ctx)
1040
- raise GraphQL::RequiredImplementationMissingError, "#{self.name}.id_from_object(object, type, ctx) must be implemented to create global ids (tried to create an id for `#{object.inspect}`)"
1152
+ # Fetch an object based on an incoming ID and the current context. This method should return an object
1153
+ # from your application, or return `nil` if there is no object or the object shouldn't be available to this operation.
1154
+ #
1155
+ # @example Fetching an object with Rails's GlobalID
1156
+ # def self.object_from_id(object_id, _context)
1157
+ # GlobalID.find(global_id)
1158
+ # # TODO: use `context[:current_user]` to determine if this object is authorized.
1159
+ # end
1160
+ # @param object_id [String] The ID to fetch an object for. This may be client-provided (as in `node(id: ...)` or `loads:`) or previously stored by the schema (eg, by the `ObjectCache`)
1161
+ # @param context [GraphQL::Query::Context] The context for the currently-executing operation
1162
+ # @return [Object, nil] The application which `object_id` references, or `nil` if there is no object or the current operation shouldn't have access to the object
1163
+ # @see id_from_object which produces these IDs
1164
+ def object_from_id(object_id, context)
1165
+ raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(object_id, context) must be implemented to load by ID (tried to load from id `#{object_id}`)"
1166
+ end
1167
+
1168
+ # Return a stable ID string for `object` so that it can be refetched later, using {.object_from_id}.
1169
+ #
1170
+ # {GlobalID}(https://github.com/rails/globalid) and {SQIDs}(https://sqids.org/ruby) can both be used to create IDs.
1171
+ #
1172
+ # @example Using Rails's GlobalID to generate IDs
1173
+ # def self.id_from_object(application_object, graphql_type, context)
1174
+ # application_object.to_gid_param
1175
+ # end
1176
+ #
1177
+ # @param application_object [Object] Some object encountered by GraphQL-Ruby while running a query
1178
+ # @param graphql_type [Class, Module] The type that GraphQL-Ruby is using for `application_object` during this query
1179
+ # @param context [GraphQL::Query::Context] The context for the operation that is currently running
1180
+ # @return [String] A stable identifier which can be passed to {.object_from_id} later to re-fetch `application_object`
1181
+ def id_from_object(application_object, graphql_type, context)
1182
+ raise GraphQL::RequiredImplementationMissingError, "#{self.name}.id_from_object(application_object, graphql_type, context) must be implemented to create global ids (tried to create an id for `#{application_object.inspect}`)"
1041
1183
  end
1042
1184
 
1043
1185
  def visible?(member, ctx)
@@ -1053,6 +1195,10 @@ module GraphQL
1053
1195
  Member::HasDirectives.get_directives(self, @own_schema_directives, :schema_directives)
1054
1196
  end
1055
1197
 
1198
+ # Called when a type is needed by name at runtime
1199
+ def load_type(type_name, ctx)
1200
+ get_type(type_name, ctx)
1201
+ end
1056
1202
  # This hook is called when an object fails an `authorized?` check.
1057
1203
  # You might report to your bug tracker here, so you can correct
1058
1204
  # the field resolvers not to return unauthorized objects.
@@ -1088,6 +1234,16 @@ module GraphQL
1088
1234
  unauthorized_object(unauthorized_error)
1089
1235
  end
1090
1236
 
1237
+ # Called at runtime when GraphQL-Ruby encounters a mismatch between the application behavior
1238
+ # and the GraphQL type system.
1239
+ #
1240
+ # The default implementation of this method is to follow the GraphQL specification,
1241
+ # but you can override this to report errors to your bug tracker or customize error handling.
1242
+ # @param type_error [GraphQL::Error] several specific error classes are passed here, see the default implementation for details
1243
+ # @param context [GraphQL::Query::Context] the context for the currently-running operation
1244
+ # @return [void]
1245
+ # @raise [GraphQL::ExecutionError] to return this error to the client
1246
+ # @raise [GraphQL::Error] to crash the query and raise a developer-facing error
1091
1247
  def type_error(type_error, ctx)
1092
1248
  case type_error
1093
1249
  when GraphQL::InvalidNullError
@@ -1142,7 +1298,11 @@ module GraphQL
1142
1298
  # @param new_directive [Class]
1143
1299
  # @return void
1144
1300
  def directive(new_directive)
1145
- add_type_and_traverse(new_directive, root: false)
1301
+ if use_visibility_profile?
1302
+ own_directives[new_directive.graphql_name] = new_directive
1303
+ else
1304
+ add_type_and_traverse(new_directive, root: false)
1305
+ end
1146
1306
  end
1147
1307
 
1148
1308
  def default_directives
@@ -1179,6 +1339,7 @@ module GraphQL
1179
1339
  # @param mode [Symbol] Trace module will only be used for this trade mode
1180
1340
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1181
1341
  # @return [void]
1342
+ # @see GraphQL::Tracing::Trace for available tracing methods
1182
1343
  def trace_with(trace_mod, mode: :default, **options)
1183
1344
  if mode.is_a?(Array)
1184
1345
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
@@ -1256,6 +1417,8 @@ module GraphQL
1256
1417
  trace_class_for_mode.new(**trace_options)
1257
1418
  end
1258
1419
 
1420
+ # @param new_analyzer [Class<GraphQL::Analysis::Analyzer>] An analyzer to run on queries to this schema
1421
+ # @see GraphQL::Analysis the analysis system
1259
1422
  def query_analyzer(new_analyzer)
1260
1423
  own_query_analyzers << new_analyzer
1261
1424
  end
@@ -1264,6 +1427,8 @@ module GraphQL
1264
1427
  find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers
1265
1428
  end
1266
1429
 
1430
+ # @param new_analyzer [Class<GraphQL::Analysis::Analyzer>] An analyzer to run on multiplexes to this schema
1431
+ # @see GraphQL::Analysis the analysis system
1267
1432
  def multiplex_analyzer(new_analyzer)
1268
1433
  own_multiplex_analyzers << new_analyzer
1269
1434
  end
@@ -1336,7 +1501,8 @@ module GraphQL
1336
1501
 
1337
1502
  # @api private
1338
1503
  def add_subscription_extension_if_necessary
1339
- if !defined?(@subscription_extension_added) && subscription && self.subscriptions
1504
+ # TODO: when there's a proper API for extending root types, migrat this to use it.
1505
+ if !defined?(@subscription_extension_added) && @subscription_object.is_a?(Class) && self.subscriptions
1340
1506
  @subscription_extension_added = true
1341
1507
  subscription.all_field_definitions.each do |field|
1342
1508
  if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) }
@@ -1346,6 +1512,11 @@ module GraphQL
1346
1512
  end
1347
1513
  end
1348
1514
 
1515
+ # Called when execution encounters a `SystemStackError`. By default, it adds a client-facing error to the response.
1516
+ # You could modify this method to report this error to your bug tracker.
1517
+ # @param query [GraphQL::Query]
1518
+ # @param err [SystemStackError]
1519
+ # @return [void]
1349
1520
  def query_stack_error(query, err)
1350
1521
  query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute."))
1351
1522
  end
@@ -1404,11 +1575,34 @@ module GraphQL
1404
1575
  end
1405
1576
  end
1406
1577
 
1578
+ # Returns `DidYouMean` if it's defined.
1579
+ # Override this to return `nil` if you don't want to use `DidYouMean`
1580
+ def did_you_mean(new_dym = NOT_CONFIGURED)
1581
+ if NOT_CONFIGURED.equal?(new_dym)
1582
+ if defined?(@did_you_mean)
1583
+ @did_you_mean
1584
+ else
1585
+ find_inherited_value(:did_you_mean, defined?(DidYouMean) ? DidYouMean : nil)
1586
+ end
1587
+ else
1588
+ @did_you_mean = new_dym
1589
+ end
1590
+ end
1591
+
1407
1592
  private
1408
1593
 
1409
1594
  def add_trace_options_for(mode, new_options)
1410
- t_opts = trace_options_for(mode)
1411
- t_opts.merge!(new_options)
1595
+ if mode == :default
1596
+ own_trace_modes.each do |mode_name, t_class|
1597
+ if t_class <= DefaultTraceClass
1598
+ t_opts = trace_options_for(mode_name)
1599
+ t_opts.merge!(new_options)
1600
+ end
1601
+ end
1602
+ else
1603
+ t_opts = trace_options_for(mode)
1604
+ t_opts.merge!(new_options)
1605
+ end
1412
1606
  nil
1413
1607
  end
1414
1608
 
@@ -1485,7 +1679,7 @@ module GraphQL
1485
1679
  end
1486
1680
 
1487
1681
  def own_references_to
1488
- @own_references_to ||= {}.tap(&:compare_by_identity)
1682
+ @own_references_to ||= {}.compare_by_identity
1489
1683
  end
1490
1684
 
1491
1685
  def non_introspection_types
@@ -1501,7 +1695,7 @@ module GraphQL
1501
1695
  end
1502
1696
 
1503
1697
  def own_possible_types
1504
- @own_possible_types ||= {}.tap(&:compare_by_identity)
1698
+ @own_possible_types ||= {}.compare_by_identity
1505
1699
  end
1506
1700
 
1507
1701
  def own_union_memberships
@@ -1552,5 +1746,9 @@ module GraphQL
1552
1746
 
1553
1747
  # Install these here so that subclasses will also install it.
1554
1748
  self.connections = GraphQL::Pagination::Connections.new(schema: self)
1749
+
1750
+ # @api private
1751
+ module DefaultTraceClass
1752
+ end
1555
1753
  end
1556
1754
  end