graphql 2.3.7 → 2.4.5

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