graphql 2.3.7 → 2.4.7

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +46 -0
  3. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  4. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  5. data/lib/generators/graphql/type_generator.rb +1 -1
  6. data/lib/graphql/analysis/field_usage.rb +1 -1
  7. data/lib/graphql/analysis/query_complexity.rb +3 -3
  8. data/lib/graphql/analysis/visitor.rb +8 -7
  9. data/lib/graphql/analysis.rb +4 -4
  10. data/lib/graphql/autoload.rb +38 -0
  11. data/lib/graphql/current.rb +52 -0
  12. data/lib/graphql/dataloader/async_dataloader.rb +7 -6
  13. data/lib/graphql/dataloader/source.rb +7 -4
  14. data/lib/graphql/dataloader.rb +40 -19
  15. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  16. data/lib/graphql/execution/interpreter/resolve.rb +13 -9
  17. data/lib/graphql/execution/interpreter/runtime.rb +35 -31
  18. data/lib/graphql/execution/interpreter.rb +6 -4
  19. data/lib/graphql/execution/lookahead.rb +18 -11
  20. data/lib/graphql/introspection/directive_type.rb +1 -1
  21. data/lib/graphql/introspection/entry_points.rb +2 -2
  22. data/lib/graphql/introspection/field_type.rb +1 -1
  23. data/lib/graphql/introspection/schema_type.rb +6 -11
  24. data/lib/graphql/introspection/type_type.rb +5 -5
  25. data/lib/graphql/invalid_null_error.rb +1 -1
  26. data/lib/graphql/language/cache.rb +13 -0
  27. data/lib/graphql/language/comment.rb +18 -0
  28. data/lib/graphql/language/document_from_schema_definition.rb +62 -34
  29. data/lib/graphql/language/lexer.rb +18 -15
  30. data/lib/graphql/language/nodes.rb +24 -16
  31. data/lib/graphql/language/parser.rb +14 -1
  32. data/lib/graphql/language/printer.rb +31 -15
  33. data/lib/graphql/language/sanitized_printer.rb +1 -1
  34. data/lib/graphql/language.rb +6 -6
  35. data/lib/graphql/pagination/connection.rb +1 -1
  36. data/lib/graphql/query/context/scoped_context.rb +1 -1
  37. data/lib/graphql/query/context.rb +13 -6
  38. data/lib/graphql/query/null_context.rb +3 -5
  39. data/lib/graphql/query/variable_validation_error.rb +1 -1
  40. data/lib/graphql/query.rb +72 -18
  41. data/lib/graphql/railtie.rb +7 -0
  42. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  43. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  44. data/lib/graphql/rubocop.rb +2 -0
  45. data/lib/graphql/schema/addition.rb +2 -1
  46. data/lib/graphql/schema/always_visible.rb +6 -2
  47. data/lib/graphql/schema/argument.rb +14 -1
  48. data/lib/graphql/schema/build_from_definition.rb +9 -1
  49. data/lib/graphql/schema/directive/flagged.rb +2 -2
  50. data/lib/graphql/schema/directive.rb +1 -1
  51. data/lib/graphql/schema/enum.rb +71 -23
  52. data/lib/graphql/schema/enum_value.rb +10 -2
  53. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  54. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  55. data/lib/graphql/schema/field.rb +102 -47
  56. data/lib/graphql/schema/field_extension.rb +1 -1
  57. data/lib/graphql/schema/has_single_input_argument.rb +5 -2
  58. data/lib/graphql/schema/input_object.rb +90 -39
  59. data/lib/graphql/schema/interface.rb +22 -5
  60. data/lib/graphql/schema/introspection_system.rb +5 -16
  61. data/lib/graphql/schema/loader.rb +1 -1
  62. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  63. data/lib/graphql/schema/member/has_arguments.rb +36 -23
  64. data/lib/graphql/schema/member/has_directives.rb +3 -3
  65. data/lib/graphql/schema/member/has_fields.rb +26 -6
  66. data/lib/graphql/schema/member/has_interfaces.rb +4 -4
  67. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  68. data/lib/graphql/schema/member/has_validators.rb +1 -1
  69. data/lib/graphql/schema/object.rb +8 -0
  70. data/lib/graphql/schema/printer.rb +1 -0
  71. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  72. data/lib/graphql/schema/resolver.rb +12 -14
  73. data/lib/graphql/schema/subscription.rb +2 -2
  74. data/lib/graphql/schema/type_expression.rb +2 -2
  75. data/lib/graphql/schema/union.rb +1 -1
  76. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  77. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  78. data/lib/graphql/schema/validator.rb +3 -1
  79. data/lib/graphql/schema/visibility/migration.rb +188 -0
  80. data/lib/graphql/schema/visibility/profile.rb +359 -0
  81. data/lib/graphql/schema/visibility/visit.rb +190 -0
  82. data/lib/graphql/schema/visibility.rb +294 -0
  83. data/lib/graphql/schema/warden.rb +179 -16
  84. data/lib/graphql/schema.rb +348 -94
  85. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  86. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  87. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  88. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  89. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  90. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  91. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  92. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  93. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +2 -2
  94. data/lib/graphql/static_validation/rules/fields_will_merge.rb +8 -7
  95. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  96. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  97. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  98. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  99. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  100. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  101. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  102. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  103. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  104. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  105. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  106. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  107. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  108. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  109. data/lib/graphql/static_validation/validation_context.rb +18 -2
  110. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
  111. data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
  112. data/lib/graphql/subscriptions/event.rb +1 -1
  113. data/lib/graphql/subscriptions/serialize.rb +2 -0
  114. data/lib/graphql/subscriptions.rb +6 -4
  115. data/lib/graphql/testing/helpers.rb +10 -6
  116. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  117. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  118. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  119. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  120. data/lib/graphql/types.rb +18 -11
  121. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  122. data/lib/graphql/version.rb +1 -1
  123. data/lib/graphql.rb +53 -45
  124. metadata +31 -8
  125. data/lib/graphql/language/token.rb +0 -34
  126. data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -5,7 +5,6 @@ require "graphql/schema/always_visible"
5
5
  require "graphql/schema/base_64_encoder"
6
6
  require "graphql/schema/find_inherited_value"
7
7
  require "graphql/schema/finder"
8
- require "graphql/schema/invalid_type_error"
9
8
  require "graphql/schema/introspection_system"
10
9
  require "graphql/schema/late_bound_type"
11
10
  require "graphql/schema/null_mask"
@@ -46,6 +45,7 @@ require "graphql/schema/mutation"
46
45
  require "graphql/schema/has_single_input_argument"
47
46
  require "graphql/schema/relay_classic_mutation"
48
47
  require "graphql/schema/subscription"
48
+ require "graphql/schema/visibility"
49
49
 
50
50
  module GraphQL
51
51
  # A GraphQL schema which may be queried with {GraphQL::Query}.
@@ -73,6 +73,9 @@ module GraphQL
73
73
  class Schema
74
74
  extend GraphQL::Schema::Member::HasAstNode
75
75
  extend GraphQL::Schema::FindInheritedValue
76
+ extend Autoload
77
+
78
+ autoload :BUILT_IN_TYPES, "graphql/schema/built_in_types"
76
79
 
77
80
  class DuplicateNamesError < GraphQL::Error
78
81
  attr_reader :duplicated_name
@@ -162,6 +165,7 @@ module GraphQL
162
165
  # re-apply them here
163
166
  mods = trace_modules_for(:default)
164
167
  mods.each { |mod| new_class.include(mod) }
168
+ new_class.include(DefaultTraceClass)
165
169
  trace_mode(:default, new_class)
166
170
  backtrace_class = Class.new(new_class)
167
171
  backtrace_class.include(GraphQL::Backtrace::Trace)
@@ -204,24 +208,19 @@ module GraphQL
204
208
  @own_trace_modes ||= {}
205
209
  end
206
210
 
207
- module DefaultTraceClass
208
- end
209
-
210
- private_constant :DefaultTraceClass
211
-
212
211
  def build_trace_mode(mode)
213
212
  case mode
214
213
  when :default
215
214
  # Use the superclass's default mode if it has one, or else start an inheritance chain at the built-in base class.
216
- base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || GraphQL::Tracing::Trace
217
- Class.new(base_class) do
215
+ base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode, build: true)) || GraphQL::Tracing::Trace
216
+ const_set(:DefaultTrace, Class.new(base_class) do
218
217
  include DefaultTraceClass
219
- end
218
+ end)
220
219
  when :default_backtrace
221
220
  schema_base_class = trace_class_for(:default, build: true)
222
- Class.new(schema_base_class) do
221
+ const_set(:DefaultTraceBacktrace, Class.new(schema_base_class) do
223
222
  include(GraphQL::Backtrace::Trace)
224
- end
223
+ end)
225
224
  else
226
225
  # First, see if the superclass has a custom-defined class for this.
227
226
  # Then, if it doesn't, use this class's default trace
@@ -237,7 +236,7 @@ module GraphQL
237
236
  add_trace_options_for(mode, default_options)
238
237
 
239
238
  Class.new(base_class) do
240
- mods.any? && include(*mods)
239
+ !mods.empty? && include(*mods)
241
240
  end
242
241
  end
243
242
  end
@@ -321,8 +320,11 @@ module GraphQL
321
320
  GraphQL::StaticValidation::Validator.new(schema: self)
322
321
  end
323
322
 
323
+ # Add `plugin` to this schema
324
+ # @param plugin [#use] A Schema plugin
325
+ # @return void
324
326
  def use(plugin, **kwargs)
325
- if kwargs.any?
327
+ if !kwargs.empty?
326
328
  plugin.use(self, **kwargs)
327
329
  else
328
330
  plugin.use(self)
@@ -338,6 +340,10 @@ module GraphQL
338
340
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
339
341
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
340
342
  def types(context = GraphQL::Query::NullContext.instance)
343
+ if use_visibility_profile?
344
+ types = Visibility::Profile.from_context(context, self)
345
+ return types.all_types_h
346
+ end
341
347
  all_types = non_introspection_types.merge(introspection_system.types)
342
348
  visible_types = {}
343
349
  all_types.each do |k, v|
@@ -363,27 +369,36 @@ module GraphQL
363
369
  end
364
370
 
365
371
  # @param type_name [String]
372
+ # @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
373
+ # @param use_visibility_profile Private, for migration to {Schema::Visibility}
366
374
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
367
- def get_type(type_name, context = GraphQL::Query::NullContext.instance)
375
+ def get_type(type_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
376
+ if use_visibility_profile
377
+ return Visibility::Profile.from_context(context, self).type(type_name)
378
+ end
368
379
  local_entry = own_types[type_name]
369
380
  type_defn = case local_entry
370
381
  when nil
371
382
  nil
372
383
  when Array
373
- visible_t = nil
374
- warden = Warden.from_context(context)
375
- local_entry.each do |t|
376
- if warden.visible_type?(t, context)
377
- if visible_t.nil?
378
- visible_t = t
379
- else
380
- raise DuplicateNamesError.new(
381
- duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
382
- )
384
+ if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
385
+ local_entry
386
+ else
387
+ visible_t = nil
388
+ warden = Warden.from_context(context)
389
+ local_entry.each do |t|
390
+ if warden.visible_type?(t, context)
391
+ if visible_t.nil?
392
+ visible_t = t
393
+ else
394
+ raise DuplicateNamesError.new(
395
+ duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
396
+ )
397
+ end
383
398
  end
384
399
  end
400
+ visible_t
385
401
  end
386
- visible_t
387
402
  when Module
388
403
  local_entry
389
404
  else
@@ -392,7 +407,7 @@ module GraphQL
392
407
 
393
408
  type_defn ||
394
409
  introspection_system.types[type_name] || # todo context-specific introspection?
395
- (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context) : nil)
410
+ (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context, use_visibility_profile) : nil)
396
411
  end
397
412
 
398
413
  # @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility?
@@ -419,55 +434,127 @@ module GraphQL
419
434
  end
420
435
  end
421
436
 
422
- def new_connections?
423
- !!connections
424
- end
425
-
426
- def query(new_query_object = nil)
427
- if new_query_object
437
+ # Get or set the root `query { ... }` object for this schema.
438
+ #
439
+ # @example Using `Types::Query` as the entry-point
440
+ # query { Types::Query }
441
+ #
442
+ # @param new_query_object [Class<GraphQL::Schema::Object>] The root type to use for queries
443
+ # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root query type.
444
+ # @return [Class<GraphQL::Schema::Object>, nil] The configured query root type, if there is one.
445
+ def query(new_query_object = nil, &lazy_load_block)
446
+ if new_query_object || block_given?
428
447
  if @query_object
429
- raise GraphQL::Error, "Second definition of `query(...)` (#{new_query_object.inspect}) is invalid, already configured with #{@query_object.inspect}"
448
+ dup_defn = new_query_object || yield
449
+ raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
450
+ elsif use_visibility_profile?
451
+ if block_given?
452
+ if visibility.preload?
453
+ @query_object = lazy_load_block.call
454
+ self.visibility.query_configured(@query_object)
455
+ else
456
+ @query_object = lazy_load_block
457
+ end
458
+ else
459
+ @query_object = new_query_object
460
+ self.visibility.query_configured(@query_object)
461
+ end
430
462
  else
431
- @query_object = new_query_object
432
- add_type_and_traverse(new_query_object, root: true)
433
- nil
463
+ @query_object = new_query_object || lazy_load_block.call
464
+ add_type_and_traverse(@query_object, root: true)
434
465
  end
466
+ nil
467
+ elsif @query_object.is_a?(Proc)
468
+ @query_object = @query_object.call
469
+ self.visibility&.query_configured(@query_object)
470
+ @query_object
435
471
  else
436
472
  @query_object || find_inherited_value(:query)
437
473
  end
438
474
  end
439
475
 
440
- def mutation(new_mutation_object = nil)
441
- if new_mutation_object
476
+ # Get or set the root `mutation { ... }` object for this schema.
477
+ #
478
+ # @example Using `Types::Mutation` as the entry-point
479
+ # mutation { Types::Mutation }
480
+ #
481
+ # @param new_mutation_object [Class<GraphQL::Schema::Object>] The root type to use for mutations
482
+ # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root mutation type.
483
+ # @return [Class<GraphQL::Schema::Object>, nil] The configured mutation root type, if there is one.
484
+ def mutation(new_mutation_object = nil, &lazy_load_block)
485
+ if new_mutation_object || block_given?
442
486
  if @mutation_object
443
- raise GraphQL::Error, "Second definition of `mutation(...)` (#{new_mutation_object.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
487
+ dup_defn = new_mutation_object || yield
488
+ raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
489
+ elsif use_visibility_profile?
490
+ if block_given?
491
+ if visibility.preload?
492
+ @mutation_object = lazy_load_block.call
493
+ self.visibility.mutation_configured(@mutation_object)
494
+ else
495
+ @mutation_object = lazy_load_block
496
+ end
497
+ else
498
+ @mutation_object = new_mutation_object
499
+ self.visibility.mutation_configured(@mutation_object)
500
+ end
444
501
  else
445
- @mutation_object = new_mutation_object
446
- add_type_and_traverse(new_mutation_object, root: true)
447
- nil
502
+ @mutation_object = new_mutation_object || lazy_load_block.call
503
+ add_type_and_traverse(@mutation_object, root: true)
448
504
  end
505
+ nil
506
+ elsif @mutation_object.is_a?(Proc)
507
+ @mutation_object = @mutation_object.call
508
+ self.visibility&.mutation_configured(@mutation_object)
509
+ @mutation_object
449
510
  else
450
511
  @mutation_object || find_inherited_value(:mutation)
451
512
  end
452
513
  end
453
514
 
454
- def subscription(new_subscription_object = nil)
455
- if new_subscription_object
515
+ # Get or set the root `subscription { ... }` object for this schema.
516
+ #
517
+ # @example Using `Types::Subscription` as the entry-point
518
+ # subscription { Types::Subscription }
519
+ #
520
+ # @param new_subscription_object [Class<GraphQL::Schema::Object>] The root type to use for subscriptions
521
+ # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root subscription type.
522
+ # @return [Class<GraphQL::Schema::Object>, nil] The configured subscription root type, if there is one.
523
+ def subscription(new_subscription_object = nil, &lazy_load_block)
524
+ if new_subscription_object || block_given?
456
525
  if @subscription_object
457
- raise GraphQL::Error, "Second definition of `subscription(...)` (#{new_subscription_object.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
526
+ dup_defn = new_subscription_object || yield
527
+ raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
528
+ elsif use_visibility_profile?
529
+ if block_given?
530
+ if visibility.preload?
531
+ @subscription_object = lazy_load_block.call
532
+ visibility.subscription_configured(@subscription_object)
533
+ else
534
+ @subscription_object = lazy_load_block
535
+ end
536
+ else
537
+ @subscription_object = new_subscription_object
538
+ self.visibility.subscription_configured(@subscription_object)
539
+ end
540
+ add_subscription_extension_if_necessary
458
541
  else
459
- @subscription_object = new_subscription_object
542
+ @subscription_object = new_subscription_object || lazy_load_block.call
460
543
  add_subscription_extension_if_necessary
461
- add_type_and_traverse(new_subscription_object, root: true)
462
- nil
544
+ add_type_and_traverse(@subscription_object, root: true)
463
545
  end
546
+ nil
547
+ elsif @subscription_object.is_a?(Proc)
548
+ @subscription_object = @subscription_object.call
549
+ add_subscription_extension_if_necessary
550
+ self.visibility.subscription_configured(@subscription_object)
551
+ @subscription_object
464
552
  else
465
553
  @subscription_object || find_inherited_value(:subscription)
466
554
  end
467
555
  end
468
556
 
469
- # @see [GraphQL::Schema::Warden] Restricted access to root types
470
- # @return [GraphQL::ObjectType, nil]
557
+ # @api private
471
558
  def root_type_for_operation(operation)
472
559
  case operation
473
560
  when "query"
@@ -481,10 +568,16 @@ module GraphQL
481
568
  end
482
569
  end
483
570
 
571
+ # @return [Array<Class>] The root types (query, mutation, subscription) defined for this schema
484
572
  def root_types
485
- @root_types
573
+ if use_visibility_profile?
574
+ [query, mutation, subscription].compact
575
+ else
576
+ @root_types
577
+ end
486
578
  end
487
579
 
580
+ # @api private
488
581
  def warden_class
489
582
  if defined?(@warden_class)
490
583
  @warden_class
@@ -495,12 +588,48 @@ module GraphQL
495
588
  end
496
589
  end
497
590
 
591
+ # @api private
498
592
  attr_writer :warden_class
499
593
 
594
+ # @api private
595
+ def visibility_profile_class
596
+ if defined?(@visibility_profile_class)
597
+ @visibility_profile_class
598
+ elsif superclass.respond_to?(:visibility_profile_class)
599
+ superclass.visibility_profile_class
600
+ else
601
+ GraphQL::Schema::Visibility::Profile
602
+ end
603
+ end
604
+
605
+ # @api private
606
+ attr_writer :visibility_profile_class, :use_visibility_profile
607
+ # @api private
608
+ attr_accessor :visibility
609
+ # @api private
610
+ def use_visibility_profile?
611
+ if defined?(@use_visibility_profile)
612
+ @use_visibility_profile
613
+ elsif superclass.respond_to?(:use_visibility_profile?)
614
+ superclass.use_visibility_profile?
615
+ else
616
+ false
617
+ end
618
+ end
619
+
500
620
  # @param type [Module] The type definition whose possible types you want to see
621
+ # @param context [GraphQL::Query::Context] used for filtering visible possible types at runtime
622
+ # @param use_visibility_profile Private, for migration to {Schema::Visibility}
501
623
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
502
624
  # @return [Array<Module>] Possible types for `type`, if it's given.
503
- def possible_types(type = nil, context = GraphQL::Query::NullContext.instance)
625
+ def possible_types(type = nil, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
626
+ if use_visibility_profile
627
+ if type
628
+ return Visibility::Profile.from_context(context, self).possible_types(type)
629
+ else
630
+ raise "Schema.possible_types is not implemented for `use_visibility_profile?`"
631
+ end
632
+ end
504
633
  if type
505
634
  # TODO duck-typing `.possible_types` would probably be nicer here
506
635
  if type.kind.union?
@@ -518,7 +647,7 @@ module GraphQL
518
647
  introspection_system.possible_types[type] ||
519
648
  (
520
649
  superclass.respond_to?(:possible_types) ?
521
- superclass.possible_types(type, context) :
650
+ superclass.possible_types(type, context, use_visibility_profile) :
522
651
  EMPTY_ARRAY
523
652
  )
524
653
  end
@@ -565,7 +694,7 @@ module GraphQL
565
694
  # and generally speaking, we won't inherit any values.
566
695
  # So optimize the most common case -- don't create a duplicate Hash.
567
696
  inherited_value = find_inherited_value(:references_to, EMPTY_HASH)
568
- if inherited_value.any?
697
+ if !inherited_value.empty?
569
698
  inherited_value.merge(own_references_to)
570
699
  else
571
700
  own_references_to
@@ -573,9 +702,8 @@ module GraphQL
573
702
  end
574
703
  end
575
704
 
576
- def type_from_ast(ast_node, context: nil)
577
- type_owner = context ? context.warden : self
578
- GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node)
705
+ def type_from_ast(ast_node, context: self.query_class.new(self, "{ __typename }").context)
706
+ GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
579
707
  end
580
708
 
581
709
  def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance)
@@ -605,20 +733,27 @@ module GraphQL
605
733
  type.fields(context)
606
734
  end
607
735
 
736
+ # Pass a custom introspection module here to use it for this schema.
737
+ # @param new_introspection_namespace [Module] If given, use this module for custom introspection on the schema
738
+ # @return [Module, nil] The configured namespace, if there is one
608
739
  def introspection(new_introspection_namespace = nil)
609
740
  if new_introspection_namespace
610
741
  @introspection = new_introspection_namespace
611
742
  # reset this cached value:
612
743
  @introspection_system = nil
744
+ introspection_system
745
+ @introspection
613
746
  else
614
747
  @introspection || find_inherited_value(:introspection)
615
748
  end
616
749
  end
617
750
 
751
+ # @return [Schema::IntrospectionSystem] Based on {introspection}
618
752
  def introspection_system
619
753
  if !@introspection_system
620
754
  @introspection_system = Schema::IntrospectionSystem.new(self)
621
755
  @introspection_system.resolve_late_bindings
756
+ self.visibility&.introspection_system_configured(@introspection_system)
622
757
  end
623
758
  @introspection_system
624
759
  end
@@ -722,6 +857,7 @@ module GraphQL
722
857
  res[:errors]
723
858
  end
724
859
 
860
+ # @param new_query_class [Class<GraphQL::Query>] A subclass to use when executing queries
725
861
  def query_class(new_query_class = NOT_CONFIGURED)
726
862
  if NOT_CONFIGURED.equal?(new_query_class)
727
863
  @query_class || (superclass.respond_to?(:query_class) ? superclass.query_class : GraphQL::Query)
@@ -732,13 +868,11 @@ module GraphQL
732
868
 
733
869
  attr_writer :validate_max_errors
734
870
 
735
- def validate_max_errors(new_validate_max_errors = nil)
736
- if new_validate_max_errors
737
- @validate_max_errors = new_validate_max_errors
738
- elsif defined?(@validate_max_errors)
739
- @validate_max_errors
871
+ def validate_max_errors(new_validate_max_errors = NOT_CONFIGURED)
872
+ if NOT_CONFIGURED.equal?(new_validate_max_errors)
873
+ defined?(@validate_max_errors) ? @validate_max_errors : find_inherited_value(:validate_max_errors)
740
874
  else
741
- find_inherited_value(:validate_max_errors)
875
+ @validate_max_errors = new_validate_max_errors
742
876
  end
743
877
  end
744
878
 
@@ -769,16 +903,6 @@ module GraphQL
769
903
  @analysis_engine || find_inherited_value(:analysis_engine, self.default_analysis_engine)
770
904
  end
771
905
 
772
- def using_ast_analysis?
773
- true
774
- end
775
-
776
- def interpreter?
777
- true
778
- end
779
-
780
- attr_writer :interpreter
781
-
782
906
  def error_bubbling(new_error_bubbling = nil)
783
907
  if !new_error_bubbling.nil?
784
908
  warn("error_bubbling(#{new_error_bubbling.inspect}) is deprecated; the default value of `false` will be the only option in GraphQL-Ruby 3.0")
@@ -856,7 +980,7 @@ module GraphQL
856
980
  # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema
857
981
  # @return [Array<Module>] Type definitions added to this schema
858
982
  def extra_types(*new_extra_types)
859
- if new_extra_types.any?
983
+ if !new_extra_types.empty?
860
984
  new_extra_types = new_extra_types.flatten
861
985
  @own_extra_types ||= []
862
986
  @own_extra_types.concat(new_extra_types)
@@ -873,11 +997,18 @@ module GraphQL
873
997
  end
874
998
  end
875
999
 
1000
+ # Tell the schema about these types so that they can be registered as implementations of interfaces in the schema.
1001
+ #
1002
+ # This method must be used when an object type is connected to the schema as an interface implementor but
1003
+ # not as a return type of a field. In that case, if the object type isn't registered here, GraphQL-Ruby won't be able to find it.
1004
+ #
1005
+ # @param new_orphan_types [Array<Class<GraphQL::Schema::Object>>] Object types to register as implementations of interfaces in the schema.
1006
+ # @return [Array<Class<GraphQL::Schema::Object>>] All previously-registered orphan types for this schema
876
1007
  def orphan_types(*new_orphan_types)
877
- if new_orphan_types.any?
1008
+ if !new_orphan_types.empty?
878
1009
  new_orphan_types = new_orphan_types.flatten
879
1010
  non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object }
880
- if non_object_types.any?
1011
+ if !non_object_types.empty?
881
1012
  raise ArgumentError, <<~ERR
882
1013
  Only object type classes should be added as `orphan_types(...)`.
883
1014
 
@@ -887,13 +1018,14 @@ module GraphQL
887
1018
  To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
888
1019
  ERR
889
1020
  end
890
- add_type_and_traverse(new_orphan_types, root: false)
1021
+ add_type_and_traverse(new_orphan_types, root: false) unless use_visibility_profile?
891
1022
  own_orphan_types.concat(new_orphan_types.flatten)
1023
+ self.visibility&.orphan_types_configured(new_orphan_types)
892
1024
  end
893
1025
 
894
1026
  inherited_ot = find_inherited_value(:orphan_types, nil)
895
1027
  if inherited_ot
896
- if own_orphan_types.any?
1028
+ if !own_orphan_types.empty?
897
1029
  inherited_ot + own_orphan_types
898
1030
  else
899
1031
  inherited_ot
@@ -919,6 +1051,8 @@ module GraphQL
919
1051
  end
920
1052
  end
921
1053
 
1054
+
1055
+ # @param new_default_logger [#log] Something to use for logging messages
922
1056
  def default_logger(new_default_logger = NOT_CONFIGURED)
923
1057
  if NOT_CONFIGURED.equal?(new_default_logger)
924
1058
  if defined?(@default_logger)
@@ -939,6 +1073,7 @@ module GraphQL
939
1073
  end
940
1074
  end
941
1075
 
1076
+ # @param new_context_class [Class<GraphQL::Query::Context>] A subclass to use when executing queries
942
1077
  def context_class(new_context_class = nil)
943
1078
  if new_context_class
944
1079
  @context_class = new_context_class
@@ -947,6 +1082,20 @@ module GraphQL
947
1082
  end
948
1083
  end
949
1084
 
1085
+ # Register a handler for errors raised during execution. The handlers can return a new value or raise a new error.
1086
+ #
1087
+ # @example Handling "not found" with a client-facing error
1088
+ # rescue_from(ActiveRecord::NotFound) { raise GraphQL::ExecutionError, "An object could not be found" }
1089
+ #
1090
+ # @param err_classes [Array<StandardError>] Classes which should be rescued by `handler_block`
1091
+ # @param handler_block The code to run when one of those errors is raised during execution
1092
+ # @yieldparam error [StandardError] An instance of one of the configured `err_classes`
1093
+ # @yieldparam object [Object] The current application object in the query when the error was raised
1094
+ # @yieldparam arguments [GraphQL::Query::Arguments] The current field arguments when the error was raised
1095
+ # @yieldparam context [GraphQL::Query::Context] The context for the currently-running operation
1096
+ # @yieldreturn [Object] Some object to use in the place where this error was raised
1097
+ # @raise [GraphQL::ExecutionError] In the handler, raise to add a client-facing error to the response
1098
+ # @raise [StandardError] In the handler, raise to crash the query with a developer-facing error
950
1099
  def rescue_from(*err_classes, &handler_block)
951
1100
  err_classes.each do |err_class|
952
1101
  Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block)
@@ -1013,8 +1162,24 @@ module GraphQL
1013
1162
  end
1014
1163
  end
1015
1164
 
1016
- def resolve_type(type, obj, ctx)
1017
- 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})"
1165
+ # GraphQL-Ruby calls this method during execution when it needs the application to determine the type to use for an object.
1166
+ #
1167
+ # Usually, this object was returned from a field whose return type is an {GraphQL::Schema::Interface} or a {GraphQL::Schema::Union}.
1168
+ # 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.
1169
+ #
1170
+ # @example Returning a GraphQL type based on the object's class name
1171
+ # class MySchema < GraphQL::Schema
1172
+ # def resolve_type(_abs_type, object, _context)
1173
+ # graphql_type_name = "Types::#{object.class.name}Type"
1174
+ # graphql_type_name.constantize # If this raises a NameError, then come implement special cases in this method
1175
+ # end
1176
+ # end
1177
+ # @param abstract_type [Class, Module, nil] The Interface or Union type which is being resolved, if there is one
1178
+ # @param application_object [Object] The object returned from a field whose type must be determined
1179
+ # @param context [GraphQL::Query::Context] The query context for the currently-executing query
1180
+ # @return [Class<GraphQL::Schema::Object] The Object type definition to use for `obj`
1181
+ def resolve_type(abstract_type, application_object, context)
1182
+ 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})"
1018
1183
  end
1019
1184
  # rubocop:enable Lint/DuplicateMethods
1020
1185
 
@@ -1029,15 +1194,45 @@ module GraphQL
1029
1194
  child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
1030
1195
  end
1031
1196
  child_class.singleton_class.prepend(ResolveTypeWithType)
1032
- super
1033
- end
1034
1197
 
1035
- def object_from_id(node_id, ctx)
1036
- 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}`)"
1198
+ if use_visibility_profile?
1199
+ vis = self.visibility
1200
+ child_class.visibility = vis.dup_for(child_class)
1201
+ end
1202
+ super
1037
1203
  end
1038
1204
 
1039
- def id_from_object(object, type, ctx)
1040
- 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}`)"
1205
+ # Fetch an object based on an incoming ID and the current context. This method should return an object
1206
+ # from your application, or return `nil` if there is no object or the object shouldn't be available to this operation.
1207
+ #
1208
+ # @example Fetching an object with Rails's GlobalID
1209
+ # def self.object_from_id(object_id, _context)
1210
+ # GlobalID.find(global_id)
1211
+ # # TODO: use `context[:current_user]` to determine if this object is authorized.
1212
+ # end
1213
+ # @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`)
1214
+ # @param context [GraphQL::Query::Context] The context for the currently-executing operation
1215
+ # @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
1216
+ # @see id_from_object which produces these IDs
1217
+ def object_from_id(object_id, context)
1218
+ 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}`)"
1219
+ end
1220
+
1221
+ # Return a stable ID string for `object` so that it can be refetched later, using {.object_from_id}.
1222
+ #
1223
+ # {GlobalID}(https://github.com/rails/globalid) and {SQIDs}(https://sqids.org/ruby) can both be used to create IDs.
1224
+ #
1225
+ # @example Using Rails's GlobalID to generate IDs
1226
+ # def self.id_from_object(application_object, graphql_type, context)
1227
+ # application_object.to_gid_param
1228
+ # end
1229
+ #
1230
+ # @param application_object [Object] Some object encountered by GraphQL-Ruby while running a query
1231
+ # @param graphql_type [Class, Module] The type that GraphQL-Ruby is using for `application_object` during this query
1232
+ # @param context [GraphQL::Query::Context] The context for the operation that is currently running
1233
+ # @return [String] A stable identifier which can be passed to {.object_from_id} later to re-fetch `application_object`
1234
+ def id_from_object(application_object, graphql_type, context)
1235
+ 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}`)"
1041
1236
  end
1042
1237
 
1043
1238
  def visible?(member, ctx)
@@ -1053,6 +1248,10 @@ module GraphQL
1053
1248
  Member::HasDirectives.get_directives(self, @own_schema_directives, :schema_directives)
1054
1249
  end
1055
1250
 
1251
+ # Called when a type is needed by name at runtime
1252
+ def load_type(type_name, ctx)
1253
+ get_type(type_name, ctx)
1254
+ end
1056
1255
  # This hook is called when an object fails an `authorized?` check.
1057
1256
  # You might report to your bug tracker here, so you can correct
1058
1257
  # the field resolvers not to return unauthorized objects.
@@ -1088,6 +1287,16 @@ module GraphQL
1088
1287
  unauthorized_object(unauthorized_error)
1089
1288
  end
1090
1289
 
1290
+ # Called at runtime when GraphQL-Ruby encounters a mismatch between the application behavior
1291
+ # and the GraphQL type system.
1292
+ #
1293
+ # The default implementation of this method is to follow the GraphQL specification,
1294
+ # but you can override this to report errors to your bug tracker or customize error handling.
1295
+ # @param type_error [GraphQL::Error] several specific error classes are passed here, see the default implementation for details
1296
+ # @param context [GraphQL::Query::Context] the context for the currently-running operation
1297
+ # @return [void]
1298
+ # @raise [GraphQL::ExecutionError] to return this error to the client
1299
+ # @raise [GraphQL::Error] to crash the query and raise a developer-facing error
1091
1300
  def type_error(type_error, ctx)
1092
1301
  case type_error
1093
1302
  when GraphQL::InvalidNullError
@@ -1126,12 +1335,12 @@ module GraphQL
1126
1335
  # Add several directives at once
1127
1336
  # @param new_directives [Class]
1128
1337
  def directives(*new_directives)
1129
- if new_directives.any?
1338
+ if !new_directives.empty?
1130
1339
  new_directives.flatten.each { |d| directive(d) }
1131
1340
  end
1132
1341
 
1133
1342
  inherited_dirs = find_inherited_value(:directives, default_directives)
1134
- if own_directives.any?
1343
+ if !own_directives.empty?
1135
1344
  inherited_dirs.merge(own_directives)
1136
1345
  else
1137
1346
  inherited_dirs
@@ -1142,7 +1351,11 @@ module GraphQL
1142
1351
  # @param new_directive [Class]
1143
1352
  # @return void
1144
1353
  def directive(new_directive)
1145
- add_type_and_traverse(new_directive, root: false)
1354
+ if use_visibility_profile?
1355
+ own_directives[new_directive.graphql_name] = new_directive
1356
+ else
1357
+ add_type_and_traverse(new_directive, root: false)
1358
+ end
1146
1359
  end
1147
1360
 
1148
1361
  def default_directives
@@ -1179,6 +1392,7 @@ module GraphQL
1179
1392
  # @param mode [Symbol] Trace module will only be used for this trade mode
1180
1393
  # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
1181
1394
  # @return [void]
1395
+ # @see GraphQL::Tracing::Trace for available tracing methods
1182
1396
  def trace_with(trace_mod, mode: :default, **options)
1183
1397
  if mode.is_a?(Array)
1184
1398
  mode.each { |m| trace_with(trace_mod, mode: m, **options) }
@@ -1256,6 +1470,8 @@ module GraphQL
1256
1470
  trace_class_for_mode.new(**trace_options)
1257
1471
  end
1258
1472
 
1473
+ # @param new_analyzer [Class<GraphQL::Analysis::Analyzer>] An analyzer to run on queries to this schema
1474
+ # @see GraphQL::Analysis the analysis system
1259
1475
  def query_analyzer(new_analyzer)
1260
1476
  own_query_analyzers << new_analyzer
1261
1477
  end
@@ -1264,6 +1480,8 @@ module GraphQL
1264
1480
  find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers
1265
1481
  end
1266
1482
 
1483
+ # @param new_analyzer [Class<GraphQL::Analysis::Analyzer>] An analyzer to run on multiplexes to this schema
1484
+ # @see GraphQL::Analysis the analysis system
1267
1485
  def multiplex_analyzer(new_analyzer)
1268
1486
  own_multiplex_analyzers << new_analyzer
1269
1487
  end
@@ -1336,7 +1554,8 @@ module GraphQL
1336
1554
 
1337
1555
  # @api private
1338
1556
  def add_subscription_extension_if_necessary
1339
- if !defined?(@subscription_extension_added) && subscription && self.subscriptions
1557
+ # TODO: when there's a proper API for extending root types, migrat this to use it.
1558
+ if !defined?(@subscription_extension_added) && @subscription_object.is_a?(Class) && self.subscriptions
1340
1559
  @subscription_extension_added = true
1341
1560
  subscription.all_field_definitions.each do |field|
1342
1561
  if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) }
@@ -1346,6 +1565,11 @@ module GraphQL
1346
1565
  end
1347
1566
  end
1348
1567
 
1568
+ # Called when execution encounters a `SystemStackError`. By default, it adds a client-facing error to the response.
1569
+ # You could modify this method to report this error to your bug tracker.
1570
+ # @param query [GraphQL::Query]
1571
+ # @param err [SystemStackError]
1572
+ # @return [void]
1349
1573
  def query_stack_error(query, err)
1350
1574
  query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute."))
1351
1575
  end
@@ -1404,11 +1628,34 @@ module GraphQL
1404
1628
  end
1405
1629
  end
1406
1630
 
1631
+ # Returns `DidYouMean` if it's defined.
1632
+ # Override this to return `nil` if you don't want to use `DidYouMean`
1633
+ def did_you_mean(new_dym = NOT_CONFIGURED)
1634
+ if NOT_CONFIGURED.equal?(new_dym)
1635
+ if defined?(@did_you_mean)
1636
+ @did_you_mean
1637
+ else
1638
+ find_inherited_value(:did_you_mean, defined?(DidYouMean) ? DidYouMean : nil)
1639
+ end
1640
+ else
1641
+ @did_you_mean = new_dym
1642
+ end
1643
+ end
1644
+
1407
1645
  private
1408
1646
 
1409
1647
  def add_trace_options_for(mode, new_options)
1410
- t_opts = trace_options_for(mode)
1411
- t_opts.merge!(new_options)
1648
+ if mode == :default
1649
+ own_trace_modes.each do |mode_name, t_class|
1650
+ if t_class <= DefaultTraceClass
1651
+ t_opts = trace_options_for(mode_name)
1652
+ t_opts.merge!(new_options)
1653
+ end
1654
+ end
1655
+ else
1656
+ t_opts = trace_options_for(mode)
1657
+ t_opts.merge!(new_options)
1658
+ end
1412
1659
  nil
1413
1660
  end
1414
1661
 
@@ -1485,7 +1732,7 @@ module GraphQL
1485
1732
  end
1486
1733
 
1487
1734
  def own_references_to
1488
- @own_references_to ||= {}.tap(&:compare_by_identity)
1735
+ @own_references_to ||= {}.compare_by_identity
1489
1736
  end
1490
1737
 
1491
1738
  def non_introspection_types
@@ -1501,7 +1748,7 @@ module GraphQL
1501
1748
  end
1502
1749
 
1503
1750
  def own_possible_types
1504
- @own_possible_types ||= {}.tap(&:compare_by_identity)
1751
+ @own_possible_types ||= {}.compare_by_identity
1505
1752
  end
1506
1753
 
1507
1754
  def own_union_memberships
@@ -1552,5 +1799,12 @@ module GraphQL
1552
1799
 
1553
1800
  # Install these here so that subclasses will also install it.
1554
1801
  self.connections = GraphQL::Pagination::Connections.new(schema: self)
1802
+
1803
+ # @api private
1804
+ module DefaultTraceClass
1805
+ end
1555
1806
  end
1556
1807
  end
1808
+
1809
+ require "graphql/schema/loader"
1810
+ require "graphql/schema/printer"