graphql 2.3.14 → 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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  3. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  4. data/lib/generators/graphql/type_generator.rb +1 -1
  5. data/lib/graphql/analysis.rb +1 -1
  6. data/lib/graphql/dataloader/async_dataloader.rb +3 -2
  7. data/lib/graphql/dataloader/source.rb +1 -1
  8. data/lib/graphql/dataloader.rb +31 -10
  9. data/lib/graphql/execution/interpreter/resolve.rb +10 -6
  10. data/lib/graphql/invalid_null_error.rb +1 -1
  11. data/lib/graphql/language/comment.rb +18 -0
  12. data/lib/graphql/language/document_from_schema_definition.rb +38 -4
  13. data/lib/graphql/language/lexer.rb +15 -12
  14. data/lib/graphql/language/nodes.rb +22 -14
  15. data/lib/graphql/language/parser.rb +5 -0
  16. data/lib/graphql/language/printer.rb +23 -7
  17. data/lib/graphql/language.rb +6 -5
  18. data/lib/graphql/query/null_context.rb +1 -1
  19. data/lib/graphql/query.rb +49 -16
  20. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +23 -8
  21. data/lib/graphql/schema/always_visible.rb +6 -3
  22. data/lib/graphql/schema/argument.rb +14 -1
  23. data/lib/graphql/schema/build_from_definition.rb +1 -0
  24. data/lib/graphql/schema/enum.rb +3 -0
  25. data/lib/graphql/schema/enum_value.rb +9 -1
  26. data/lib/graphql/schema/field.rb +35 -14
  27. data/lib/graphql/schema/input_object.rb +20 -7
  28. data/lib/graphql/schema/interface.rb +1 -0
  29. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  30. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  31. data/lib/graphql/schema/member/has_fields.rb +2 -2
  32. data/lib/graphql/schema/printer.rb +1 -0
  33. data/lib/graphql/schema/resolver.rb +3 -4
  34. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  35. data/lib/graphql/schema/visibility/migration.rb +186 -0
  36. data/lib/graphql/schema/visibility/profile.rb +523 -0
  37. data/lib/graphql/schema/visibility.rb +75 -0
  38. data/lib/graphql/schema/warden.rb +77 -15
  39. data/lib/graphql/schema.rb +203 -61
  40. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  41. data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
  42. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  43. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +2 -1
  44. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
  45. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
  46. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
  47. data/lib/graphql/static_validation/validation_context.rb +15 -0
  48. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
  49. data/lib/graphql/subscriptions.rb +3 -1
  50. data/lib/graphql/testing/helpers.rb +2 -1
  51. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  52. data/lib/graphql/version.rb +1 -1
  53. metadata +11 -9
  54. data/lib/graphql/schema/subset.rb +0 -509
  55. data/lib/graphql/schema/types_migration.rb +0 -187
@@ -45,8 +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"
49
- require "graphql/schema/types_migration"
48
+ require "graphql/schema/visibility"
50
49
 
51
50
  module GraphQL
52
51
  # A GraphQL schema which may be queried with {GraphQL::Query}.
@@ -163,6 +162,7 @@ module GraphQL
163
162
  # re-apply them here
164
163
  mods = trace_modules_for(:default)
165
164
  mods.each { |mod| new_class.include(mod) }
165
+ new_class.include(DefaultTraceClass)
166
166
  trace_mode(:default, new_class)
167
167
  backtrace_class = Class.new(new_class)
168
168
  backtrace_class.include(GraphQL::Backtrace::Trace)
@@ -205,24 +205,19 @@ module GraphQL
205
205
  @own_trace_modes ||= {}
206
206
  end
207
207
 
208
- module DefaultTraceClass
209
- end
210
-
211
- private_constant :DefaultTraceClass
212
-
213
208
  def build_trace_mode(mode)
214
209
  case mode
215
210
  when :default
216
211
  # Use the superclass's default mode if it has one, or else start an inheritance chain at the built-in base class.
217
- base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || GraphQL::Tracing::Trace
218
- 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
219
214
  include DefaultTraceClass
220
- end
215
+ end)
221
216
  when :default_backtrace
222
217
  schema_base_class = trace_class_for(:default, build: true)
223
- Class.new(schema_base_class) do
218
+ const_set(:DefaultTraceBacktrace, Class.new(schema_base_class) do
224
219
  include(GraphQL::Backtrace::Trace)
225
- end
220
+ end)
226
221
  else
227
222
  # First, see if the superclass has a custom-defined class for this.
228
223
  # Then, if it doesn't, use this class's default trace
@@ -322,6 +317,9 @@ module GraphQL
322
317
  GraphQL::StaticValidation::Validator.new(schema: self)
323
318
  end
324
319
 
320
+ # Add `plugin` to this schema
321
+ # @param plugin [#use] A Schema plugin
322
+ # @return void
325
323
  def use(plugin, **kwargs)
326
324
  if kwargs.any?
327
325
  plugin.use(self, **kwargs)
@@ -339,8 +337,9 @@ module GraphQL
339
337
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
340
338
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
341
339
  def types(context = GraphQL::Query::NullContext.instance)
342
- if use_schema_subset?
343
- return Subset.from_context(context, self).all_types_h
340
+ if use_visibility_profile?
341
+ types = Visibility::Profile.from_context(context, self)
342
+ return types.all_types_h
344
343
  end
345
344
  all_types = non_introspection_types.merge(introspection_system.types)
346
345
  visible_types = {}
@@ -367,17 +366,19 @@ module GraphQL
367
366
  end
368
367
 
369
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}
370
371
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
371
- def get_type(type_name, context = GraphQL::Query::NullContext.instance)
372
- if use_schema_subset?
373
- return Subset.from_context(context, self).type(type_name)
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)
374
375
  end
375
376
  local_entry = own_types[type_name]
376
377
  type_defn = case local_entry
377
378
  when nil
378
379
  nil
379
380
  when Array
380
- 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)
381
382
  local_entry
382
383
  else
383
384
  visible_t = nil
@@ -403,7 +404,7 @@ module GraphQL
403
404
 
404
405
  type_defn ||
405
406
  introspection_system.types[type_name] || # todo context-specific introspection?
406
- (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)
407
408
  end
408
409
 
409
410
  # @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility?
@@ -430,12 +431,20 @@ module GraphQL
430
431
  end
431
432
  end
432
433
 
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.
433
442
  def query(new_query_object = nil, &lazy_load_block)
434
443
  if new_query_object || block_given?
435
444
  if @query_object
436
445
  dup_defn = new_query_object || yield
437
446
  raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
438
- elsif use_schema_subset?
447
+ elsif use_visibility_profile?
439
448
  @query_object = block_given? ? lazy_load_block : new_query_object
440
449
  else
441
450
  @query_object = new_query_object || lazy_load_block.call
@@ -449,12 +458,20 @@ module GraphQL
449
458
  end
450
459
  end
451
460
 
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.
452
469
  def mutation(new_mutation_object = nil, &lazy_load_block)
453
470
  if new_mutation_object || block_given?
454
471
  if @mutation_object
455
472
  dup_defn = new_mutation_object || yield
456
473
  raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
457
- elsif use_schema_subset?
474
+ elsif use_visibility_profile?
458
475
  @mutation_object = block_given? ? lazy_load_block : new_mutation_object
459
476
  else
460
477
  @mutation_object = new_mutation_object || lazy_load_block.call
@@ -468,12 +485,20 @@ module GraphQL
468
485
  end
469
486
  end
470
487
 
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.
471
496
  def subscription(new_subscription_object = nil, &lazy_load_block)
472
497
  if new_subscription_object || block_given?
473
498
  if @subscription_object
474
499
  dup_defn = new_subscription_object || yield
475
500
  raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
476
- elsif use_schema_subset?
501
+ elsif use_visibility_profile?
477
502
  @subscription_object = block_given? ? lazy_load_block : new_subscription_object
478
503
  add_subscription_extension_if_necessary
479
504
  else
@@ -491,8 +516,7 @@ module GraphQL
491
516
  end
492
517
  end
493
518
 
494
- # @see [GraphQL::Schema::Warden] Restricted access to root types
495
- # @return [GraphQL::ObjectType, nil]
519
+ # @api private
496
520
  def root_type_for_operation(operation)
497
521
  case operation
498
522
  when "query"
@@ -506,14 +530,16 @@ module GraphQL
506
530
  end
507
531
  end
508
532
 
533
+ # @return [Array<Class>] The root types (query, mutation, subscription) defined for this schema
509
534
  def root_types
510
- if use_schema_subset?
535
+ if use_visibility_profile?
511
536
  [query, mutation, subscription].compact
512
537
  else
513
538
  @root_types
514
539
  end
515
540
  end
516
541
 
542
+ # @api private
517
543
  def warden_class
518
544
  if defined?(@warden_class)
519
545
  @warden_class
@@ -524,39 +550,46 @@ module GraphQL
524
550
  end
525
551
  end
526
552
 
553
+ # @api private
527
554
  attr_writer :warden_class
528
555
 
529
- def subset_class
530
- if defined?(@subset_class)
531
- @subset_class
532
- elsif superclass.respond_to?(:subset_class)
533
- 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
534
562
  else
535
- GraphQL::Schema::Subset
563
+ GraphQL::Schema::Visibility::Profile
536
564
  end
537
565
  end
538
566
 
539
- attr_writer :subset_class, :use_schema_subset
540
-
541
- def use_schema_subset?
542
- if defined?(@use_schema_subset)
543
- @use_schema_subset
544
- elsif superclass.respond_to?(:use_schema_subset?)
545
- 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?
546
577
  else
547
578
  false
548
579
  end
549
580
  end
550
581
 
551
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}
552
585
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
553
586
  # @return [Array<Module>] Possible types for `type`, if it's given.
554
- def possible_types(type = nil, context = GraphQL::Query::NullContext.instance)
555
- if use_schema_subset?
587
+ def possible_types(type = nil, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
588
+ if use_visibility_profile
556
589
  if type
557
- return Subset.from_context(context, self).possible_types(type)
590
+ return Visibility::Profile.from_context(context, self).possible_types(type)
558
591
  else
559
- raise "Schema.possible_types is not implemented for `use_schema_subset?`"
592
+ raise "Schema.possible_types is not implemented for `use_visibility_profile?`"
560
593
  end
561
594
  end
562
595
  if type
@@ -576,7 +609,7 @@ module GraphQL
576
609
  introspection_system.possible_types[type] ||
577
610
  (
578
611
  superclass.respond_to?(:possible_types) ?
579
- superclass.possible_types(type, context) :
612
+ superclass.possible_types(type, context, use_visibility_profile) :
580
613
  EMPTY_ARRAY
581
614
  )
582
615
  end
@@ -779,6 +812,7 @@ module GraphQL
779
812
  res[:errors]
780
813
  end
781
814
 
815
+ # @param new_query_class [Class<GraphQL::Query>] A subclass to use when executing queries
782
816
  def query_class(new_query_class = NOT_CONFIGURED)
783
817
  if NOT_CONFIGURED.equal?(new_query_class)
784
818
  @query_class || (superclass.respond_to?(:query_class) ? superclass.query_class : GraphQL::Query)
@@ -789,13 +823,11 @@ module GraphQL
789
823
 
790
824
  attr_writer :validate_max_errors
791
825
 
792
- def validate_max_errors(new_validate_max_errors = nil)
793
- if new_validate_max_errors
794
- @validate_max_errors = new_validate_max_errors
795
- elsif defined?(@validate_max_errors)
796
- @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)
797
829
  else
798
- find_inherited_value(:validate_max_errors)
830
+ @validate_max_errors = new_validate_max_errors
799
831
  end
800
832
  end
801
833
 
@@ -934,7 +966,7 @@ module GraphQL
934
966
  To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
935
967
  ERR
936
968
  end
937
- add_type_and_traverse(new_orphan_types, root: false) unless use_schema_subset?
969
+ add_type_and_traverse(new_orphan_types, root: false) unless use_visibility_profile?
938
970
  own_orphan_types.concat(new_orphan_types.flatten)
939
971
  end
940
972
 
@@ -966,6 +998,8 @@ module GraphQL
966
998
  end
967
999
  end
968
1000
 
1001
+
1002
+ # @param new_default_logger [#log] Something to use for logging messages
969
1003
  def default_logger(new_default_logger = NOT_CONFIGURED)
970
1004
  if NOT_CONFIGURED.equal?(new_default_logger)
971
1005
  if defined?(@default_logger)
@@ -986,6 +1020,7 @@ module GraphQL
986
1020
  end
987
1021
  end
988
1022
 
1023
+ # @param new_context_class [Class<GraphQL::Query::Context>] A subclass to use when executing queries
989
1024
  def context_class(new_context_class = nil)
990
1025
  if new_context_class
991
1026
  @context_class = new_context_class
@@ -994,6 +1029,20 @@ module GraphQL
994
1029
  end
995
1030
  end
996
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
997
1046
  def rescue_from(*err_classes, &handler_block)
998
1047
  err_classes.each do |err_class|
999
1048
  Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block)
@@ -1060,8 +1109,24 @@ module GraphQL
1060
1109
  end
1061
1110
  end
1062
1111
 
1063
- def resolve_type(type, obj, ctx)
1064
- 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})"
1065
1130
  end
1066
1131
  # rubocop:enable Lint/DuplicateMethods
1067
1132
 
@@ -1076,15 +1141,45 @@ module GraphQL
1076
1141
  child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
1077
1142
  end
1078
1143
  child_class.singleton_class.prepend(ResolveTypeWithType)
1079
- super
1080
- end
1081
1144
 
1082
- def object_from_id(node_id, ctx)
1083
- 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
1084
1150
  end
1085
1151
 
1086
- def id_from_object(object, type, ctx)
1087
- 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}`)"
1088
1183
  end
1089
1184
 
1090
1185
  def visible?(member, ctx)
@@ -1139,6 +1234,16 @@ module GraphQL
1139
1234
  unauthorized_object(unauthorized_error)
1140
1235
  end
1141
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
1142
1247
  def type_error(type_error, ctx)
1143
1248
  case type_error
1144
1249
  when GraphQL::InvalidNullError
@@ -1193,7 +1298,7 @@ module GraphQL
1193
1298
  # @param new_directive [Class]
1194
1299
  # @return void
1195
1300
  def directive(new_directive)
1196
- if use_schema_subset?
1301
+ if use_visibility_profile?
1197
1302
  own_directives[new_directive.graphql_name] = new_directive
1198
1303
  else
1199
1304
  add_type_and_traverse(new_directive, root: false)
@@ -1234,6 +1339,7 @@ module GraphQL
1234
1339
  # @param mode [Symbol] Trace module will only be used for this trade mode
1235
1340
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1236
1341
  # @return [void]
1342
+ # @see GraphQL::Tracing::Trace for available tracing methods
1237
1343
  def trace_with(trace_mod, mode: :default, **options)
1238
1344
  if mode.is_a?(Array)
1239
1345
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
@@ -1311,6 +1417,8 @@ module GraphQL
1311
1417
  trace_class_for_mode.new(**trace_options)
1312
1418
  end
1313
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
1314
1422
  def query_analyzer(new_analyzer)
1315
1423
  own_query_analyzers << new_analyzer
1316
1424
  end
@@ -1319,6 +1427,8 @@ module GraphQL
1319
1427
  find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers
1320
1428
  end
1321
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
1322
1432
  def multiplex_analyzer(new_analyzer)
1323
1433
  own_multiplex_analyzers << new_analyzer
1324
1434
  end
@@ -1402,6 +1512,11 @@ module GraphQL
1402
1512
  end
1403
1513
  end
1404
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]
1405
1520
  def query_stack_error(query, err)
1406
1521
  query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute."))
1407
1522
  end
@@ -1460,11 +1575,34 @@ module GraphQL
1460
1575
  end
1461
1576
  end
1462
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
+
1463
1592
  private
1464
1593
 
1465
1594
  def add_trace_options_for(mode, new_options)
1466
- t_opts = trace_options_for(mode)
1467
- 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
1468
1606
  nil
1469
1607
  end
1470
1608
 
@@ -1608,5 +1746,9 @@ module GraphQL
1608
1746
 
1609
1747
  # Install these here so that subclasses will also install it.
1610
1748
  self.connections = GraphQL::Pagination::Connections.new(schema: self)
1749
+
1750
+ # @api private
1751
+ module DefaultTraceClass
1752
+ end
1611
1753
  end
1612
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
@@ -23,8 +23,18 @@ module GraphQL
23
23
  type_name = fragment_node.type.name
24
24
  type = @types.type(type_name)
25
25
  if type.nil?
26
+ @all_possible_fragment_type_names ||= begin
27
+ names = []
28
+ context.types.all_types.each do |type|
29
+ if type.kind.fields?
30
+ names << type.graphql_name
31
+ end
32
+ end
33
+ names
34
+ end
35
+
26
36
  add_error(GraphQL::StaticValidation::FragmentTypesExistError.new(
27
- "No such type #{type_name}, so it can't be a fragment condition",
37
+ "No such type #{type_name}, so it can't be a fragment condition#{context.did_you_mean_suggestion(type_name, @all_possible_fragment_type_names)}",
28
38
  nodes: fragment_node,
29
39
  type: type_name
30
40
  ))
@@ -7,8 +7,17 @@ module GraphQL
7
7
  type = context.query.types.type(type_name)
8
8
 
9
9
  if type.nil?
10
+ @all_possible_input_type_names ||= begin
11
+ names = []
12
+ context.types.all_types.each { |(t)|
13
+ if t.kind.input?
14
+ names << t.graphql_name
15
+ end
16
+ }
17
+ names
18
+ end
10
19
  add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new(
11
- "#{type_name} isn't a defined input type (on $#{node.name})",
20
+ "#{type_name} isn't a defined input type (on $#{node.name})#{context.did_you_mean_suggestion(type_name, @all_possible_input_type_names)}",
12
21
  nodes: node,
13
22
  name: node.name,
14
23
  type: type_name
@@ -48,6 +48,21 @@ module GraphQL
48
48
  def schema_directives
49
49
  @schema_directives ||= schema.directives
50
50
  end
51
+
52
+ def did_you_mean_suggestion(name, options)
53
+ if did_you_mean = schema.did_you_mean
54
+ suggestions = did_you_mean::SpellChecker.new(dictionary: options).correct(name)
55
+ case suggestions.size
56
+ when 0
57
+ ""
58
+ when 1
59
+ " (Did you mean `#{suggestions.first}`?)"
60
+ else
61
+ last_sugg = suggestions.pop
62
+ " (Did you mean #{suggestions.map {|s| "`#{s}`"}.join(", ")} or `#{last_sugg}`?)"
63
+ end
64
+ end
65
+ end
51
66
  end
52
67
  end
53
68
  end
@@ -166,7 +166,8 @@ module GraphQL
166
166
  #
167
167
  def setup_stream(channel, initial_event)
168
168
  topic = initial_event.topic
169
- channel.stream_from(stream_event_name(initial_event), coder: @action_cable_coder) do |message|
169
+ event_stream = stream_event_name(initial_event)
170
+ channel.stream_from(event_stream, coder: @action_cable_coder) do |message|
170
171
  events_by_fingerprint = @events[topic]
171
172
  object = nil
172
173
  events_by_fingerprint.each do |_fingerprint, events|