graphql 2.3.14 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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|