graphql 2.3.8 → 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 (73) 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.rb +1 -1
  7. data/lib/graphql/current.rb +52 -0
  8. data/lib/graphql/dataloader/async_dataloader.rb +3 -2
  9. data/lib/graphql/dataloader/source.rb +6 -3
  10. data/lib/graphql/dataloader.rb +35 -11
  11. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  12. data/lib/graphql/execution/interpreter/resolve.rb +10 -6
  13. data/lib/graphql/execution/interpreter/runtime.rb +23 -19
  14. data/lib/graphql/execution/interpreter.rb +2 -0
  15. data/lib/graphql/introspection/entry_points.rb +2 -2
  16. data/lib/graphql/introspection/schema_type.rb +4 -19
  17. data/lib/graphql/invalid_null_error.rb +1 -1
  18. data/lib/graphql/language/comment.rb +18 -0
  19. data/lib/graphql/language/document_from_schema_definition.rb +38 -4
  20. data/lib/graphql/language/lexer.rb +15 -12
  21. data/lib/graphql/language/nodes.rb +24 -16
  22. data/lib/graphql/language/parser.rb +14 -1
  23. data/lib/graphql/language/printer.rb +23 -7
  24. data/lib/graphql/language.rb +6 -5
  25. data/lib/graphql/query/context.rb +4 -2
  26. data/lib/graphql/query/null_context.rb +1 -5
  27. data/lib/graphql/query.rb +49 -20
  28. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  29. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  30. data/lib/graphql/rubocop.rb +2 -0
  31. data/lib/graphql/schema/addition.rb +1 -0
  32. data/lib/graphql/schema/always_visible.rb +6 -3
  33. data/lib/graphql/schema/argument.rb +14 -1
  34. data/lib/graphql/schema/build_from_definition.rb +9 -1
  35. data/lib/graphql/schema/directive/flagged.rb +1 -1
  36. data/lib/graphql/schema/enum.rb +49 -15
  37. data/lib/graphql/schema/enum_value.rb +9 -1
  38. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  39. data/lib/graphql/schema/field.rb +91 -36
  40. data/lib/graphql/schema/input_object.rb +20 -7
  41. data/lib/graphql/schema/interface.rb +21 -4
  42. data/lib/graphql/schema/introspection_system.rb +3 -2
  43. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  44. data/lib/graphql/schema/member/has_arguments.rb +9 -5
  45. data/lib/graphql/schema/member/has_fields.rb +5 -5
  46. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  47. data/lib/graphql/schema/printer.rb +1 -0
  48. data/lib/graphql/schema/resolver.rb +3 -4
  49. data/lib/graphql/schema/validator/all_validator.rb +2 -0
  50. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  51. data/lib/graphql/schema/visibility/migration.rb +186 -0
  52. data/lib/graphql/schema/visibility/profile.rb +523 -0
  53. data/lib/graphql/schema/visibility.rb +75 -0
  54. data/lib/graphql/schema/warden.rb +80 -21
  55. data/lib/graphql/schema.rb +254 -85
  56. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  57. data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
  58. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  59. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +2 -1
  60. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
  61. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
  62. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  63. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
  64. data/lib/graphql/static_validation/validation_context.rb +15 -0
  65. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
  66. data/lib/graphql/subscriptions.rb +3 -1
  67. data/lib/graphql/testing/helpers.rb +7 -3
  68. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  69. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  70. data/lib/graphql/version.rb +1 -1
  71. data/lib/graphql.rb +3 -0
  72. metadata +28 -7
  73. data/lib/graphql/schema/subset.rb +0 -397
@@ -45,7 +45,7 @@ require "graphql/schema/mutation"
45
45
  require "graphql/schema/has_single_input_argument"
46
46
  require "graphql/schema/relay_classic_mutation"
47
47
  require "graphql/schema/subscription"
48
- require "graphql/schema/subset"
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,14 +366,19 @@ 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
- if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
381
+ if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
374
382
  local_entry
375
383
  else
376
384
  visible_t = nil
@@ -396,7 +404,7 @@ module GraphQL
396
404
 
397
405
  type_defn ||
398
406
  introspection_system.types[type_name] || # todo context-specific introspection?
399
- (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)
400
408
  end
401
409
 
402
410
  # @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility?
@@ -423,55 +431,92 @@ module GraphQL
423
431
  end
424
432
  end
425
433
 
426
- def new_connections?
427
- !!connections
428
- end
429
-
430
- def query(new_query_object = nil)
431
- 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?
432
444
  if @query_object
433
- 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
434
449
  else
435
- @query_object = new_query_object
436
- add_type_and_traverse(new_query_object, root: true)
437
- nil
450
+ @query_object = new_query_object || lazy_load_block.call
451
+ add_type_and_traverse(@query_object, root: true)
438
452
  end
453
+ nil
454
+ elsif @query_object.is_a?(Proc)
455
+ @query_object = @query_object.call
439
456
  else
440
457
  @query_object || find_inherited_value(:query)
441
458
  end
442
459
  end
443
460
 
444
- def mutation(new_mutation_object = nil)
445
- 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?
446
471
  if @mutation_object
447
- 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
448
476
  else
449
- @mutation_object = new_mutation_object
450
- add_type_and_traverse(new_mutation_object, root: true)
451
- nil
477
+ @mutation_object = new_mutation_object || lazy_load_block.call
478
+ add_type_and_traverse(@mutation_object, root: true)
452
479
  end
480
+ nil
481
+ elsif @mutation_object.is_a?(Proc)
482
+ @mutation_object = @mutation_object.call
453
483
  else
454
484
  @mutation_object || find_inherited_value(:mutation)
455
485
  end
456
486
  end
457
487
 
458
- def subscription(new_subscription_object = nil)
459
- 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?
460
498
  if @subscription_object
461
- 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
462
504
  else
463
- @subscription_object = new_subscription_object
505
+ @subscription_object = new_subscription_object || lazy_load_block.call
464
506
  add_subscription_extension_if_necessary
465
- add_type_and_traverse(new_subscription_object, root: true)
466
- nil
507
+ add_type_and_traverse(@subscription_object, root: true)
467
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
468
514
  else
469
515
  @subscription_object || find_inherited_value(:subscription)
470
516
  end
471
517
  end
472
518
 
473
- # @see [GraphQL::Schema::Warden] Restricted access to root types
474
- # @return [GraphQL::ObjectType, nil]
519
+ # @api private
475
520
  def root_type_for_operation(operation)
476
521
  case operation
477
522
  when "query"
@@ -485,10 +530,16 @@ module GraphQL
485
530
  end
486
531
  end
487
532
 
533
+ # @return [Array<Class>] The root types (query, mutation, subscription) defined for this schema
488
534
  def root_types
489
- @root_types
535
+ if use_visibility_profile?
536
+ [query, mutation, subscription].compact
537
+ else
538
+ @root_types
539
+ end
490
540
  end
491
541
 
542
+ # @api private
492
543
  def warden_class
493
544
  if defined?(@warden_class)
494
545
  @warden_class
@@ -499,34 +550,48 @@ module GraphQL
499
550
  end
500
551
  end
501
552
 
553
+ # @api private
502
554
  attr_writer :warden_class
503
555
 
504
- def subset_class
505
- if defined?(@subset_class)
506
- @subset_class
507
- elsif superclass.respond_to?(:subset_class)
508
- superclass.subset_class
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
509
562
  else
510
- GraphQL::Schema::Subset
563
+ GraphQL::Schema::Visibility::Profile
511
564
  end
512
565
  end
513
566
 
514
- attr_writer :subset_class, :use_schema_subset
515
-
516
- def use_schema_subset?
517
- if defined?(@use_schema_subset)
518
- @use_schema_subset
519
- elsif superclass.respond_to?(:use_schema_subset?)
520
- superclass.use_schema_subset?
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?
521
577
  else
522
578
  false
523
579
  end
524
580
  end
525
581
 
526
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}
527
585
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
528
586
  # @return [Array<Module>] Possible types for `type`, if it's given.
529
- 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
530
595
  if type
531
596
  # TODO duck-typing `.possible_types` would probably be nicer here
532
597
  if type.kind.union?
@@ -544,7 +609,7 @@ module GraphQL
544
609
  introspection_system.possible_types[type] ||
545
610
  (
546
611
  superclass.respond_to?(:possible_types) ?
547
- superclass.possible_types(type, context) :
612
+ superclass.possible_types(type, context, use_visibility_profile) :
548
613
  EMPTY_ARRAY
549
614
  )
550
615
  end
@@ -747,6 +812,7 @@ module GraphQL
747
812
  res[:errors]
748
813
  end
749
814
 
815
+ # @param new_query_class [Class<GraphQL::Query>] A subclass to use when executing queries
750
816
  def query_class(new_query_class = NOT_CONFIGURED)
751
817
  if NOT_CONFIGURED.equal?(new_query_class)
752
818
  @query_class || (superclass.respond_to?(:query_class) ? superclass.query_class : GraphQL::Query)
@@ -757,13 +823,11 @@ module GraphQL
757
823
 
758
824
  attr_writer :validate_max_errors
759
825
 
760
- def validate_max_errors(new_validate_max_errors = nil)
761
- if new_validate_max_errors
762
- @validate_max_errors = new_validate_max_errors
763
- elsif defined?(@validate_max_errors)
764
- @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)
765
829
  else
766
- find_inherited_value(:validate_max_errors)
830
+ @validate_max_errors = new_validate_max_errors
767
831
  end
768
832
  end
769
833
 
@@ -794,16 +858,6 @@ module GraphQL
794
858
  @analysis_engine || find_inherited_value(:analysis_engine, self.default_analysis_engine)
795
859
  end
796
860
 
797
- def using_ast_analysis?
798
- true
799
- end
800
-
801
- def interpreter?
802
- true
803
- end
804
-
805
- attr_writer :interpreter
806
-
807
861
  def error_bubbling(new_error_bubbling = nil)
808
862
  if !new_error_bubbling.nil?
809
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")
@@ -912,7 +966,7 @@ module GraphQL
912
966
  To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
913
967
  ERR
914
968
  end
915
- add_type_and_traverse(new_orphan_types, root: false)
969
+ add_type_and_traverse(new_orphan_types, root: false) unless use_visibility_profile?
916
970
  own_orphan_types.concat(new_orphan_types.flatten)
917
971
  end
918
972
 
@@ -944,6 +998,8 @@ module GraphQL
944
998
  end
945
999
  end
946
1000
 
1001
+
1002
+ # @param new_default_logger [#log] Something to use for logging messages
947
1003
  def default_logger(new_default_logger = NOT_CONFIGURED)
948
1004
  if NOT_CONFIGURED.equal?(new_default_logger)
949
1005
  if defined?(@default_logger)
@@ -964,6 +1020,7 @@ module GraphQL
964
1020
  end
965
1021
  end
966
1022
 
1023
+ # @param new_context_class [Class<GraphQL::Query::Context>] A subclass to use when executing queries
967
1024
  def context_class(new_context_class = nil)
968
1025
  if new_context_class
969
1026
  @context_class = new_context_class
@@ -972,6 +1029,20 @@ module GraphQL
972
1029
  end
973
1030
  end
974
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
975
1046
  def rescue_from(*err_classes, &handler_block)
976
1047
  err_classes.each do |err_class|
977
1048
  Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block)
@@ -1038,8 +1109,24 @@ module GraphQL
1038
1109
  end
1039
1110
  end
1040
1111
 
1041
- def resolve_type(type, obj, ctx)
1042
- 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})"
1043
1130
  end
1044
1131
  # rubocop:enable Lint/DuplicateMethods
1045
1132
 
@@ -1054,15 +1141,45 @@ module GraphQL
1054
1141
  child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
1055
1142
  end
1056
1143
  child_class.singleton_class.prepend(ResolveTypeWithType)
1057
- super
1058
- end
1059
1144
 
1060
- def object_from_id(node_id, ctx)
1061
- 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
1062
1150
  end
1063
1151
 
1064
- def id_from_object(object, type, ctx)
1065
- 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}`)"
1066
1183
  end
1067
1184
 
1068
1185
  def visible?(member, ctx)
@@ -1117,6 +1234,16 @@ module GraphQL
1117
1234
  unauthorized_object(unauthorized_error)
1118
1235
  end
1119
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
1120
1247
  def type_error(type_error, ctx)
1121
1248
  case type_error
1122
1249
  when GraphQL::InvalidNullError
@@ -1171,7 +1298,11 @@ module GraphQL
1171
1298
  # @param new_directive [Class]
1172
1299
  # @return void
1173
1300
  def directive(new_directive)
1174
- 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
1175
1306
  end
1176
1307
 
1177
1308
  def default_directives
@@ -1208,6 +1339,7 @@ module GraphQL
1208
1339
  # @param mode [Symbol] Trace module will only be used for this trade mode
1209
1340
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1210
1341
  # @return [void]
1342
+ # @see GraphQL::Tracing::Trace for available tracing methods
1211
1343
  def trace_with(trace_mod, mode: :default, **options)
1212
1344
  if mode.is_a?(Array)
1213
1345
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
@@ -1285,6 +1417,8 @@ module GraphQL
1285
1417
  trace_class_for_mode.new(**trace_options)
1286
1418
  end
1287
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
1288
1422
  def query_analyzer(new_analyzer)
1289
1423
  own_query_analyzers << new_analyzer
1290
1424
  end
@@ -1293,6 +1427,8 @@ module GraphQL
1293
1427
  find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers
1294
1428
  end
1295
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
1296
1432
  def multiplex_analyzer(new_analyzer)
1297
1433
  own_multiplex_analyzers << new_analyzer
1298
1434
  end
@@ -1365,7 +1501,8 @@ module GraphQL
1365
1501
 
1366
1502
  # @api private
1367
1503
  def add_subscription_extension_if_necessary
1368
- 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
1369
1506
  @subscription_extension_added = true
1370
1507
  subscription.all_field_definitions.each do |field|
1371
1508
  if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) }
@@ -1375,6 +1512,11 @@ module GraphQL
1375
1512
  end
1376
1513
  end
1377
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]
1378
1520
  def query_stack_error(query, err)
1379
1521
  query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute."))
1380
1522
  end
@@ -1433,11 +1575,34 @@ module GraphQL
1433
1575
  end
1434
1576
  end
1435
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
+
1436
1592
  private
1437
1593
 
1438
1594
  def add_trace_options_for(mode, new_options)
1439
- t_opts = trace_options_for(mode)
1440
- 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
1441
1606
  nil
1442
1607
  end
1443
1608
 
@@ -1514,7 +1679,7 @@ module GraphQL
1514
1679
  end
1515
1680
 
1516
1681
  def own_references_to
1517
- @own_references_to ||= {}.tap(&:compare_by_identity)
1682
+ @own_references_to ||= {}.compare_by_identity
1518
1683
  end
1519
1684
 
1520
1685
  def non_introspection_types
@@ -1530,7 +1695,7 @@ module GraphQL
1530
1695
  end
1531
1696
 
1532
1697
  def own_possible_types
1533
- @own_possible_types ||= {}.tap(&:compare_by_identity)
1698
+ @own_possible_types ||= {}.compare_by_identity
1534
1699
  end
1535
1700
 
1536
1701
  def own_union_memberships
@@ -1581,5 +1746,9 @@ module GraphQL
1581
1746
 
1582
1747
  # Install these here so that subclasses will also install it.
1583
1748
  self.connections = GraphQL::Pagination::Connections.new(schema: self)
1749
+
1750
+ # @api private
1751
+ module DefaultTraceClass
1752
+ end
1584
1753
  end
1585
1754
  end
@@ -10,8 +10,9 @@ module GraphQL
10
10
  elsif parent_defn
11
11
  kind_of_node = node_type(parent)
12
12
  error_arg_name = parent_name(parent, parent_defn)
13
+ arg_names = context.types.arguments(parent_defn).map(&:graphql_name)
13
14
  add_error(GraphQL::StaticValidation::ArgumentsAreDefinedError.new(
14
- "#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'",
15
+ "#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'#{context.did_you_mean_suggestion(node.name, arg_names)}",
15
16
  nodes: node,
16
17
  name: error_arg_name,
17
18
  type: kind_of_node,
@@ -10,8 +10,9 @@ module GraphQL
10
10
  if !@types.directive_exists?(node.name)
11
11
  @directives_are_defined_errors_by_name ||= {}
12
12
  error = @directives_are_defined_errors_by_name[node.name] ||= begin
13
+ @directive_names ||= @types.directives.map(&:graphql_name)
13
14
  err = GraphQL::StaticValidation::DirectivesAreDefinedError.new(
14
- "Directive @#{node.name} is not defined",
15
+ "Directive @#{node.name} is not defined#{context.did_you_mean_suggestion(node.name, @directive_names)}",
15
16
  nodes: [],
16
17
  directive: node.name
17
18
  )
@@ -19,6 +19,7 @@ module GraphQL
19
19
  GraphQL::Schema::Directive::FRAGMENT_DEFINITION => "fragment definitions",
20
20
  GraphQL::Schema::Directive::FRAGMENT_SPREAD => "fragment spreads",
21
21
  GraphQL::Schema::Directive::INLINE_FRAGMENT => "inline fragments",
22
+ GraphQL::Schema::Directive::VARIABLE_DEFINITION => "variable definitions",
22
23
  }
23
24
 
24
25
  SIMPLE_LOCATIONS = {
@@ -26,6 +27,7 @@ module GraphQL
26
27
  Nodes::InlineFragment => GraphQL::Schema::Directive::INLINE_FRAGMENT,
27
28
  Nodes::FragmentSpread => GraphQL::Schema::Directive::FRAGMENT_SPREAD,
28
29
  Nodes::FragmentDefinition => GraphQL::Schema::Directive::FRAGMENT_DEFINITION,
30
+ Nodes::VariableDefinition => GraphQL::Schema::Directive::VARIABLE_DEFINITION,
29
31
  }
30
32
 
31
33
  SIMPLE_LOCATION_NODES = SIMPLE_LOCATIONS.keys
@@ -14,8 +14,9 @@ module GraphQL
14
14
  node_name: parent_type.graphql_name
15
15
  ))
16
16
  else
17
+ message = "Field '#{node.name}' doesn't exist on type '#{parent_type.graphql_name}'#{context.did_you_mean_suggestion(node.name, context.types.fields(parent_type).map(&:graphql_name))}"
17
18
  add_error(GraphQL::StaticValidation::FieldsAreDefinedOnTypeError.new(
18
- "Field '#{node.name}' doesn't exist on type '#{parent_type.graphql_name}'",
19
+ message,
19
20
  nodes: node,
20
21
  field: node.name,
21
22
  type: parent_type.graphql_name
@@ -212,6 +212,7 @@ module GraphQL
212
212
 
213
213
  def find_conflict(response_key, field1, field2, mutually_exclusive: false)
214
214
  return if @conflict_count >= context.max_errors
215
+ return if field1.definition.nil? || field2.definition.nil?
215
216
 
216
217
  node1 = field1.node
217
218
  node2 = field2.node