graphql 2.3.5 → 2.3.14

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 (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