graphql 2.3.18 → 2.4.1

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 (31) 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 +3 -5
  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 +19 -3
  11. data/lib/graphql/schema/enum_value.rb +1 -1
  12. data/lib/graphql/schema/input_object.rb +20 -7
  13. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  14. data/lib/graphql/schema/member/has_fields.rb +2 -2
  15. data/lib/graphql/schema/printer.rb +1 -0
  16. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  17. data/lib/graphql/schema/visibility/migration.rb +34 -35
  18. data/lib/graphql/schema/visibility/{subset.rb → profile.rb} +37 -19
  19. data/lib/graphql/schema/visibility.rb +57 -12
  20. data/lib/graphql/schema/warden.rb +87 -21
  21. data/lib/graphql/schema.rb +177 -41
  22. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  23. data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
  24. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +2 -1
  25. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
  26. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
  27. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
  28. data/lib/graphql/static_validation/validation_context.rb +15 -0
  29. data/lib/graphql/testing/helpers.rb +1 -1
  30. data/lib/graphql/version.rb +1 -1
  31. 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.null_profile(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.1"
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.1
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-11-04 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