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
@@ -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)