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
@@ -135,10 +135,11 @@ module GraphQL
135
135
 
136
136
  def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
137
137
  warden = Warden.from_context(context)
138
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
138
139
  for ancestor in ancestors
139
140
  if ancestor.respond_to?(:own_arguments) &&
140
141
  (a = ancestor.own_arguments[argument_name]) &&
141
- (a = Warden.visible_entry?(:visible_argument?, a, context, warden))
142
+ (skip_visible || (a = Warden.visible_entry?(:visible_argument?, a, context, warden)))
142
143
  return a
143
144
  end
144
145
  end
@@ -197,16 +198,20 @@ module GraphQL
197
198
  end
198
199
 
199
200
  def all_argument_definitions
200
- all_defns = own_arguments.values
201
- all_defns.flatten!
202
- all_defns
201
+ if own_arguments.any?
202
+ all_defns = own_arguments.values
203
+ all_defns.flatten!
204
+ all_defns
205
+ else
206
+ EmptyObjects::EMPTY_ARRAY
207
+ end
203
208
  end
204
209
 
205
210
  # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
206
211
  def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
207
212
  warden = Warden.from_context(context)
208
- if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))
209
- visible_arg
213
+ if (arg_config = own_arguments[argument_name]) && ((context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)) || (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden)))
214
+ visible_arg || arg_config
210
215
  elsif defined?(@resolver_class) && @resolver_class
211
216
  @resolver_class.get_field_argument(argument_name, context)
212
217
  else
@@ -230,7 +235,7 @@ module GraphQL
230
235
  # @return [Interpreter::Arguments, Execution::Lazy<Interpreter::Arguments>]
231
236
  def coerce_arguments(parent_object, values, context, &block)
232
237
  # Cache this hash to avoid re-merging it
233
- arg_defns = context.warden.arguments(self)
238
+ arg_defns = context.types.arguments(self)
234
239
  total_args_count = arg_defns.size
235
240
 
236
241
  finished_args = nil
@@ -364,8 +369,8 @@ module GraphQL
364
369
  end
365
370
 
366
371
  if !(
367
- context.warden.possible_types(argument.loads).include?(application_object_type) ||
368
- context.warden.loadable?(argument.loads, context)
372
+ context.types.possible_types(argument.loads).include?(application_object_type) ||
373
+ context.types.loadable?(argument.loads, context)
369
374
  )
370
375
  err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object)
371
376
  application_object = load_application_object_failed(err)
@@ -99,11 +99,12 @@ module GraphQL
99
99
  module InterfaceMethods
100
100
  def get_field(field_name, context = GraphQL::Query::NullContext.instance)
101
101
  warden = Warden.from_context(context)
102
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
102
103
  for ancestor in ancestors
103
104
  if ancestor.respond_to?(:own_fields) &&
104
105
  (f_entry = ancestor.own_fields[field_name]) &&
105
- (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
106
- return f
106
+ (skip_visible || (f_entry = Warden.visible_entry?(:visible_field?, f_entry, context, warden)))
107
+ return f_entry
107
108
  end
108
109
  end
109
110
  nil
@@ -120,7 +121,7 @@ module GraphQL
120
121
  # Choose the most local definition that passes `.visible?` --
121
122
  # stop checking for fields by name once one has been found.
122
123
  if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
123
- visible_fields[field_name] = f
124
+ visible_fields[field_name] = f.ensure_loaded
124
125
  end
125
126
  end
126
127
  end
@@ -134,13 +135,14 @@ module GraphQL
134
135
  # Objects need to check that the interface implementation is visible, too
135
136
  warden = Warden.from_context(context)
136
137
  ancs = ancestors
138
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
137
139
  i = 0
138
140
  while (ancestor = ancs[i])
139
141
  if ancestor.respond_to?(:own_fields) &&
140
142
  visible_interface_implementation?(ancestor, context, warden) &&
141
143
  (f_entry = ancestor.own_fields[field_name]) &&
142
- (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
143
- return f
144
+ (skip_visible || (f_entry = Warden.visible_entry?(:visible_field?, f_entry, context, warden)))
145
+ return (skip_visible ? f_entry : f_entry.ensure_loaded)
144
146
  end
145
147
  i += 1
146
148
  end
@@ -159,7 +161,7 @@ module GraphQL
159
161
  # Choose the most local definition that passes `.visible?` --
160
162
  # stop checking for fields by name once one has been found.
161
163
  if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
162
- visible_fields[field_name] = f
164
+ visible_fields[field_name] = f.ensure_loaded
163
165
  end
164
166
  end
165
167
  end
@@ -7,7 +7,11 @@ module GraphQL
7
7
  module HasUnresolvedTypeError
8
8
  private
9
9
  def add_unresolved_type_error(child_class)
10
- child_class.const_set(:UnresolvedTypeError, Class.new(GraphQL::UnresolvedTypeError))
10
+ if child_class.name # Don't set this for anonymous classes
11
+ child_class.const_set(:UnresolvedTypeError, Class.new(GraphQL::UnresolvedTypeError))
12
+ else
13
+ child_class.const_set(:UnresolvedTypeError, UnresolvedTypeError)
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -25,6 +25,7 @@ module GraphQL
25
25
  extend GraphQL::Schema::Member::HasValidators
26
26
  include Schema::Member::HasPath
27
27
  extend Schema::Member::HasPath
28
+ extend Schema::Member::HasDirectives
28
29
 
29
30
  # @param object [Object] The application object that this field is being resolved on
30
31
  # @param context [GraphQL::Query::Context]
@@ -35,7 +36,7 @@ module GraphQL
35
36
  @field = field
36
37
  # Since this hash is constantly rebuilt, cache it for this call
37
38
  @arguments_by_keyword = {}
38
- self.class.arguments(context).each do |name, arg|
39
+ context.types.arguments(self.class).each do |arg|
39
40
  @arguments_by_keyword[arg.keyword] = arg
40
41
  end
41
42
  @prepared_arguments = nil
@@ -151,7 +152,7 @@ module GraphQL
151
152
  # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
152
153
  def authorized?(**inputs)
153
154
  arg_owner = @field # || self.class
154
- args = arg_owner.arguments(context)
155
+ args = context.types.arguments(arg_owner)
155
156
  authorize_arguments(args, inputs)
156
157
  end
157
158
 
@@ -168,7 +169,7 @@ module GraphQL
168
169
  private
169
170
 
170
171
  def authorize_arguments(args, inputs)
171
- args.each_value do |argument|
172
+ args.each do |argument|
172
173
  arg_keyword = argument.keyword
173
174
  if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
174
175
  auth_result = argument.authorized?(self, arg_value, context)
@@ -181,10 +182,9 @@ module GraphQL
181
182
  elsif auth_result == false
182
183
  return auth_result
183
184
  end
184
- else
185
- true
186
185
  end
187
186
  end
187
+ true
188
188
  end
189
189
 
190
190
  def load_arguments(args)
@@ -0,0 +1,509 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ # This class filters the types, fields, arguments, enum values, and directives in a schema
6
+ # based on the given `context`.
7
+ #
8
+ # It's like {Warden}, but has some differences:
9
+ #
10
+ # - It doesn't use {Schema}'s top-level caches (eg {Schema.references_to}, {Schema.possible_types}, {Schema.types})
11
+ # - It doesn't hide Interface or Union types when all their possible types are hidden. (Instead, those types should implement `.visible?` to hide in that case.)
12
+ # - It checks `.visible?` on root introspection types
13
+ #
14
+ # In the future, {Subset} will support lazy-loading types as needed during execution and multi-request caching of subsets.
15
+ #
16
+ # @see Schema::TypesMigration for a helper class in adopting this filter
17
+ class Subset
18
+ # @return [Schema::Subset]
19
+ def self.from_context(ctx, schema)
20
+ if ctx.respond_to?(:types) && (types = ctx.types).is_a?(self)
21
+ types
22
+ else
23
+ # TODO use a cached instance from the schema
24
+ self.new(context: ctx, schema: schema)
25
+ end
26
+ end
27
+
28
+ def self.pass_thru(context:, schema:)
29
+ subset = self.new(context: context, schema: schema)
30
+ subset.instance_variable_set(:@cached_visible, Hash.new { |h,k| h[k] = true })
31
+ subset
32
+ end
33
+
34
+ def initialize(context:, schema:)
35
+ @context = context
36
+ @schema = schema
37
+ @all_types = {}
38
+ @all_types_loaded = false
39
+ @unvisited_types = []
40
+ @referenced_types = Hash.new { |h, type_defn| h[type_defn] = [] }.compare_by_identity
41
+ @cached_directives = {}
42
+ @all_directives = nil
43
+ @cached_visible = Hash.new { |h, member|
44
+ h[member] = @schema.visible?(member, @context)
45
+ }.compare_by_identity
46
+
47
+ @cached_visible_fields = Hash.new { |h, owner|
48
+ h[owner] = Hash.new do |h2, field|
49
+ h2[field] = if @cached_visible[field] &&
50
+ (ret_type = field.type.unwrap) &&
51
+ @cached_visible[ret_type] &&
52
+ (owner == field.owner || (!owner.kind.object?) || field_on_visible_interface?(field, owner))
53
+
54
+ if !field.introspection?
55
+ # The problem is that some introspection fields may have references
56
+ # to non-custom introspection types.
57
+ # If those were added here, they'd cause a DuplicateNamesError.
58
+ # This is basically a bug -- those fields _should_ reference the custom types.
59
+ add_type(ret_type, field)
60
+ end
61
+ true
62
+ else
63
+ false
64
+ end
65
+ end.compare_by_identity
66
+ }.compare_by_identity
67
+
68
+ @cached_visible_arguments = Hash.new do |h, arg|
69
+ h[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type]
70
+ add_type(arg_type, arg)
71
+ true
72
+ else
73
+ false
74
+ end
75
+ end.compare_by_identity
76
+
77
+ @cached_parent_fields = Hash.new do |h, type|
78
+ h[type] = Hash.new do |h2, field_name|
79
+ h2[field_name] = type.get_field(field_name, @context)
80
+ end
81
+ end.compare_by_identity
82
+
83
+ @cached_parent_arguments = Hash.new do |h, arg_owner|
84
+ h[arg_owner] = Hash.new do |h2, arg_name|
85
+ h2[arg_name] = arg_owner.get_argument(arg_name, @context)
86
+ end
87
+ end.compare_by_identity
88
+
89
+ @cached_possible_types = Hash.new do |h, type|
90
+ h[type] = case type.kind.name
91
+ when "INTERFACE"
92
+ load_all_types
93
+ pts = []
94
+ @unfiltered_interface_type_memberships[type].each { |itm|
95
+ if @cached_visible[itm] && (ot = itm.object_type) && @cached_visible[ot] && referenced?(ot)
96
+ pts << ot
97
+ end
98
+ }
99
+ pts
100
+ when "UNION"
101
+ pts = []
102
+ type.type_memberships.each { |tm|
103
+ if @cached_visible[tm] &&
104
+ (ot = tm.object_type) &&
105
+ @cached_visible[ot] &&
106
+ referenced?(ot)
107
+ pts << ot
108
+ end
109
+ }
110
+ pts
111
+ when "OBJECT"
112
+ load_all_types
113
+ if @all_types[type.graphql_name] == type
114
+ [type]
115
+ else
116
+ EmptyObjects::EMPTY_ARRAY
117
+ end
118
+ else
119
+ GraphQL::EmptyObjects::EMPTY_ARRAY
120
+ end
121
+ end.compare_by_identity
122
+
123
+ @cached_enum_values = Hash.new do |h, enum_t|
124
+ values = non_duplicate_items(enum_t.all_enum_value_definitions, @cached_visible)
125
+ if values.size == 0
126
+ raise GraphQL::Schema::Enum::MissingValuesError.new(enum_t)
127
+ end
128
+ h[enum_t] = values
129
+ end.compare_by_identity
130
+
131
+ @cached_fields = Hash.new do |h, owner|
132
+ h[owner] = non_duplicate_items(owner.all_field_definitions.each(&:ensure_loaded), @cached_visible_fields[owner])
133
+ end.compare_by_identity
134
+
135
+ @cached_arguments = Hash.new do |h, owner|
136
+ h[owner] = non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments)
137
+ end.compare_by_identity
138
+ end
139
+
140
+ def field_on_visible_interface?(field, owner)
141
+ ints = owner.interface_type_memberships.map(&:abstract_type)
142
+ field_name = field.graphql_name
143
+ filtered_ints = interfaces(owner)
144
+ any_interface_has_field = false
145
+ any_interface_has_visible_field = false
146
+ ints.each do |int_t|
147
+ if (_int_f_defn = @cached_parent_fields[int_t][field_name])
148
+ any_interface_has_field = true
149
+
150
+ if filtered_ints.include?(int_t) # TODO cycles, or maybe not necessary since previously checked? && @cached_visible_fields[owner][field]
151
+ any_interface_has_visible_field = true
152
+ break
153
+ end
154
+ end
155
+ end
156
+
157
+ if any_interface_has_field
158
+ any_interface_has_visible_field
159
+ else
160
+ true
161
+ end
162
+ end
163
+
164
+ def type(type_name)
165
+ t = if (loaded_t = @all_types[type_name])
166
+ loaded_t
167
+ elsif !@all_types_loaded
168
+ load_all_types
169
+ @all_types[type_name]
170
+ end
171
+ if t
172
+ if t.is_a?(Array)
173
+ vis_t = nil
174
+ t.each do |t_defn|
175
+ if @cached_visible[t_defn]
176
+ if vis_t.nil?
177
+ vis_t = t_defn
178
+ else
179
+ raise_duplicate_definition(vis_t, t_defn)
180
+ end
181
+ end
182
+ end
183
+ vis_t
184
+ else
185
+ if t && @cached_visible[t]
186
+ t
187
+ else
188
+ nil
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ def field(owner, field_name)
195
+ f = if owner.kind.fields? && (field = @cached_parent_fields[owner][field_name])
196
+ field
197
+ elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
198
+ entry_point_field
199
+ elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
200
+ dynamic_field
201
+ else
202
+ nil
203
+ end
204
+ if f.is_a?(Array)
205
+ visible_f = nil
206
+ f.each do |f_defn|
207
+ if @cached_visible_fields[owner][f_defn]
208
+
209
+ if visible_f.nil?
210
+ visible_f = f_defn
211
+ else
212
+ raise_duplicate_definition(visible_f, f_defn)
213
+ end
214
+ end
215
+ end
216
+ visible_f.ensure_loaded
217
+ elsif f && @cached_visible_fields[owner][f.ensure_loaded]
218
+ f
219
+ else
220
+ nil
221
+ end
222
+ end
223
+
224
+ def fields(owner)
225
+ @cached_fields[owner]
226
+ end
227
+
228
+ def arguments(owner)
229
+ @cached_arguments[owner]
230
+ end
231
+
232
+ def argument(owner, arg_name)
233
+ arg = @cached_parent_arguments[owner][arg_name]
234
+ if arg.is_a?(Array)
235
+ visible_arg = nil
236
+ arg.each do |arg_defn|
237
+ if @cached_visible_arguments[arg_defn]
238
+ if visible_arg.nil?
239
+ visible_arg = arg_defn
240
+ else
241
+ raise_duplicate_definition(visible_arg, arg_defn)
242
+ end
243
+ end
244
+ end
245
+ visible_arg
246
+ else
247
+ if arg && @cached_visible_arguments[arg]
248
+ arg
249
+ else
250
+ nil
251
+ end
252
+ end
253
+ end
254
+
255
+ def possible_types(type)
256
+ @cached_possible_types[type]
257
+ end
258
+
259
+ def interfaces(obj_or_int_type)
260
+ ints = obj_or_int_type.interface_type_memberships
261
+ .select { |itm| @cached_visible[itm] && @cached_visible[itm.abstract_type] }
262
+ .map!(&:abstract_type)
263
+ ints.uniq! # Remove any duplicate interfaces implemented via other interfaces
264
+ ints
265
+ end
266
+
267
+ def query_root
268
+ add_if_visible(@schema.query)
269
+ end
270
+
271
+ def mutation_root
272
+ add_if_visible(@schema.mutation)
273
+ end
274
+
275
+ def subscription_root
276
+ add_if_visible(@schema.subscription)
277
+ end
278
+
279
+ def all_types
280
+ load_all_types
281
+ @all_types.values
282
+ end
283
+
284
+ def all_types_h
285
+ load_all_types
286
+ @all_types
287
+ end
288
+
289
+ def enum_values(owner)
290
+ @cached_enum_values[owner]
291
+ end
292
+
293
+ def directive_exists?(dir_name)
294
+ if (dir = @schema.directives[dir_name]) && @cached_visible[dir]
295
+ !!dir
296
+ else
297
+ load_all_types
298
+ !!@cached_directives[dir_name]
299
+ end
300
+ end
301
+
302
+ def directives
303
+ @all_directives ||= begin
304
+ load_all_types
305
+ dirs = []
306
+ @schema.directives.each do |name, dir_defn|
307
+ if !@cached_directives[name] && @cached_visible[dir_defn]
308
+ dirs << dir_defn
309
+ end
310
+ end
311
+ dirs.concat(@cached_directives.values)
312
+ end
313
+ end
314
+
315
+ def loadable?(t, _ctx)
316
+ !@all_types[t.graphql_name] && @cached_visible[t]
317
+ end
318
+
319
+ def loaded_types
320
+ @all_types.values
321
+ end
322
+
323
+ def reachable_type?(name)
324
+ load_all_types
325
+ !!@all_types[name]
326
+ end
327
+
328
+ private
329
+
330
+ def add_if_visible(t)
331
+ (t && @cached_visible[t]) ? (add_type(t, true); t) : nil
332
+ end
333
+
334
+ def add_type(t, by_member)
335
+ if t && @cached_visible[t]
336
+ n = t.graphql_name
337
+ if (prev_t = @all_types[n])
338
+ if !prev_t.equal?(t)
339
+ raise_duplicate_definition(prev_t, t)
340
+ end
341
+ false
342
+ else
343
+ @referenced_types[t] << by_member
344
+ @all_types[n] = t
345
+ @unvisited_types << t
346
+ true
347
+ end
348
+ else
349
+ false
350
+ end
351
+ end
352
+
353
+ def non_duplicate_items(definitions, visibility_cache)
354
+ non_dups = []
355
+ definitions.each do |defn|
356
+ if visibility_cache[defn]
357
+ if (dup_defn = non_dups.find { |d| d.graphql_name == defn.graphql_name })
358
+ raise_duplicate_definition(dup_defn, defn)
359
+ end
360
+ non_dups << defn
361
+ end
362
+ end
363
+ non_dups
364
+ end
365
+
366
+ def raise_duplicate_definition(first_defn, second_defn)
367
+ raise DuplicateNamesError.new(duplicated_name: first_defn.path, duplicated_definition_1: first_defn.inspect, duplicated_definition_2: second_defn.inspect)
368
+ end
369
+
370
+ def referenced?(t)
371
+ load_all_types
372
+ @referenced_types[t].any? { |reference| (reference == true) || @cached_visible[reference] }
373
+ end
374
+
375
+ def load_all_types
376
+ return if @all_types_loaded
377
+ @all_types_loaded = true
378
+ entry_point_types = [
379
+ query_root,
380
+ mutation_root,
381
+ subscription_root,
382
+ *@schema.introspection_system.types.values,
383
+ ]
384
+
385
+ # Don't include any orphan_types whose interfaces aren't visible.
386
+ @schema.orphan_types.each do |orphan_type|
387
+ if @cached_visible[orphan_type] &&
388
+ orphan_type.interface_type_memberships.any? { |tm| @cached_visible[tm] && @cached_visible[tm.abstract_type] }
389
+ entry_point_types << orphan_type
390
+ end
391
+ end
392
+
393
+ @schema.directives.each do |_dir_name, dir_class|
394
+ if @cached_visible[dir_class]
395
+ arguments(dir_class).each do |arg|
396
+ entry_point_types << arg.type.unwrap
397
+ end
398
+ end
399
+ end
400
+
401
+ entry_point_types.compact! # TODO why is this necessary?!
402
+ entry_point_types.flatten! # handle multiple defns
403
+ entry_point_types.each { |t| add_type(t, true) }
404
+
405
+ @unfiltered_interface_type_memberships = Hash.new { |h, k| h[k] = [] }.compare_by_identity
406
+ @add_possible_types = Set.new
407
+
408
+ while @unvisited_types.any?
409
+ while t = @unvisited_types.pop
410
+ # These have already been checked for `.visible?`
411
+ visit_type(t)
412
+ end
413
+ @add_possible_types.each do |int_t|
414
+ itms = @unfiltered_interface_type_memberships[int_t]
415
+ itms.each do |itm|
416
+ if @cached_visible[itm] && (obj_type = itm.object_type) && @cached_visible[obj_type]
417
+ add_type(obj_type, itm)
418
+ end
419
+ end
420
+ end
421
+ @add_possible_types.clear
422
+ end
423
+
424
+ @all_types.delete_if { |type_name, type_defn| !referenced?(type_defn) }
425
+ nil
426
+ end
427
+
428
+ def visit_type(type)
429
+ visit_directives(type)
430
+ case type.kind.name
431
+ when "OBJECT", "INTERFACE"
432
+ if type.kind.object?
433
+ type.interface_type_memberships.each do |itm|
434
+ @unfiltered_interface_type_memberships[itm.abstract_type] << itm
435
+ end
436
+ # recurse into visible implemented interfaces
437
+ interfaces(type).each do |interface|
438
+ add_type(interface, type)
439
+ end
440
+ else
441
+ type.orphan_types.each { |t| add_type(t, type)}
442
+ end
443
+
444
+ # recurse into visible fields
445
+ t_f = type.all_field_definitions
446
+ t_f.each do |field|
447
+ field.ensure_loaded
448
+ if @cached_visible[field]
449
+ visit_directives(field)
450
+ field_type = field.type.unwrap
451
+ if field_type.kind.interface?
452
+ @add_possible_types.add(field_type)
453
+ end
454
+ add_type(field_type, field)
455
+
456
+ # recurse into visible arguments
457
+ arguments(field).each do |argument|
458
+ visit_directives(argument)
459
+ add_type(argument.type.unwrap, argument)
460
+ end
461
+ end
462
+ end
463
+ when "INPUT_OBJECT"
464
+ # recurse into visible arguments
465
+ arguments(type).each do |argument|
466
+ visit_directives(argument)
467
+ add_type(argument.type.unwrap, argument)
468
+ end
469
+ when "UNION"
470
+ # recurse into visible possible types
471
+ type.type_memberships.each do |tm|
472
+ if @cached_visible[tm]
473
+ obj_t = tm.object_type
474
+ if obj_t.is_a?(String)
475
+ obj_t = Member::BuildType.constantize(obj_t)
476
+ tm.object_type = obj_t
477
+ end
478
+ if @cached_visible[obj_t]
479
+ add_type(obj_t, tm)
480
+ end
481
+ end
482
+ end
483
+ when "ENUM"
484
+ enum_values(type).each do |val|
485
+ visit_directives(val)
486
+ end
487
+ when "SCALAR"
488
+ # pass
489
+ end
490
+ end
491
+
492
+ def visit_directives(member)
493
+ member.directives.each { |dir|
494
+ dir_class = dir.class
495
+ if @cached_visible[dir_class]
496
+ dir_name = dir_class.graphql_name
497
+ if (existing_dir = @cached_directives[dir_name])
498
+ if existing_dir != dir_class
499
+ raise ArgumentError, "Two directives for `@#{dir_name}`: #{existing_dir}, #{dir.class}"
500
+ end
501
+ else
502
+ @cached_directives[dir.graphql_name] = dir_class
503
+ end
504
+ end
505
+ }
506
+ end
507
+ end
508
+ end
509
+ end
@@ -5,13 +5,13 @@ module GraphQL
5
5
  module TypeExpression
6
6
  # Fetch a type from a type map by its AST specification.
7
7
  # Return `nil` if not found.
8
- # @param type_owner [#get_type] A thing for looking up types by name
8
+ # @param type_owner [#type] A thing for looking up types by name
9
9
  # @param ast_node [GraphQL::Language::Nodes::AbstractNode]
10
10
  # @return [Class, GraphQL::Schema::NonNull, GraphQL::Schema:List]
11
11
  def self.build_type(type_owner, ast_node)
12
12
  case ast_node
13
13
  when GraphQL::Language::Nodes::TypeName
14
- type_owner.get_type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware
14
+ type_owner.type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware
15
15
  when GraphQL::Language::Nodes::NonNullType
16
16
  ast_inner_type = ast_node.of_type
17
17
  inner_type = build_type(type_owner, ast_inner_type)