graphql 2.3.5 → 2.3.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +46 -0
  3. data/lib/graphql/analysis/analyzer.rb +89 -0
  4. data/lib/graphql/analysis/field_usage.rb +82 -0
  5. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  6. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  7. data/lib/graphql/analysis/query_complexity.rb +183 -0
  8. data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
  9. data/lib/graphql/analysis/visitor.rb +283 -0
  10. data/lib/graphql/analysis.rb +92 -1
  11. data/lib/graphql/current.rb +52 -0
  12. data/lib/graphql/dataloader/async_dataloader.rb +2 -0
  13. data/lib/graphql/dataloader/source.rb +5 -2
  14. data/lib/graphql/dataloader.rb +4 -1
  15. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  16. data/lib/graphql/execution/interpreter/runtime.rb +29 -25
  17. data/lib/graphql/execution/interpreter.rb +3 -1
  18. data/lib/graphql/execution/lookahead.rb +10 -10
  19. data/lib/graphql/introspection/directive_type.rb +1 -1
  20. data/lib/graphql/introspection/entry_points.rb +2 -2
  21. data/lib/graphql/introspection/field_type.rb +1 -1
  22. data/lib/graphql/introspection/schema_type.rb +6 -11
  23. data/lib/graphql/introspection/type_type.rb +5 -5
  24. data/lib/graphql/language/document_from_schema_definition.rb +19 -26
  25. data/lib/graphql/language/lexer.rb +0 -3
  26. data/lib/graphql/language/nodes.rb +2 -2
  27. data/lib/graphql/language/parser.rb +9 -1
  28. data/lib/graphql/language/sanitized_printer.rb +1 -1
  29. data/lib/graphql/language.rb +0 -1
  30. data/lib/graphql/query/context.rb +7 -1
  31. data/lib/graphql/query/null_context.rb +2 -2
  32. data/lib/graphql/query/validation_pipeline.rb +2 -2
  33. data/lib/graphql/query.rb +26 -7
  34. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +129 -0
  35. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  36. data/lib/graphql/rubocop.rb +2 -0
  37. data/lib/graphql/schema/addition.rb +1 -0
  38. data/lib/graphql/schema/always_visible.rb +1 -0
  39. data/lib/graphql/schema/argument.rb +19 -5
  40. data/lib/graphql/schema/build_from_definition.rb +8 -1
  41. data/lib/graphql/schema/directive/flagged.rb +1 -1
  42. data/lib/graphql/schema/directive.rb +2 -0
  43. data/lib/graphql/schema/enum.rb +51 -20
  44. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  45. data/lib/graphql/schema/field.rb +85 -39
  46. data/lib/graphql/schema/has_single_input_argument.rb +2 -1
  47. data/lib/graphql/schema/input_object.rb +8 -7
  48. data/lib/graphql/schema/interface.rb +20 -4
  49. data/lib/graphql/schema/introspection_system.rb +5 -16
  50. data/lib/graphql/schema/member/has_arguments.rb +14 -9
  51. data/lib/graphql/schema/member/has_fields.rb +8 -6
  52. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  53. data/lib/graphql/schema/resolver.rb +5 -5
  54. data/lib/graphql/schema/subset.rb +509 -0
  55. data/lib/graphql/schema/type_expression.rb +2 -2
  56. data/lib/graphql/schema/types_migration.rb +187 -0
  57. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  58. data/lib/graphql/schema/validator.rb +2 -0
  59. data/lib/graphql/schema/warden.rb +89 -5
  60. data/lib/graphql/schema.rb +109 -53
  61. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  62. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  63. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  64. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  65. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -2
  66. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  67. data/lib/graphql/static_validation/rules/fields_will_merge.rb +7 -7
  68. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  69. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  70. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  71. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  72. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  73. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -3
  74. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  75. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  76. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  77. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  78. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  79. data/lib/graphql/static_validation/validation_context.rb +2 -2
  80. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  81. data/lib/graphql/subscriptions/event.rb +1 -1
  82. data/lib/graphql/subscriptions.rb +3 -3
  83. data/lib/graphql/testing/helpers.rb +8 -5
  84. data/lib/graphql/types/relay/connection_behaviors.rb +10 -0
  85. data/lib/graphql/types/relay/edge_behaviors.rb +10 -0
  86. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  87. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  88. data/lib/graphql/version.rb +1 -1
  89. data/lib/graphql.rb +3 -0
  90. metadata +31 -13
  91. data/lib/graphql/analysis/ast/analyzer.rb +0 -91
  92. data/lib/graphql/analysis/ast/field_usage.rb +0 -84
  93. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  94. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  95. data/lib/graphql/analysis/ast/query_complexity.rb +0 -185
  96. data/lib/graphql/analysis/ast/visitor.rb +0 -284
  97. data/lib/graphql/analysis/ast.rb +0 -94
  98. data/lib/graphql/language/token.rb +0 -34
  99. data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ # You can add this plugin to your schema to see how {GraphQL::Schema::Warden} and {GraphQL::Schema::Subset}
5
+ # handle `.visible?` differently in your schema.
6
+ #
7
+ # This plugin runs the same method on both implementations and raises an error when the results diverge.
8
+ #
9
+ # To fix the error, modify your schema so that both implementations return the same thing.
10
+ # Or, open an issue on GitHub to discuss the difference.
11
+ #
12
+ # This plugin adds overhead to runtime and may cause unexpected crashes -- **don't** use it in production!
13
+ #
14
+ # This plugin adds two keys to `context` when running:
15
+ #
16
+ # - `types_migration_running: true`
17
+ # - For the {Warden} which it instantiates, it adds `types_migration_warden_running: true`.
18
+ #
19
+ # Use those keys to modify your `visible?` behavior as needed.
20
+ #
21
+ # Also, in a pinch, you can set `skip_types_migration_error: true` in context to turn off this plugin's behavior per-query.
22
+ # (In that case, it uses {Subset} directly.)
23
+ #
24
+ # @example Adding this plugin
25
+ #
26
+ # if !Rails.env.production?
27
+ # use GraphQL::Schema::TypesMigration
28
+ # end
29
+ class TypesMigration < GraphQL::Schema::Subset
30
+ def self.use(schema)
31
+ schema.subset_class = self
32
+ end
33
+
34
+ class RuntimeTypesMismatchError < GraphQL::Error
35
+ def initialize(method_called, warden_result, subset_result, method_args)
36
+ super(<<~ERR)
37
+ Mismatch in types for `##{method_called}(#{method_args.map(&:inspect).join(", ")})`:
38
+
39
+ #{compare_results(warden_result, subset_result)}
40
+
41
+ Update your `.visible?` implementation to make these implementations return the same value.
42
+
43
+ See: https://graphql-ruby.org/authorization/visibility_migration.html
44
+ ERR
45
+ end
46
+
47
+ private
48
+ def compare_results(warden_result, subset_result)
49
+ if warden_result.is_a?(Array) && subset_result.is_a?(Array)
50
+ all_results = warden_result | subset_result
51
+ all_results.sort_by!(&:graphql_name)
52
+
53
+ entries_text = all_results.map { |entry| "#{entry.graphql_name} (#{entry})"}
54
+ width = entries_text.map(&:size).max
55
+ yes = " ✔ "
56
+ no = " "
57
+ res = "".dup
58
+ res << "#{"Result".center(width)} Warden Subset \n"
59
+ all_results.each_with_index do |entry, idx|
60
+ res << "#{entries_text[idx].ljust(width)}#{warden_result.include?(entry) ? yes : no}#{subset_result.include?(entry) ? yes : no}\n"
61
+ end
62
+ res << "\n"
63
+ else
64
+ "- Warden returned: #{humanize(warden_result)}\n\n- Subset returned: #{humanize(subset_result)}"
65
+ end
66
+ end
67
+ def humanize(val)
68
+ case val
69
+ when Array
70
+ "#{val.size}: #{val.map { |v| humanize(v) }.sort.inspect}"
71
+ when Module
72
+ if val.respond_to?(:graphql_name)
73
+ "#{val.graphql_name} (#{val.inspect})"
74
+ else
75
+ val.inspect
76
+ end
77
+ else
78
+ val.inspect
79
+ end
80
+ end
81
+ end
82
+
83
+ def initialize(context:, schema:)
84
+ @skip_error = context[:skip_types_migration_error]
85
+ context[:types_migration_running] = true
86
+ @subset_types = GraphQL::Schema::Subset.new(context: context, schema: schema)
87
+ if !@skip_error
88
+ warden_ctx_vals = context.to_h.dup
89
+ warden_ctx_vals[:types_migration_warden_running] = true
90
+ if defined?(schema::WardenCompatSchema)
91
+ warden_schema = schema::WardenCompatSchema
92
+ else
93
+ warden_schema = Class.new(schema)
94
+ warden_schema.use_schema_subset = false
95
+ # TODO public API
96
+ warden_schema.send(:add_type_and_traverse, [warden_schema.query, warden_schema.mutation, warden_schema.subscription].compact, root: true)
97
+ warden_schema.send(:add_type_and_traverse, warden_schema.directives.values + warden_schema.orphan_types, root: false)
98
+ end
99
+ warden_ctx = GraphQL::Query::Context.new(query: context.query, values: warden_ctx_vals)
100
+ example_warden = GraphQL::Schema::Warden.new(schema: warden_schema, context: warden_ctx)
101
+ @warden_types = example_warden.schema_subset
102
+ warden_ctx.warden = example_warden
103
+ warden_ctx.types = @warden_types
104
+ end
105
+ end
106
+
107
+ def loaded_types
108
+ @subset_types.loaded_types
109
+ end
110
+
111
+ PUBLIC_SUBSET_METHODS = [
112
+ :enum_values,
113
+ :interfaces,
114
+ :all_types,
115
+ :fields,
116
+ :loadable?,
117
+ :type,
118
+ :arguments,
119
+ :argument,
120
+ :directive_exists?,
121
+ :directives,
122
+ :field,
123
+ :query_root,
124
+ :mutation_root,
125
+ :possible_types,
126
+ :subscription_root,
127
+ :reachable_type?
128
+ ]
129
+
130
+ PUBLIC_SUBSET_METHODS.each do |subset_method|
131
+ define_method(subset_method) do |*args|
132
+ call_method_and_compare(subset_method, args)
133
+ end
134
+ end
135
+
136
+ def call_method_and_compare(method, args)
137
+ res_1 = @subset_types.public_send(method, *args)
138
+ if @skip_error
139
+ return res_1
140
+ end
141
+
142
+ res_2 = @warden_types.public_send(method, *args)
143
+ normalized_res_1 = res_1.is_a?(Array) ? Set.new(res_1) : res_1
144
+ normalized_res_2 = res_2.is_a?(Array) ? Set.new(res_2) : res_2
145
+ if !equivalent_schema_members?(normalized_res_1, normalized_res_2)
146
+ # Raise the errors with the orignally returned values:
147
+ err = RuntimeTypesMismatchError.new(method, res_2, res_1, args)
148
+ raise err
149
+ else
150
+ res_1
151
+ end
152
+ end
153
+
154
+ def equivalent_schema_members?(member1, member2)
155
+ if member1.class != member2.class
156
+ return false
157
+ end
158
+
159
+ case member1
160
+ when Set
161
+ member1_array = member1.to_a.sort_by(&:graphql_name)
162
+ member2_array = member2.to_a.sort_by(&:graphql_name)
163
+ member1_array.each_with_index do |inner_member1, idx|
164
+ inner_member2 = member2_array[idx]
165
+ equivalent_schema_members?(inner_member1, inner_member2)
166
+ end
167
+ when GraphQL::Schema::Field
168
+ member1.ensure_loaded
169
+ member2.ensure_loaded
170
+ if member1.introspection? && member2.introspection?
171
+ member1.inspect == member2.inspect
172
+ else
173
+ member1 == member2
174
+ end
175
+ when Module
176
+ if member1.introspection? && member2.introspection?
177
+ member1.graphql_name == member2.graphql_name
178
+ else
179
+ member1 == member2
180
+ end
181
+ else
182
+ member1 == member2
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Validator
6
+ # Use this to validate each member of an array value.
7
+ #
8
+ # @example validate format of all strings in an array
9
+ #
10
+ # argument :handles, [String],
11
+ # validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ } } }
12
+ #
13
+ # @example multiple validators can be combined
14
+ #
15
+ # argument :handles, [String],
16
+ # validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ }, length: { maximum: 32 } } }
17
+ #
18
+ # @example any type can be used
19
+ #
20
+ # argument :choices, [Integer],
21
+ # validates: { all: { inclusion: { in: 1..12 } } }
22
+ #
23
+ class AllValidator < Validator
24
+ def initialize(validated:, allow_blank: false, allow_null: false, **validators)
25
+ super(validated: validated, allow_blank: allow_blank, allow_null: allow_null)
26
+
27
+ @validators = Validator.from_config(validated, validators)
28
+ end
29
+
30
+ def validate(object, context, value)
31
+ return EMPTY_ARRAY if permitted_empty_value?(value)
32
+
33
+ all_errors = EMPTY_ARRAY
34
+
35
+ value.each do |subvalue|
36
+ @validators.each do |validator|
37
+ errors = validator.validate(object, context, subvalue)
38
+ if errors &&
39
+ (errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
40
+ (errors.is_a?(String))
41
+ if all_errors.frozen? # It's empty
42
+ all_errors = []
43
+ end
44
+ if errors.is_a?(String)
45
+ all_errors << errors
46
+ else
47
+ all_errors.concat(errors)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ unless all_errors.frozen?
54
+ all_errors.uniq!
55
+ end
56
+
57
+ all_errors
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -169,3 +169,5 @@ require "graphql/schema/validator/allow_null_validator"
169
169
  GraphQL::Schema::Validator.install(:allow_null, GraphQL::Schema::Validator::AllowNullValidator)
170
170
  require "graphql/schema/validator/allow_blank_validator"
171
171
  GraphQL::Schema::Validator.install(:allow_blank, GraphQL::Schema::Validator::AllowBlankValidator)
172
+ require "graphql/schema/validator/all_validator"
173
+ GraphQL::Schema::Validator.install(:all, GraphQL::Schema::Validator::AllValidator)
@@ -61,14 +61,27 @@ module GraphQL
61
61
  def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
62
62
  def arguments(owner, ctx); owner.arguments(ctx); end
63
63
  def loadable?(type, ctx); type.visible?(ctx); end
64
+ def schema_subset
65
+ @schema_subset ||= Warden::SchemaSubset.new(self)
66
+ end
64
67
  end
65
68
  end
66
69
 
67
70
  class NullWarden
68
71
  def initialize(_filter = nil, context:, schema:)
69
72
  @schema = schema
73
+ @schema_subset = Warden::SchemaSubset.new(self)
74
+ end
75
+
76
+ # @api private
77
+ module NullSubset
78
+ def self.new(context:, schema:)
79
+ NullWarden.new(context: context, schema: schema).schema_subset
80
+ end
70
81
  end
71
82
 
83
+ attr_reader :schema_subset
84
+
72
85
  def visible_field?(field_defn, _ctx = nil, owner = nil); true; end
73
86
  def visible_argument?(arg_defn, _ctx = nil); true; end
74
87
  def visible_type?(type_defn, _ctx = nil); true; end
@@ -91,6 +104,80 @@ module GraphQL
91
104
  def interfaces(obj_type); obj_type.interfaces; end
92
105
  end
93
106
 
107
+ def schema_subset
108
+ @schema_subset ||= SchemaSubset.new(self)
109
+ end
110
+
111
+ class SchemaSubset
112
+ def initialize(warden)
113
+ @warden = warden
114
+ end
115
+
116
+ def directives
117
+ @warden.directives
118
+ end
119
+
120
+ def directive_exists?(dir_name)
121
+ @warden.directives.any? { |d| d.graphql_name == dir_name }
122
+ end
123
+
124
+ def type(name)
125
+ @warden.get_type(name)
126
+ end
127
+
128
+ def field(owner, field_name)
129
+ @warden.get_field(owner, field_name)
130
+ end
131
+
132
+ def argument(owner, arg_name)
133
+ @warden.get_argument(owner, arg_name)
134
+ end
135
+
136
+ def query_root
137
+ @warden.root_type_for_operation("query")
138
+ end
139
+
140
+ def mutation_root
141
+ @warden.root_type_for_operation("mutation")
142
+ end
143
+
144
+ def subscription_root
145
+ @warden.root_type_for_operation("subscription")
146
+ end
147
+
148
+ def arguments(owner)
149
+ @warden.arguments(owner)
150
+ end
151
+
152
+ def fields(owner)
153
+ @warden.fields(owner)
154
+ end
155
+
156
+ def possible_types(type)
157
+ @warden.possible_types(type)
158
+ end
159
+
160
+ def enum_values(enum_type)
161
+ @warden.enum_values(enum_type)
162
+ end
163
+
164
+ def all_types
165
+ @warden.reachable_types
166
+ end
167
+
168
+ def interfaces(obj_type)
169
+ @warden.interfaces(obj_type)
170
+ end
171
+
172
+ def loadable?(t, ctx) # TODO remove ctx here?
173
+ @warden.loadable?(t, ctx)
174
+ end
175
+
176
+ def reachable_type?(type_name)
177
+ !!@warden.reachable_type?(type_name)
178
+ end
179
+ end
180
+
94
181
  # @param context [GraphQL::Query::Context]
95
182
  # @param schema [GraphQL::Schema]
96
183
  def initialize(context:, schema:)
@@ -101,13 +188,12 @@ module GraphQL
101
188
  @subscription = @schema.subscription
102
189
  @context = context
103
190
  @visibility_cache = read_through { |m| schema.visible?(m, context) }
104
- @visibility_cache.compare_by_identity
105
191
  # Initialize all ivars to improve object shape consistency:
106
192
  @types = @visible_types = @reachable_types = @visible_parent_fields =
107
193
  @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
108
194
  @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
109
195
  @visible_and_reachable_type = @unions = @unfiltered_interfaces =
110
- @reachable_type_set =
196
+ @reachable_type_set = @schema_subset =
111
197
  nil
112
198
  end
113
199
 
@@ -376,9 +462,7 @@ module GraphQL
376
462
  end
377
463
 
378
464
  def read_through
379
- h = Hash.new { |h, k| h[k] = yield(k) }
380
- h.compare_by_identity
381
- h
465
+ Hash.new { |h, k| h[k] = yield(k) }.compare_by_identity
382
466
  end
383
467
 
384
468
  def reachable_type_set
@@ -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,8 @@ 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/subset"
49
+ require "graphql/schema/types_migration"
49
50
 
50
51
  module GraphQL
51
52
  # A GraphQL schema which may be queried with {GraphQL::Query}.
@@ -338,6 +339,9 @@ 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_schema_subset?
343
+ return Subset.from_context(context, self).all_types_h
344
+ end
341
345
  all_types = non_introspection_types.merge(introspection_system.types)
342
346
  visible_types = {}
343
347
  all_types.each do |k, v|
@@ -365,25 +369,32 @@ module GraphQL
365
369
  # @param type_name [String]
366
370
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
367
371
  def get_type(type_name, context = GraphQL::Query::NullContext.instance)
372
+ if use_schema_subset?
373
+ return Subset.from_context(context, self).type(type_name)
374
+ end
368
375
  local_entry = own_types[type_name]
369
376
  type_defn = case local_entry
370
377
  when nil
371
378
  nil
372
379
  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
- )
380
+ if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
381
+ local_entry
382
+ else
383
+ visible_t = nil
384
+ warden = Warden.from_context(context)
385
+ local_entry.each do |t|
386
+ if warden.visible_type?(t, context)
387
+ if visible_t.nil?
388
+ visible_t = t
389
+ else
390
+ raise DuplicateNamesError.new(
391
+ duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
392
+ )
393
+ end
383
394
  end
384
395
  end
396
+ visible_t
385
397
  end
386
- visible_t
387
398
  when Module
388
399
  local_entry
389
400
  else
@@ -419,48 +430,62 @@ module GraphQL
419
430
  end
420
431
  end
421
432
 
422
- def new_connections?
423
- !!connections
424
- end
425
-
426
- def query(new_query_object = nil)
427
- if new_query_object
433
+ def query(new_query_object = nil, &lazy_load_block)
434
+ if new_query_object || block_given?
428
435
  if @query_object
429
- raise GraphQL::Error, "Second definition of `query(...)` (#{new_query_object.inspect}) is invalid, already configured with #{@query_object.inspect}"
436
+ dup_defn = new_query_object || yield
437
+ raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
438
+ elsif use_schema_subset?
439
+ @query_object = block_given? ? lazy_load_block : new_query_object
430
440
  else
431
- @query_object = new_query_object
432
- add_type_and_traverse(new_query_object, root: true)
433
- nil
441
+ @query_object = new_query_object || lazy_load_block.call
442
+ add_type_and_traverse(@query_object, root: true)
434
443
  end
444
+ nil
445
+ elsif @query_object.is_a?(Proc)
446
+ @query_object = @query_object.call
435
447
  else
436
448
  @query_object || find_inherited_value(:query)
437
449
  end
438
450
  end
439
451
 
440
- def mutation(new_mutation_object = nil)
441
- if new_mutation_object
452
+ def mutation(new_mutation_object = nil, &lazy_load_block)
453
+ if new_mutation_object || block_given?
442
454
  if @mutation_object
443
- raise GraphQL::Error, "Second definition of `mutation(...)` (#{new_mutation_object.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
455
+ dup_defn = new_mutation_object || yield
456
+ raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
457
+ elsif use_schema_subset?
458
+ @mutation_object = block_given? ? lazy_load_block : new_mutation_object
444
459
  else
445
- @mutation_object = new_mutation_object
446
- add_type_and_traverse(new_mutation_object, root: true)
447
- nil
460
+ @mutation_object = new_mutation_object || lazy_load_block.call
461
+ add_type_and_traverse(@mutation_object, root: true)
448
462
  end
463
+ nil
464
+ elsif @mutation_object.is_a?(Proc)
465
+ @mutation_object = @mutation_object.call
449
466
  else
450
467
  @mutation_object || find_inherited_value(:mutation)
451
468
  end
452
469
  end
453
470
 
454
- def subscription(new_subscription_object = nil)
455
- if new_subscription_object
471
+ def subscription(new_subscription_object = nil, &lazy_load_block)
472
+ if new_subscription_object || block_given?
456
473
  if @subscription_object
457
- raise GraphQL::Error, "Second definition of `subscription(...)` (#{new_subscription_object.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
474
+ dup_defn = new_subscription_object || yield
475
+ raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
476
+ elsif use_schema_subset?
477
+ @subscription_object = block_given? ? lazy_load_block : new_subscription_object
478
+ add_subscription_extension_if_necessary
458
479
  else
459
- @subscription_object = new_subscription_object
480
+ @subscription_object = new_subscription_object || lazy_load_block.call
460
481
  add_subscription_extension_if_necessary
461
- add_type_and_traverse(new_subscription_object, root: true)
462
- nil
482
+ add_type_and_traverse(@subscription_object, root: true)
463
483
  end
484
+ nil
485
+ elsif @subscription_object.is_a?(Proc)
486
+ @subscription_object = @subscription_object.call
487
+ add_subscription_extension_if_necessary
488
+ @subscription_object
464
489
  else
465
490
  @subscription_object || find_inherited_value(:subscription)
466
491
  end
@@ -482,7 +507,11 @@ module GraphQL
482
507
  end
483
508
 
484
509
  def root_types
485
- @root_types
510
+ if use_schema_subset?
511
+ [query, mutation, subscription].compact
512
+ else
513
+ @root_types
514
+ end
486
515
  end
487
516
 
488
517
  def warden_class
@@ -497,10 +526,39 @@ module GraphQL
497
526
 
498
527
  attr_writer :warden_class
499
528
 
529
+ def subset_class
530
+ if defined?(@subset_class)
531
+ @subset_class
532
+ elsif superclass.respond_to?(:subset_class)
533
+ superclass.subset_class
534
+ else
535
+ GraphQL::Schema::Subset
536
+ end
537
+ end
538
+
539
+ attr_writer :subset_class, :use_schema_subset
540
+
541
+ def use_schema_subset?
542
+ if defined?(@use_schema_subset)
543
+ @use_schema_subset
544
+ elsif superclass.respond_to?(:use_schema_subset?)
545
+ superclass.use_schema_subset?
546
+ else
547
+ false
548
+ end
549
+ end
550
+
500
551
  # @param type [Module] The type definition whose possible types you want to see
501
552
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
502
553
  # @return [Array<Module>] Possible types for `type`, if it's given.
503
554
  def possible_types(type = nil, context = GraphQL::Query::NullContext.instance)
555
+ if use_schema_subset?
556
+ if type
557
+ return Subset.from_context(context, self).possible_types(type)
558
+ else
559
+ raise "Schema.possible_types is not implemented for `use_schema_subset?`"
560
+ end
561
+ end
504
562
  if type
505
563
  # TODO duck-typing `.possible_types` would probably be nicer here
506
564
  if type.kind.union?
@@ -573,9 +631,8 @@ module GraphQL
573
631
  end
574
632
  end
575
633
 
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)
634
+ def type_from_ast(ast_node, context: self.query_class.new(self, "{ __typename }").context)
635
+ GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
579
636
  end
580
637
 
581
638
  def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance)
@@ -769,16 +826,6 @@ module GraphQL
769
826
  @analysis_engine || find_inherited_value(:analysis_engine, self.default_analysis_engine)
770
827
  end
771
828
 
772
- def using_ast_analysis?
773
- true
774
- end
775
-
776
- def interpreter?
777
- true
778
- end
779
-
780
- attr_writer :interpreter
781
-
782
829
  def error_bubbling(new_error_bubbling = nil)
783
830
  if !new_error_bubbling.nil?
784
831
  warn("error_bubbling(#{new_error_bubbling.inspect}) is deprecated; the default value of `false` will be the only option in GraphQL-Ruby 3.0")
@@ -887,7 +934,7 @@ module GraphQL
887
934
  To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
888
935
  ERR
889
936
  end
890
- add_type_and_traverse(new_orphan_types, root: false)
937
+ add_type_and_traverse(new_orphan_types, root: false) unless use_schema_subset?
891
938
  own_orphan_types.concat(new_orphan_types.flatten)
892
939
  end
893
940
 
@@ -1053,6 +1100,10 @@ module GraphQL
1053
1100
  Member::HasDirectives.get_directives(self, @own_schema_directives, :schema_directives)
1054
1101
  end
1055
1102
 
1103
+ # Called when a type is needed by name at runtime
1104
+ def load_type(type_name, ctx)
1105
+ get_type(type_name, ctx)
1106
+ end
1056
1107
  # This hook is called when an object fails an `authorized?` check.
1057
1108
  # You might report to your bug tracker here, so you can correct
1058
1109
  # the field resolvers not to return unauthorized objects.
@@ -1142,7 +1193,11 @@ module GraphQL
1142
1193
  # @param new_directive [Class]
1143
1194
  # @return void
1144
1195
  def directive(new_directive)
1145
- add_type_and_traverse(new_directive, root: false)
1196
+ if use_schema_subset?
1197
+ own_directives[new_directive.graphql_name] = new_directive
1198
+ else
1199
+ add_type_and_traverse(new_directive, root: false)
1200
+ end
1146
1201
  end
1147
1202
 
1148
1203
  def default_directives
@@ -1336,7 +1391,8 @@ module GraphQL
1336
1391
 
1337
1392
  # @api private
1338
1393
  def add_subscription_extension_if_necessary
1339
- if !defined?(@subscription_extension_added) && subscription && self.subscriptions
1394
+ # TODO: when there's a proper API for extending root types, migrat this to use it.
1395
+ if !defined?(@subscription_extension_added) && @subscription_object.is_a?(Class) && self.subscriptions
1340
1396
  @subscription_extension_added = true
1341
1397
  subscription.all_field_definitions.each do |field|
1342
1398
  if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) }
@@ -1485,7 +1541,7 @@ module GraphQL
1485
1541
  end
1486
1542
 
1487
1543
  def own_references_to
1488
- @own_references_to ||= {}.tap(&:compare_by_identity)
1544
+ @own_references_to ||= {}.compare_by_identity
1489
1545
  end
1490
1546
 
1491
1547
  def non_introspection_types
@@ -1501,7 +1557,7 @@ module GraphQL
1501
1557
  end
1502
1558
 
1503
1559
  def own_possible_types
1504
- @own_possible_types ||= {}.tap(&:compare_by_identity)
1560
+ @own_possible_types ||= {}.compare_by_identity
1505
1561
  end
1506
1562
 
1507
1563
  def own_union_memberships