graphql 2.3.6 → 2.3.8

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/field_usage.rb +1 -1
  3. data/lib/graphql/analysis/query_complexity.rb +3 -3
  4. data/lib/graphql/analysis/visitor.rb +7 -6
  5. data/lib/graphql/execution/interpreter/runtime.rb +6 -6
  6. data/lib/graphql/execution/lookahead.rb +10 -10
  7. data/lib/graphql/introspection/directive_type.rb +1 -1
  8. data/lib/graphql/introspection/entry_points.rb +2 -2
  9. data/lib/graphql/introspection/field_type.rb +1 -1
  10. data/lib/graphql/introspection/schema_type.rb +13 -3
  11. data/lib/graphql/introspection/type_type.rb +5 -5
  12. data/lib/graphql/language/document_from_schema_definition.rb +19 -26
  13. data/lib/graphql/language/lexer.rb +0 -3
  14. data/lib/graphql/language/sanitized_printer.rb +1 -1
  15. data/lib/graphql/language.rb +0 -1
  16. data/lib/graphql/query/context.rb +4 -0
  17. data/lib/graphql/query/null_context.rb +4 -0
  18. data/lib/graphql/query.rb +26 -3
  19. data/lib/graphql/schema/always_visible.rb +1 -0
  20. data/lib/graphql/schema/enum.rb +4 -4
  21. data/lib/graphql/schema/field.rb +7 -3
  22. data/lib/graphql/schema/has_single_input_argument.rb +2 -1
  23. data/lib/graphql/schema/input_object.rb +8 -7
  24. data/lib/graphql/schema/introspection_system.rb +2 -14
  25. data/lib/graphql/schema/member/has_arguments.rb +7 -6
  26. data/lib/graphql/schema/member/has_fields.rb +6 -4
  27. data/lib/graphql/schema/resolver.rb +4 -5
  28. data/lib/graphql/schema/subset.rb +397 -0
  29. data/lib/graphql/schema/type_expression.rb +2 -2
  30. data/lib/graphql/schema/validator/all_validator.rb +60 -0
  31. data/lib/graphql/schema/validator.rb +2 -0
  32. data/lib/graphql/schema/warden.rb +88 -1
  33. data/lib/graphql/schema.rb +44 -15
  34. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  35. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  36. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  37. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  38. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -2
  39. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  40. data/lib/graphql/static_validation/rules/fields_will_merge.rb +7 -7
  41. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  42. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  43. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  44. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  45. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  46. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -3
  47. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  48. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  49. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  50. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  51. data/lib/graphql/static_validation/validation_context.rb +2 -2
  52. data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
  53. data/lib/graphql/subscriptions/event.rb +1 -1
  54. data/lib/graphql/subscriptions.rb +2 -2
  55. data/lib/graphql/testing/helpers.rb +2 -2
  56. data/lib/graphql/types/relay/connection_behaviors.rb +10 -0
  57. data/lib/graphql/types/relay/edge_behaviors.rb +10 -0
  58. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  59. data/lib/graphql/version.rb +1 -1
  60. metadata +4 -4
  61. data/lib/graphql/language/token.rb +0 -34
  62. data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -23,7 +23,8 @@ module GraphQL
23
23
  @ruby_style_hash = ruby_kwargs
24
24
  @arguments = arguments
25
25
  # Apply prepares, not great to have it duplicated here.
26
- self.class.arguments(context).each_value do |arg_defn|
26
+ arg_defns = context ? context.types.arguments(self.class) : self.class.arguments(context).each_value
27
+ arg_defns.each do |arg_defn|
27
28
  ruby_kwargs_key = arg_defn.keyword
28
29
  if @ruby_style_hash.key?(ruby_kwargs_key)
29
30
  # Weirdly, procs are applied during coercion, but not methods.
@@ -58,7 +59,7 @@ module GraphQL
58
59
  def self.authorized?(obj, value, ctx)
59
60
  # Authorize each argument (but this doesn't apply if `prepare` is implemented):
60
61
  if value.respond_to?(:key?)
61
- arguments(ctx).each do |_name, input_obj_arg|
62
+ ctx.types.arguments(self).each do |input_obj_arg|
62
63
  if value.key?(input_obj_arg.keyword) &&
63
64
  !input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
64
65
  return false
@@ -149,7 +150,7 @@ module GraphQL
149
150
  INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object."
150
151
 
151
152
  def validate_non_null_input(input, ctx, max_errors: nil)
152
- warden = ctx.warden
153
+ types = ctx.types
153
154
 
154
155
  if input.is_a?(Array)
155
156
  return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
@@ -161,9 +162,9 @@ module GraphQL
161
162
  end
162
163
 
163
164
  # Inject missing required arguments
164
- missing_required_inputs = self.arguments(ctx).reduce({}) do |m, (argument_name, argument)|
165
- if !input.key?(argument_name) && argument.type.non_null? && warden.get_argument(self, argument_name)
166
- m[argument_name] = nil
165
+ missing_required_inputs = ctx.types.arguments(self).reduce({}) do |m, (argument)|
166
+ if !input.key?(argument.graphql_name) && argument.type.non_null? && types.argument(self, argument.graphql_name)
167
+ m[argument.graphql_name] = nil
167
168
  end
168
169
 
169
170
  m
@@ -172,7 +173,7 @@ module GraphQL
172
173
  result = nil
173
174
  [input, missing_required_inputs].each do |args_to_validate|
174
175
  args_to_validate.each do |argument_name, value|
175
- argument = warden.get_argument(self, argument_name)
176
+ argument = types.argument(self, argument_name)
176
177
  # Items in the input that are unexpected
177
178
  if argument.nil?
178
179
  result ||= Query::InputValidationResult.new
@@ -69,7 +69,7 @@ module GraphQL
69
69
  def resolve_late_bindings
70
70
  @types.each do |name, t|
71
71
  if t.kind.fields?
72
- t.fields.each do |_name, field_defn|
72
+ t.all_field_definitions.each do |field_defn|
73
73
  field_defn.type = resolve_late_binding(field_defn.type)
74
74
  end
75
75
  end
@@ -113,19 +113,7 @@ module GraphQL
113
113
 
114
114
  def get_fields_from_class(class_sym:)
115
115
  object_type_defn = load_constant(class_sym)
116
-
117
- if object_type_defn.is_a?(Module)
118
- object_type_defn.fields
119
- else
120
- extracted_field_defns = {}
121
- object_class = object_type_defn.metadata[:type_class]
122
- object_type_defn.all_fields.each do |field_defn|
123
- inner_resolve = field_defn.resolve_proc
124
- resolve_with_instantiate = PerFieldProxyResolve.new(object_class: object_class, inner_resolve: inner_resolve)
125
- extracted_field_defns[field_defn.name] = field_defn.redefine(resolve: resolve_with_instantiate)
126
- end
127
- extracted_field_defns
128
- end
116
+ object_type_defn.fields
129
117
  end
130
118
 
131
119
  # This is probably not 100% robust -- but it has to be good enough to avoid modifying the built-in introspection types
@@ -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
@@ -205,8 +206,8 @@ module GraphQL
205
206
  # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
206
207
  def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
207
208
  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
209
+ 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)))
210
+ visible_arg || arg_config
210
211
  elsif defined?(@resolver_class) && @resolver_class
211
212
  @resolver_class.get_field_argument(argument_name, context)
212
213
  else
@@ -230,7 +231,7 @@ module GraphQL
230
231
  # @return [Interpreter::Arguments, Execution::Lazy<Interpreter::Arguments>]
231
232
  def coerce_arguments(parent_object, values, context, &block)
232
233
  # Cache this hash to avoid re-merging it
233
- arg_defns = context.warden.arguments(self)
234
+ arg_defns = context.types.arguments(self)
234
235
  total_args_count = arg_defns.size
235
236
 
236
237
  finished_args = nil
@@ -364,8 +365,8 @@ module GraphQL
364
365
  end
365
366
 
366
367
  if !(
367
- context.warden.possible_types(argument.loads).include?(application_object_type) ||
368
- context.warden.loadable?(argument.loads, context)
368
+ context.types.possible_types(argument.loads).include?(application_object_type) ||
369
+ context.types.loadable?(argument.loads, context)
369
370
  )
370
371
  err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object)
371
372
  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
@@ -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 f_entry
144
146
  end
145
147
  i += 1
146
148
  end
@@ -36,7 +36,7 @@ module GraphQL
36
36
  @field = field
37
37
  # Since this hash is constantly rebuilt, cache it for this call
38
38
  @arguments_by_keyword = {}
39
- self.class.arguments(context).each do |name, arg|
39
+ context.types.arguments(self.class).each do |arg|
40
40
  @arguments_by_keyword[arg.keyword] = arg
41
41
  end
42
42
  @prepared_arguments = nil
@@ -152,7 +152,7 @@ module GraphQL
152
152
  # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
153
153
  def authorized?(**inputs)
154
154
  arg_owner = @field # || self.class
155
- args = arg_owner.arguments(context)
155
+ args = context.types.arguments(arg_owner)
156
156
  authorize_arguments(args, inputs)
157
157
  end
158
158
 
@@ -169,7 +169,7 @@ module GraphQL
169
169
  private
170
170
 
171
171
  def authorize_arguments(args, inputs)
172
- args.each_value do |argument|
172
+ args.each do |argument|
173
173
  arg_keyword = argument.keyword
174
174
  if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
175
175
  auth_result = argument.authorized?(self, arg_value, context)
@@ -182,10 +182,9 @@ module GraphQL
182
182
  elsif auth_result == false
183
183
  return auth_result
184
184
  end
185
- else
186
- true
187
185
  end
188
186
  end
187
+ true
189
188
  end
190
189
 
191
190
  def load_arguments(args)
@@ -0,0 +1,397 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Subset
6
+ def initialize(query)
7
+ @query = query
8
+ @context = query.context
9
+ @schema = query.schema
10
+ @all_types = {}
11
+ @all_types_loaded = false
12
+ @unvisited_types = []
13
+ @referenced_types = Hash.new { |h, type_defn| h[type_defn] = [] }.compare_by_identity
14
+ @cached_possible_types = nil
15
+ @cached_visible = Hash.new { |h, member|
16
+ h[member] = @schema.visible?(member, @context)
17
+ }.compare_by_identity
18
+
19
+ @cached_visible_fields = Hash.new { |h, owner|
20
+ h[owner] = Hash.new do |h2, field|
21
+ h2[field] = if @cached_visible[field] &&
22
+ (ret_type = field.type.unwrap) &&
23
+ @cached_visible[ret_type] &&
24
+ reachable_type?(ret_type.graphql_name) &&
25
+ (owner == field.owner || (!owner.kind.object?) || field_on_visible_interface?(field, owner))
26
+
27
+ if !field.introspection?
28
+ # The problem is that some introspection fields may have references
29
+ # to non-custom introspection types.
30
+ # If those were added here, they'd cause a DuplicateNamesError.
31
+ # This is basically a bug -- those fields _should_ reference the custom types.
32
+ add_type(ret_type, field)
33
+ end
34
+ true
35
+ else
36
+ false
37
+ end
38
+ end.compare_by_identity
39
+ }.compare_by_identity
40
+
41
+ @cached_visible_arguments = Hash.new do |h, arg|
42
+ h[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type]
43
+ add_type(arg_type, arg)
44
+ true
45
+ else
46
+ false
47
+ end
48
+ end.compare_by_identity
49
+
50
+ @unfiltered_pt = Hash.new do |hash, type|
51
+ hash[type] = @schema.possible_types(type)
52
+ end.compare_by_identity
53
+ end
54
+
55
+ def field_on_visible_interface?(field, owner)
56
+ ints = owner.interface_type_memberships.map(&:abstract_type)
57
+ field_name = field.graphql_name
58
+ filtered_ints = interfaces(owner)
59
+ any_interface_has_field = false
60
+ any_interface_has_visible_field = false
61
+ ints.each do |int_t|
62
+ if (_int_f_defn = int_t.get_field(field_name, @context))
63
+ any_interface_has_field = true
64
+
65
+ if filtered_ints.include?(int_t) # TODO cycles, or maybe not necessary since previously checked? && @cached_visible_fields[owner][field]
66
+ any_interface_has_visible_field = true
67
+ break
68
+ end
69
+ end
70
+ end
71
+
72
+ if any_interface_has_field
73
+ any_interface_has_visible_field
74
+ else
75
+ true
76
+ end
77
+ end
78
+
79
+ def type(type_name)
80
+ t = if (loaded_t = @all_types[type_name])
81
+ loaded_t
82
+ elsif !@all_types_loaded
83
+ load_all_types
84
+ @all_types[type_name]
85
+ end
86
+
87
+ if t
88
+ if t.is_a?(Array)
89
+ vis_t = nil
90
+ t.each do |t_defn|
91
+ if @cached_visible[t_defn]
92
+ if vis_t.nil?
93
+ vis_t = t_defn
94
+ else
95
+ raise_duplicate_definition(vis_t, t_defn)
96
+ end
97
+ end
98
+ end
99
+ vis_t
100
+ else
101
+ if t && @cached_visible[t]
102
+ t
103
+ else
104
+ nil
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ def field(owner, field_name)
111
+ f = if owner.kind.fields? && (field = owner.get_field(field_name, @context))
112
+ field
113
+ elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
114
+ entry_point_field
115
+ elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
116
+ dynamic_field
117
+ else
118
+ nil
119
+ end
120
+ if f.is_a?(Array)
121
+ visible_f = nil
122
+ f.each do |f_defn|
123
+ if @cached_visible_fields[owner][f_defn]
124
+
125
+ if visible_f.nil?
126
+ visible_f = f_defn
127
+ else
128
+ raise_duplicate_definition(visible_f, f_defn)
129
+ end
130
+ end
131
+ end
132
+ visible_f
133
+ else
134
+ if f && @cached_visible_fields[owner][f]
135
+ f
136
+ else
137
+ nil
138
+ end
139
+ end
140
+ end
141
+
142
+ def fields(owner)
143
+ non_duplicate_items(owner.all_field_definitions, @cached_visible_fields[owner])
144
+ end
145
+
146
+ def arguments(owner)
147
+ non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments)
148
+ end
149
+
150
+ def argument(owner, arg_name)
151
+ # TODO this makes a Warden.visible_entry call down the stack
152
+ # I need a non-Warden implementation
153
+ arg = owner.get_argument(arg_name, @context)
154
+ if arg.is_a?(Array)
155
+ visible_arg = nil
156
+ arg.each do |arg_defn|
157
+ if @cached_visible_arguments[arg_defn]
158
+ if arg_defn&.loads
159
+ add_type(arg_defn.loads, arg_defn)
160
+ end
161
+ if visible_arg.nil?
162
+ visible_arg = arg_defn
163
+ else
164
+ raise_duplicate_definition(visible_arg, arg_defn)
165
+ end
166
+ end
167
+ end
168
+ visible_arg
169
+ else
170
+ if arg && @cached_visible_arguments[arg]
171
+ if arg&.loads
172
+ add_type(arg.loads, arg)
173
+ end
174
+ arg
175
+ else
176
+ nil
177
+ end
178
+ end
179
+ end
180
+
181
+ def possible_types(type)
182
+ @cached_possible_types ||= Hash.new do |h, type|
183
+ pt = case type.kind.name
184
+ when "INTERFACE"
185
+ # TODO this requires the global map
186
+ @unfiltered_pt[type]
187
+ when "UNION"
188
+ type.type_memberships.select { |tm| @cached_visible[tm] && @cached_visible[tm.object_type] }.map!(&:object_type)
189
+ else
190
+ [type]
191
+ end
192
+
193
+ # TODO use `select!` when possible, skip it for `[type]`
194
+ h[type] = pt.select { |t|
195
+ @cached_visible[t] && referenced?(t)
196
+ }
197
+ end.compare_by_identity
198
+ @cached_possible_types[type]
199
+ end
200
+
201
+ def interfaces(obj_or_int_type)
202
+ ints = obj_or_int_type.interface_type_memberships
203
+ .select { |itm| @cached_visible[itm] && @cached_visible[itm.abstract_type] }
204
+ .map!(&:abstract_type)
205
+ ints.uniq! # Remove any duplicate interfaces implemented via other interfaces
206
+ ints
207
+ end
208
+
209
+ def query_root
210
+ add_if_visible(@schema.query)
211
+ end
212
+
213
+ def mutation_root
214
+ add_if_visible(@schema.mutation)
215
+ end
216
+
217
+ def subscription_root
218
+ add_if_visible(@schema.subscription)
219
+ end
220
+
221
+ def all_types
222
+ @all_types_filtered ||= begin
223
+ load_all_types
224
+ at = []
225
+ @all_types.each do |_name, type_defn|
226
+ if possible_types(type_defn).any? || referenced?(type_defn)
227
+ at << type_defn
228
+ end
229
+ end
230
+ at
231
+ end
232
+ end
233
+
234
+ def enum_values(owner)
235
+ values = non_duplicate_items(owner.all_enum_value_definitions, @cached_visible)
236
+ if values.size == 0
237
+ raise GraphQL::Schema::Enum::MissingValuesError.new(owner)
238
+ end
239
+ values
240
+ end
241
+
242
+ def directive_exists?(dir_name)
243
+ dir = @schema.directives[dir_name]
244
+ dir && @cached_visible[dir]
245
+ end
246
+
247
+ def directives
248
+ @schema.directives.each_value.select { |d| @cached_visible[d] }
249
+ end
250
+
251
+ def loadable?(t, _ctx)
252
+ !@all_types[t.graphql_name] # TODO make sure t is not reachable but t is visible
253
+ end
254
+
255
+ # TODO rename this to indicate that it is called with a typename
256
+ def reachable_type?(type_name)
257
+ load_all_types
258
+ !!((t = @all_types[type_name]) && referenced?(t))
259
+ end
260
+
261
+ def loaded_types
262
+ @all_types.values
263
+ end
264
+
265
+ private
266
+
267
+ def add_if_visible(t)
268
+ (t && @cached_visible[t]) ? (add_type(t, true); t) : nil
269
+ end
270
+
271
+ def add_type(t, by_member)
272
+ if t && @cached_visible[t]
273
+ n = t.graphql_name
274
+ if (prev_t = @all_types[n])
275
+ if !prev_t.equal?(t)
276
+ raise_duplicate_definition(prev_t, t)
277
+ end
278
+ false
279
+ else
280
+ @referenced_types[t] << by_member
281
+ @all_types[n] = t
282
+ @unvisited_types << t
283
+ true
284
+ end
285
+ else
286
+ false
287
+ end
288
+ end
289
+
290
+ def non_duplicate_items(definitions, visibility_cache)
291
+ non_dups = []
292
+ definitions.each do |defn|
293
+ if visibility_cache[defn]
294
+ if (dup_defn = non_dups.find { |d| d.graphql_name == defn.graphql_name })
295
+ raise_duplicate_definition(dup_defn, defn)
296
+ end
297
+ non_dups << defn
298
+ end
299
+ end
300
+ non_dups
301
+ end
302
+
303
+ def raise_duplicate_definition(first_defn, second_defn)
304
+ raise DuplicateNamesError.new(duplicated_name: first_defn.path, duplicated_definition_1: first_defn.inspect, duplicated_definition_2: second_defn.inspect)
305
+ end
306
+
307
+ def referenced?(t)
308
+ load_all_types
309
+ res = if @referenced_types[t].any? { |member| (member == true) || @cached_visible[member] }
310
+ if t.kind.abstract?
311
+ possible_types(t).any?
312
+ else
313
+ true
314
+ end
315
+ end
316
+ res
317
+ end
318
+
319
+ def load_all_types
320
+ return if @all_types_loaded
321
+ @all_types_loaded = true
322
+ schema_types = [
323
+ query_root,
324
+ mutation_root,
325
+ subscription_root,
326
+ *@schema.introspection_system.types.values,
327
+ ]
328
+
329
+ # Don't include any orphan_types whose interfaces aren't visible.
330
+ @schema.orphan_types.each do |orphan_type|
331
+ if @cached_visible[orphan_type] &&
332
+ orphan_type.interface_type_memberships.any? { |tm| @cached_visible[tm] && @cached_visible[tm.abstract_type] }
333
+ schema_types << orphan_type
334
+ end
335
+ end
336
+ schema_types.compact! # TODO why is this necessary?!
337
+ schema_types.flatten! # handle multiple defns
338
+ schema_types.each { |t| add_type(t, true) }
339
+
340
+ while t = @unvisited_types.pop
341
+ # These have already been checked for `.visible?`
342
+ visit_type(t)
343
+ end
344
+
345
+ @all_types.delete_if { |type_name, type_defn| !referenced?(type_defn) }
346
+ nil
347
+ end
348
+
349
+ def visit_type(type)
350
+ if type.kind.input_object?
351
+ # recurse into visible arguments
352
+ arguments(type).each do |argument|
353
+ add_type(argument.type.unwrap, argument)
354
+ end
355
+ elsif type.kind.union?
356
+ # recurse into visible possible types
357
+ type.type_memberships.each do |tm|
358
+ if @cached_visible[tm] && @cached_visible[tm.object_type]
359
+ add_type(tm.object_type, tm)
360
+ end
361
+ end
362
+ elsif type.kind.fields?
363
+ if type.kind.object?
364
+ # recurse into visible implemented interfaces
365
+ interfaces(type).each do |interface|
366
+ add_type(interface, type)
367
+ end
368
+ end
369
+
370
+ # recurse into visible fields
371
+ t_f = type.all_field_definitions
372
+ t_f.each do |field|
373
+ if @cached_visible[field]
374
+ field_type = field.type.unwrap
375
+ if field_type.kind.interface?
376
+ pt = @unfiltered_pt[field_type]
377
+ pt.each do |obj_type|
378
+ if @cached_visible[obj_type] &&
379
+ (tm = obj_type.interface_type_memberships.find { |tm| tm.abstract_type == field_type }) &&
380
+ @cached_visible[tm]
381
+ add_type(obj_type, tm)
382
+ end
383
+ end
384
+ end
385
+ add_type(field_type, field)
386
+
387
+ # recurse into visible arguments
388
+ arguments(field).each do |argument|
389
+ add_type(argument.type.unwrap, argument)
390
+ end
391
+ end
392
+ end
393
+ end
394
+ end
395
+ end
396
+ end
397
+ 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)
@@ -0,0 +1,60 @@
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
+ all_errors = EMPTY_ARRAY
32
+
33
+ value.each do |subvalue|
34
+ @validators.each do |validator|
35
+ errors = validator.validate(object, context, subvalue)
36
+ if errors &&
37
+ (errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
38
+ (errors.is_a?(String))
39
+ if all_errors.frozen? # It's empty
40
+ all_errors = []
41
+ end
42
+ if errors.is_a?(String)
43
+ all_errors << errors
44
+ else
45
+ all_errors.concat(errors)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ unless all_errors.frozen?
52
+ all_errors.uniq!
53
+ end
54
+
55
+ all_errors
56
+ end
57
+ end
58
+ end
59
+ end
60
+ 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)