graphql 2.3.18 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/dataloader/async_dataloader.rb +3 -2
  3. data/lib/graphql/dataloader/source.rb +1 -1
  4. data/lib/graphql/dataloader.rb +31 -10
  5. data/lib/graphql/query/null_context.rb +1 -1
  6. data/lib/graphql/query.rb +49 -16
  7. data/lib/graphql/schema/always_visible.rb +6 -3
  8. data/lib/graphql/schema/argument.rb +1 -0
  9. data/lib/graphql/schema/build_from_definition.rb +1 -0
  10. data/lib/graphql/schema/enum.rb +2 -0
  11. data/lib/graphql/schema/input_object.rb +20 -7
  12. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  13. data/lib/graphql/schema/member/has_fields.rb +2 -2
  14. data/lib/graphql/schema/printer.rb +1 -0
  15. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  16. data/lib/graphql/schema/visibility/migration.rb +32 -34
  17. data/lib/graphql/schema/visibility/{subset.rb → profile.rb} +31 -17
  18. data/lib/graphql/schema/visibility.rb +57 -12
  19. data/lib/graphql/schema/warden.rb +77 -15
  20. data/lib/graphql/schema.rb +177 -41
  21. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  22. data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
  23. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +2 -1
  24. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
  25. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
  26. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
  27. data/lib/graphql/static_validation/validation_context.rb +15 -0
  28. data/lib/graphql/testing/helpers.rb +1 -1
  29. data/lib/graphql/version.rb +1 -1
  30. metadata +3 -3
@@ -317,6 +317,9 @@ module GraphQL
317
317
  GraphQL::StaticValidation::Validator.new(schema: self)
318
318
  end
319
319
 
320
+ # Add `plugin` to this schema
321
+ # @param plugin [#use] A Schema plugin
322
+ # @return void
320
323
  def use(plugin, **kwargs)
321
324
  if kwargs.any?
322
325
  plugin.use(self, **kwargs)
@@ -334,8 +337,9 @@ module GraphQL
334
337
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
335
338
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
336
339
  def types(context = GraphQL::Query::NullContext.instance)
337
- if use_schema_visibility?
338
- return Visibility::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
339
343
  end
340
344
  all_types = non_introspection_types.merge(introspection_system.types)
341
345
  visible_types = {}
@@ -362,17 +366,19 @@ module GraphQL
362
366
  end
363
367
 
364
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}
365
371
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
366
- def get_type(type_name, context = GraphQL::Query::NullContext.instance)
367
- if use_schema_visibility?
368
- return Visibility::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)
369
375
  end
370
376
  local_entry = own_types[type_name]
371
377
  type_defn = case local_entry
372
378
  when nil
373
379
  nil
374
380
  when Array
375
- if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)
381
+ if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
376
382
  local_entry
377
383
  else
378
384
  visible_t = nil
@@ -398,7 +404,7 @@ module GraphQL
398
404
 
399
405
  type_defn ||
400
406
  introspection_system.types[type_name] || # todo context-specific introspection?
401
- (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)
402
408
  end
403
409
 
404
410
  # @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility?
@@ -425,12 +431,20 @@ module GraphQL
425
431
  end
426
432
  end
427
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.
428
442
  def query(new_query_object = nil, &lazy_load_block)
429
443
  if new_query_object || block_given?
430
444
  if @query_object
431
445
  dup_defn = new_query_object || yield
432
446
  raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
433
- elsif use_schema_visibility?
447
+ elsif use_visibility_profile?
434
448
  @query_object = block_given? ? lazy_load_block : new_query_object
435
449
  else
436
450
  @query_object = new_query_object || lazy_load_block.call
@@ -444,12 +458,20 @@ module GraphQL
444
458
  end
445
459
  end
446
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.
447
469
  def mutation(new_mutation_object = nil, &lazy_load_block)
448
470
  if new_mutation_object || block_given?
449
471
  if @mutation_object
450
472
  dup_defn = new_mutation_object || yield
451
473
  raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
452
- elsif use_schema_visibility?
474
+ elsif use_visibility_profile?
453
475
  @mutation_object = block_given? ? lazy_load_block : new_mutation_object
454
476
  else
455
477
  @mutation_object = new_mutation_object || lazy_load_block.call
@@ -463,12 +485,20 @@ module GraphQL
463
485
  end
464
486
  end
465
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.
466
496
  def subscription(new_subscription_object = nil, &lazy_load_block)
467
497
  if new_subscription_object || block_given?
468
498
  if @subscription_object
469
499
  dup_defn = new_subscription_object || yield
470
500
  raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
471
- elsif use_schema_visibility?
501
+ elsif use_visibility_profile?
472
502
  @subscription_object = block_given? ? lazy_load_block : new_subscription_object
473
503
  add_subscription_extension_if_necessary
474
504
  else
@@ -486,8 +516,7 @@ module GraphQL
486
516
  end
487
517
  end
488
518
 
489
- # @see [GraphQL::Schema::Warden] Restricted access to root types
490
- # @return [GraphQL::ObjectType, nil]
519
+ # @api private
491
520
  def root_type_for_operation(operation)
492
521
  case operation
493
522
  when "query"
@@ -501,14 +530,16 @@ module GraphQL
501
530
  end
502
531
  end
503
532
 
533
+ # @return [Array<Class>] The root types (query, mutation, subscription) defined for this schema
504
534
  def root_types
505
- if use_schema_visibility?
535
+ if use_visibility_profile?
506
536
  [query, mutation, subscription].compact
507
537
  else
508
538
  @root_types
509
539
  end
510
540
  end
511
541
 
542
+ # @api private
512
543
  def warden_class
513
544
  if defined?(@warden_class)
514
545
  @warden_class
@@ -519,39 +550,46 @@ module GraphQL
519
550
  end
520
551
  end
521
552
 
553
+ # @api private
522
554
  attr_writer :warden_class
523
555
 
524
- def subset_class
525
- if defined?(@subset_class)
526
- @subset_class
527
- elsif superclass.respond_to?(:subset_class)
528
- 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
529
562
  else
530
- GraphQL::Schema::Visibility::Subset
563
+ GraphQL::Schema::Visibility::Profile
531
564
  end
532
565
  end
533
566
 
534
- attr_writer :subset_class, :use_schema_visibility, :visibility
535
-
536
- def use_schema_visibility?
537
- if defined?(@use_schema_visibility)
538
- @use_schema_visibility
539
- elsif superclass.respond_to?(:use_schema_visibility?)
540
- superclass.use_schema_visibility?
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?
541
577
  else
542
578
  false
543
579
  end
544
580
  end
545
581
 
546
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}
547
585
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
548
586
  # @return [Array<Module>] Possible types for `type`, if it's given.
549
- def possible_types(type = nil, context = GraphQL::Query::NullContext.instance)
550
- if use_schema_visibility?
587
+ def possible_types(type = nil, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
588
+ if use_visibility_profile
551
589
  if type
552
- return Visibility::Subset.from_context(context, self).possible_types(type)
590
+ return Visibility::Profile.from_context(context, self).possible_types(type)
553
591
  else
554
- raise "Schema.possible_types is not implemented for `use_schema_visibility?`"
592
+ raise "Schema.possible_types is not implemented for `use_visibility_profile?`"
555
593
  end
556
594
  end
557
595
  if type
@@ -571,7 +609,7 @@ module GraphQL
571
609
  introspection_system.possible_types[type] ||
572
610
  (
573
611
  superclass.respond_to?(:possible_types) ?
574
- superclass.possible_types(type, context) :
612
+ superclass.possible_types(type, context, use_visibility_profile) :
575
613
  EMPTY_ARRAY
576
614
  )
577
615
  end
@@ -774,6 +812,7 @@ module GraphQL
774
812
  res[:errors]
775
813
  end
776
814
 
815
+ # @param new_query_class [Class<GraphQL::Query>] A subclass to use when executing queries
777
816
  def query_class(new_query_class = NOT_CONFIGURED)
778
817
  if NOT_CONFIGURED.equal?(new_query_class)
779
818
  @query_class || (superclass.respond_to?(:query_class) ? superclass.query_class : GraphQL::Query)
@@ -927,7 +966,7 @@ module GraphQL
927
966
  To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
928
967
  ERR
929
968
  end
930
- add_type_and_traverse(new_orphan_types, root: false) unless use_schema_visibility?
969
+ add_type_and_traverse(new_orphan_types, root: false) unless use_visibility_profile?
931
970
  own_orphan_types.concat(new_orphan_types.flatten)
932
971
  end
933
972
 
@@ -959,6 +998,8 @@ module GraphQL
959
998
  end
960
999
  end
961
1000
 
1001
+
1002
+ # @param new_default_logger [#log] Something to use for logging messages
962
1003
  def default_logger(new_default_logger = NOT_CONFIGURED)
963
1004
  if NOT_CONFIGURED.equal?(new_default_logger)
964
1005
  if defined?(@default_logger)
@@ -979,6 +1020,7 @@ module GraphQL
979
1020
  end
980
1021
  end
981
1022
 
1023
+ # @param new_context_class [Class<GraphQL::Query::Context>] A subclass to use when executing queries
982
1024
  def context_class(new_context_class = nil)
983
1025
  if new_context_class
984
1026
  @context_class = new_context_class
@@ -987,6 +1029,20 @@ module GraphQL
987
1029
  end
988
1030
  end
989
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
990
1046
  def rescue_from(*err_classes, &handler_block)
991
1047
  err_classes.each do |err_class|
992
1048
  Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block)
@@ -1053,8 +1109,24 @@ module GraphQL
1053
1109
  end
1054
1110
  end
1055
1111
 
1056
- def resolve_type(type, obj, ctx)
1057
- 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})"
1058
1130
  end
1059
1131
  # rubocop:enable Lint/DuplicateMethods
1060
1132
 
@@ -1069,15 +1141,45 @@ module GraphQL
1069
1141
  child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
1070
1142
  end
1071
1143
  child_class.singleton_class.prepend(ResolveTypeWithType)
1072
- super
1073
- end
1074
1144
 
1075
- def object_from_id(node_id, ctx)
1076
- 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
1077
1150
  end
1078
1151
 
1079
- def id_from_object(object, type, ctx)
1080
- 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}`)"
1081
1183
  end
1082
1184
 
1083
1185
  def visible?(member, ctx)
@@ -1132,6 +1234,16 @@ module GraphQL
1132
1234
  unauthorized_object(unauthorized_error)
1133
1235
  end
1134
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
1135
1247
  def type_error(type_error, ctx)
1136
1248
  case type_error
1137
1249
  when GraphQL::InvalidNullError
@@ -1186,7 +1298,7 @@ module GraphQL
1186
1298
  # @param new_directive [Class]
1187
1299
  # @return void
1188
1300
  def directive(new_directive)
1189
- if use_schema_visibility?
1301
+ if use_visibility_profile?
1190
1302
  own_directives[new_directive.graphql_name] = new_directive
1191
1303
  else
1192
1304
  add_type_and_traverse(new_directive, root: false)
@@ -1227,6 +1339,7 @@ module GraphQL
1227
1339
  # @param mode [Symbol] Trace module will only be used for this trade mode
1228
1340
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1229
1341
  # @return [void]
1342
+ # @see GraphQL::Tracing::Trace for available tracing methods
1230
1343
  def trace_with(trace_mod, mode: :default, **options)
1231
1344
  if mode.is_a?(Array)
1232
1345
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
@@ -1304,6 +1417,8 @@ module GraphQL
1304
1417
  trace_class_for_mode.new(**trace_options)
1305
1418
  end
1306
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
1307
1422
  def query_analyzer(new_analyzer)
1308
1423
  own_query_analyzers << new_analyzer
1309
1424
  end
@@ -1312,6 +1427,8 @@ module GraphQL
1312
1427
  find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers
1313
1428
  end
1314
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
1315
1432
  def multiplex_analyzer(new_analyzer)
1316
1433
  own_multiplex_analyzers << new_analyzer
1317
1434
  end
@@ -1395,6 +1512,11 @@ module GraphQL
1395
1512
  end
1396
1513
  end
1397
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]
1398
1520
  def query_stack_error(query, err)
1399
1521
  query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute."))
1400
1522
  end
@@ -1453,6 +1575,20 @@ module GraphQL
1453
1575
  end
1454
1576
  end
1455
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
+
1456
1592
  private
1457
1593
 
1458
1594
  def add_trace_options_for(mode, new_options)
@@ -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
  )
@@ -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
@@ -92,7 +92,7 @@ module GraphQL
92
92
  end
93
93
  graphql_result
94
94
  else
95
- unfiltered_type = Schema::Visibility::Subset.pass_thru(schema: schema, context: context).type(type_name)
95
+ unfiltered_type = Schema::Visibility::Profile.pass_thru(schema: schema, context: context).type(type_name)
96
96
  if unfiltered_type
97
97
  raise TypeNotVisibleError.new(type_name: type_name)
98
98
  else
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.3.18"
3
+ VERSION = "2.4.0"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.18
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-07 00:00:00.000000000 Z
11
+ date: 2024-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -503,7 +503,7 @@ files:
503
503
  - lib/graphql/schema/validator/required_validator.rb
504
504
  - lib/graphql/schema/visibility.rb
505
505
  - lib/graphql/schema/visibility/migration.rb
506
- - lib/graphql/schema/visibility/subset.rb
506
+ - lib/graphql/schema/visibility/profile.rb
507
507
  - lib/graphql/schema/warden.rb
508
508
  - lib/graphql/schema/wrapper.rb
509
509
  - lib/graphql/static_validation.rb