graphql 2.3.7 → 2.4.7

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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +46 -0
  3. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  4. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  5. data/lib/generators/graphql/type_generator.rb +1 -1
  6. data/lib/graphql/analysis/field_usage.rb +1 -1
  7. data/lib/graphql/analysis/query_complexity.rb +3 -3
  8. data/lib/graphql/analysis/visitor.rb +8 -7
  9. data/lib/graphql/analysis.rb +4 -4
  10. data/lib/graphql/autoload.rb +38 -0
  11. data/lib/graphql/current.rb +52 -0
  12. data/lib/graphql/dataloader/async_dataloader.rb +7 -6
  13. data/lib/graphql/dataloader/source.rb +7 -4
  14. data/lib/graphql/dataloader.rb +40 -19
  15. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  16. data/lib/graphql/execution/interpreter/resolve.rb +13 -9
  17. data/lib/graphql/execution/interpreter/runtime.rb +35 -31
  18. data/lib/graphql/execution/interpreter.rb +6 -4
  19. data/lib/graphql/execution/lookahead.rb +18 -11
  20. data/lib/graphql/introspection/directive_type.rb +1 -1
  21. data/lib/graphql/introspection/entry_points.rb +2 -2
  22. data/lib/graphql/introspection/field_type.rb +1 -1
  23. data/lib/graphql/introspection/schema_type.rb +6 -11
  24. data/lib/graphql/introspection/type_type.rb +5 -5
  25. data/lib/graphql/invalid_null_error.rb +1 -1
  26. data/lib/graphql/language/cache.rb +13 -0
  27. data/lib/graphql/language/comment.rb +18 -0
  28. data/lib/graphql/language/document_from_schema_definition.rb +62 -34
  29. data/lib/graphql/language/lexer.rb +18 -15
  30. data/lib/graphql/language/nodes.rb +24 -16
  31. data/lib/graphql/language/parser.rb +14 -1
  32. data/lib/graphql/language/printer.rb +31 -15
  33. data/lib/graphql/language/sanitized_printer.rb +1 -1
  34. data/lib/graphql/language.rb +6 -6
  35. data/lib/graphql/pagination/connection.rb +1 -1
  36. data/lib/graphql/query/context/scoped_context.rb +1 -1
  37. data/lib/graphql/query/context.rb +13 -6
  38. data/lib/graphql/query/null_context.rb +3 -5
  39. data/lib/graphql/query/variable_validation_error.rb +1 -1
  40. data/lib/graphql/query.rb +72 -18
  41. data/lib/graphql/railtie.rb +7 -0
  42. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  43. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  44. data/lib/graphql/rubocop.rb +2 -0
  45. data/lib/graphql/schema/addition.rb +2 -1
  46. data/lib/graphql/schema/always_visible.rb +6 -2
  47. data/lib/graphql/schema/argument.rb +14 -1
  48. data/lib/graphql/schema/build_from_definition.rb +9 -1
  49. data/lib/graphql/schema/directive/flagged.rb +2 -2
  50. data/lib/graphql/schema/directive.rb +1 -1
  51. data/lib/graphql/schema/enum.rb +71 -23
  52. data/lib/graphql/schema/enum_value.rb +10 -2
  53. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  54. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  55. data/lib/graphql/schema/field.rb +102 -47
  56. data/lib/graphql/schema/field_extension.rb +1 -1
  57. data/lib/graphql/schema/has_single_input_argument.rb +5 -2
  58. data/lib/graphql/schema/input_object.rb +90 -39
  59. data/lib/graphql/schema/interface.rb +22 -5
  60. data/lib/graphql/schema/introspection_system.rb +5 -16
  61. data/lib/graphql/schema/loader.rb +1 -1
  62. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  63. data/lib/graphql/schema/member/has_arguments.rb +36 -23
  64. data/lib/graphql/schema/member/has_directives.rb +3 -3
  65. data/lib/graphql/schema/member/has_fields.rb +26 -6
  66. data/lib/graphql/schema/member/has_interfaces.rb +4 -4
  67. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  68. data/lib/graphql/schema/member/has_validators.rb +1 -1
  69. data/lib/graphql/schema/object.rb +8 -0
  70. data/lib/graphql/schema/printer.rb +1 -0
  71. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  72. data/lib/graphql/schema/resolver.rb +12 -14
  73. data/lib/graphql/schema/subscription.rb +2 -2
  74. data/lib/graphql/schema/type_expression.rb +2 -2
  75. data/lib/graphql/schema/union.rb +1 -1
  76. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  77. data/lib/graphql/schema/validator/required_validator.rb +28 -4
  78. data/lib/graphql/schema/validator.rb +3 -1
  79. data/lib/graphql/schema/visibility/migration.rb +188 -0
  80. data/lib/graphql/schema/visibility/profile.rb +359 -0
  81. data/lib/graphql/schema/visibility/visit.rb +190 -0
  82. data/lib/graphql/schema/visibility.rb +294 -0
  83. data/lib/graphql/schema/warden.rb +179 -16
  84. data/lib/graphql/schema.rb +348 -94
  85. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  86. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  87. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  88. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  89. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  90. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  91. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  92. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  93. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +2 -2
  94. data/lib/graphql/static_validation/rules/fields_will_merge.rb +8 -7
  95. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  96. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  97. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  98. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  99. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  100. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  101. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  102. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  103. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  104. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  105. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  106. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  107. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  108. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  109. data/lib/graphql/static_validation/validation_context.rb +18 -2
  110. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
  111. data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
  112. data/lib/graphql/subscriptions/event.rb +1 -1
  113. data/lib/graphql/subscriptions/serialize.rb +2 -0
  114. data/lib/graphql/subscriptions.rb +6 -4
  115. data/lib/graphql/testing/helpers.rb +10 -6
  116. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  117. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  118. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  119. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  120. data/lib/graphql/types.rb +18 -11
  121. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  122. data/lib/graphql/version.rb +1 -1
  123. data/lib/graphql.rb +53 -45
  124. metadata +31 -8
  125. data/lib/graphql/language/token.rb +0 -34
  126. data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -0,0 +1,294 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/schema/visibility/profile"
3
+ require "graphql/schema/visibility/migration"
4
+ require "graphql/schema/visibility/visit"
5
+
6
+ module GraphQL
7
+ class Schema
8
+ # Use this plugin to make some parts of your schema hidden from some viewers.
9
+ #
10
+ class Visibility
11
+ # @param schema [Class<GraphQL::Schema>]
12
+ # @param profiles [Hash<Symbol => Hash>] A hash of `name => context` pairs for preloading visibility profiles
13
+ # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?`)
14
+ # @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
15
+ def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails) ? Rails.env.production? : nil), migration_errors: false)
16
+ schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
17
+ if preload
18
+ schema.visibility.preload
19
+ end
20
+ end
21
+
22
+ def initialize(schema, dynamic:, preload:, profiles:, migration_errors:)
23
+ @schema = schema
24
+ schema.use_visibility_profile = true
25
+ schema.visibility_profile_class = if migration_errors
26
+ Visibility::Migration
27
+ else
28
+ Visibility::Profile
29
+ end
30
+ @preload = preload
31
+ @profiles = profiles
32
+ @cached_profiles = {}
33
+ @dynamic = dynamic
34
+ @migration_errors = migration_errors
35
+ # Top-level type caches:
36
+ @visit = nil
37
+ @interface_type_memberships = nil
38
+ @directives = nil
39
+ @types = nil
40
+ @all_references = nil
41
+ @loaded_all = false
42
+ end
43
+
44
+ def all_directives
45
+ load_all
46
+ @directives
47
+ end
48
+
49
+ def all_interface_type_memberships
50
+ load_all
51
+ @interface_type_memberships
52
+ end
53
+
54
+ def all_references
55
+ load_all
56
+ @all_references
57
+ end
58
+
59
+ def get_type(type_name)
60
+ load_all
61
+ @types[type_name]
62
+ end
63
+
64
+ def preload?
65
+ @preload
66
+ end
67
+
68
+ def preload
69
+ # Traverse the schema now (and in the *_configured hooks below)
70
+ # To make sure things are loaded during boot
71
+ @preloaded_types = Set.new
72
+ types_to_visit = [
73
+ @schema.query,
74
+ @schema.mutation,
75
+ @schema.subscription,
76
+ *@schema.introspection_system.types.values,
77
+ *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
78
+ *@schema.orphan_types,
79
+ ]
80
+ # Root types may have been nil:
81
+ types_to_visit.compact!
82
+ ensure_all_loaded(types_to_visit)
83
+ @profiles.each do |profile_name, example_ctx|
84
+ example_ctx[:visibility_profile] = profile_name
85
+ prof = profile_for(example_ctx, profile_name)
86
+ prof.all_types # force loading
87
+ end
88
+ end
89
+
90
+ # @api private
91
+ def query_configured(query_type)
92
+ if @preload
93
+ ensure_all_loaded([query_type])
94
+ end
95
+ end
96
+
97
+ # @api private
98
+ def mutation_configured(mutation_type)
99
+ if @preload
100
+ ensure_all_loaded([mutation_type])
101
+ end
102
+ end
103
+
104
+ # @api private
105
+ def subscription_configured(subscription_type)
106
+ if @preload
107
+ ensure_all_loaded([subscription_type])
108
+ end
109
+ end
110
+
111
+ # @api private
112
+ def orphan_types_configured(orphan_types)
113
+ if @preload
114
+ ensure_all_loaded(orphan_types)
115
+ end
116
+ end
117
+
118
+ # @api private
119
+ def introspection_system_configured(introspection_system)
120
+ if @preload
121
+ introspection_types = [
122
+ *@schema.introspection_system.types.values,
123
+ *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
124
+ ]
125
+ ensure_all_loaded(introspection_types)
126
+ end
127
+ end
128
+
129
+ # Make another Visibility for `schema` based on this one
130
+ # @return [Visibility]
131
+ # @api private
132
+ def dup_for(other_schema)
133
+ self.class.new(
134
+ other_schema,
135
+ dynamic: @dynamic,
136
+ preload: @preload,
137
+ profiles: @profiles,
138
+ migration_errors: @migration_errors
139
+ )
140
+ end
141
+
142
+ def migration_errors?
143
+ @migration_errors
144
+ end
145
+
146
+ attr_reader :cached_profiles
147
+
148
+ def profile_for(context, visibility_profile)
149
+ if !@profiles.empty?
150
+ if visibility_profile.nil?
151
+ if @dynamic
152
+ if context.is_a?(Query::NullContext)
153
+ top_level_profile
154
+ else
155
+ @schema.visibility_profile_class.new(context: context, schema: @schema)
156
+ end
157
+ elsif !@profiles.empty?
158
+ raise ArgumentError, "#{@schema} expects a visibility profile, but `visibility_profile:` wasn't passed. Provide a `visibility_profile:` value or add `dynamic: true` to your visibility configuration."
159
+ end
160
+ elsif !@profiles.include?(visibility_profile)
161
+ raise ArgumentError, "`#{visibility_profile.inspect}` isn't allowed for `visibility_profile:` (must be one of #{@profiles.keys.map(&:inspect).join(", ")}). Or, add `#{visibility_profile.inspect}` to the list of profiles in the schema definition."
162
+ else
163
+ @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: context, schema: @schema)
164
+ end
165
+ elsif context.is_a?(Query::NullContext)
166
+ top_level_profile
167
+ else
168
+ @schema.visibility_profile_class.new(context: context, schema: @schema)
169
+ end
170
+ end
171
+
172
+ attr_reader :top_level
173
+
174
+ # @api private
175
+ attr_reader :unfiltered_interface_type_memberships
176
+
177
+ def top_level_profile(refresh: false)
178
+ if refresh
179
+ @top_level_profile = nil
180
+ end
181
+ @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema)
182
+ end
183
+
184
+ private
185
+
186
+ def ensure_all_loaded(types_to_visit)
187
+ while (type = types_to_visit.shift)
188
+ if type.kind.fields? && @preloaded_types.add?(type)
189
+ type.all_field_definitions.each do |field_defn|
190
+ field_defn.ensure_loaded
191
+ types_to_visit << field_defn.type.unwrap
192
+ end
193
+ end
194
+ end
195
+ top_level_profile(refresh: true)
196
+ nil
197
+ end
198
+
199
+ def load_all(types: nil)
200
+ if @visit.nil?
201
+ # Set up the visit system
202
+ @interface_type_memberships = Hash.new { |h, interface_type| h[interface_type] = [] }.compare_by_identity
203
+ @directives = []
204
+ @types = {} # String => Module
205
+ @all_references = Hash.new { |h, member| h[member] = Set.new.compare_by_identity }.compare_by_identity
206
+ @unions_for_references = Set.new
207
+ @visit = Visibility::Visit.new(@schema) do |member|
208
+ if member.is_a?(Module)
209
+ type_name = member.graphql_name
210
+ if (prev_t = @types[type_name])
211
+ if prev_t.is_a?(Array)
212
+ prev_t << member
213
+ else
214
+ @types[type_name] = [member, prev_t]
215
+ end
216
+ else
217
+ @types[member.graphql_name] = member
218
+ end
219
+ member.directives.each { |dir| @all_references[dir.class] << member }
220
+ if member < GraphQL::Schema::Directive
221
+ @directives << member
222
+ elsif member.respond_to?(:interface_type_memberships)
223
+ member.interface_type_memberships.each do |itm|
224
+ @all_references[itm.abstract_type] << member
225
+ @interface_type_memberships[itm.abstract_type] << itm
226
+ end
227
+ elsif member < GraphQL::Schema::Union
228
+ @unions_for_references << member
229
+ end
230
+ elsif member.is_a?(GraphQL::Schema::Argument)
231
+ member.validate_default_value
232
+ @all_references[member.type.unwrap] << member
233
+ if !(dirs = member.directives).empty?
234
+ dir_owner = member.owner
235
+ if dir_owner.respond_to?(:owner)
236
+ dir_owner = dir_owner.owner
237
+ end
238
+ dirs.each { |dir| @all_references[dir.class] << dir_owner }
239
+ end
240
+ elsif member.is_a?(GraphQL::Schema::Field)
241
+ @all_references[member.type.unwrap] << member
242
+ if !(dirs = member.directives).empty?
243
+ dir_owner = member.owner
244
+ dirs.each { |dir| @all_references[dir.class] << dir_owner }
245
+ end
246
+ elsif member.is_a?(GraphQL::Schema::EnumValue)
247
+ if !(dirs = member.directives).empty?
248
+ dir_owner = member.owner
249
+ dirs.each { |dir| @all_references[dir.class] << dir_owner }
250
+ end
251
+ end
252
+ true
253
+ end
254
+
255
+ @schema.root_types.each { |t| @all_references[t] << true }
256
+ @schema.introspection_system.types.each_value { |t| @all_references[t] << true }
257
+ @schema.directives.each_value { |dir_class| @all_references[dir_class] << true }
258
+
259
+ @visit.visit_each(types: []) # visit default directives
260
+ end
261
+
262
+ if types
263
+ @visit.visit_each(types: types, directives: [])
264
+ elsif @loaded_all == false
265
+ @loaded_all = true
266
+ @visit.visit_each
267
+ else
268
+ # already loaded all
269
+ return
270
+ end
271
+
272
+ # TODO: somehow don't iterate over all these,
273
+ # only the ones that may have been modified
274
+ @interface_type_memberships.each do |int_type, type_memberships|
275
+ referers = @all_references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) }
276
+ if !referers.empty?
277
+ type_memberships.each do |type_membership|
278
+ implementor_type = type_membership.object_type
279
+ # Add new items only:
280
+ @all_references[implementor_type] |= referers
281
+ end
282
+ end
283
+ end
284
+
285
+ @unions_for_references.each do |union_type|
286
+ refs = @all_references[union_type]
287
+ union_type.all_possible_types.each do |object_type|
288
+ @all_references[object_type] |= refs # Add new items
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end
294
+ end
@@ -19,6 +19,17 @@ module GraphQL
19
19
  PassThruWarden
20
20
  end
21
21
 
22
+ def self.types_from_context(context)
23
+ context.types || PassThruWarden
24
+ rescue NoMethodError
25
+ # this might be a hash which won't respond to #warden
26
+ PassThruWarden
27
+ end
28
+
29
+ def self.use(schema)
30
+ # no-op
31
+ end
32
+
22
33
  # @param visibility_method [Symbol] a Warden method to call for this entry
23
34
  # @param entry [Object, Array<Object>] One or more definitions for a given name in a GraphQL Schema
24
35
  # @param context [GraphQL::Query::Context]
@@ -61,23 +72,33 @@ module GraphQL
61
72
  def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
62
73
  def arguments(owner, ctx); owner.arguments(ctx); end
63
74
  def loadable?(type, ctx); type.visible?(ctx); end
75
+ def loadable_possible_types(type, ctx); type.possible_types(ctx); end
76
+ def visibility_profile
77
+ @visibility_profile ||= Warden::VisibilityProfile.new(self)
78
+ end
64
79
  end
65
80
  end
66
81
 
67
82
  class NullWarden
68
83
  def initialize(_filter = nil, context:, schema:)
69
84
  @schema = schema
85
+ @visibility_profile = Warden::VisibilityProfile.new(self)
70
86
  end
71
87
 
88
+ # No-op, but for compatibility:
89
+ attr_writer :skip_warning
90
+
91
+ attr_reader :visibility_profile
92
+
72
93
  def visible_field?(field_defn, _ctx = nil, owner = nil); true; end
73
94
  def visible_argument?(arg_defn, _ctx = nil); true; end
74
95
  def visible_type?(type_defn, _ctx = nil); true; end
75
- def visible_enum_value?(enum_value, _ctx = nil); true; end
96
+ def visible_enum_value?(enum_value, _ctx = nil); enum_value.visible?(Query::NullContext.instance); end
76
97
  def visible_type_membership?(type_membership, _ctx = nil); true; end
77
98
  def interface_type_memberships(obj_type, _ctx = nil); obj_type.interface_type_memberships; end
78
- def get_type(type_name); @schema.get_type(type_name); end # rubocop:disable Development/ContextIsPassedCop
99
+ def get_type(type_name); @schema.get_type(type_name, Query::NullContext.instance, false); end # rubocop:disable Development/ContextIsPassedCop
79
100
  def arguments(argument_owner, ctx = nil); argument_owner.all_argument_definitions; end
80
- def enum_values(enum_defn); enum_defn.enum_values; end # rubocop:disable Development/ContextIsPassedCop
101
+ def enum_values(enum_defn); enum_defn.enum_values(Query::NullContext.instance); end # rubocop:disable Development/ContextIsPassedCop
81
102
  def get_argument(parent_type, argument_name); parent_type.get_argument(argument_name); end # rubocop:disable Development/ContextIsPassedCop
82
103
  def types; @schema.types; end # rubocop:disable Development/ContextIsPassedCop
83
104
  def root_type_for_operation(op_name); @schema.root_type_for_operation(op_name); end
@@ -86,11 +107,94 @@ module GraphQL
86
107
  def get_field(parent_type, field_name); @schema.get_field(parent_type, field_name); end
87
108
  def reachable_type?(type_name); true; end
88
109
  def loadable?(type, _ctx); true; end
110
+ def loadable_possible_types(union_type, _ctx); union_type.possible_types; end
89
111
  def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop
90
- def possible_types(type_defn); @schema.possible_types(type_defn); end
112
+ def possible_types(type_defn); @schema.possible_types(type_defn, Query::NullContext.instance, false); end
91
113
  def interfaces(obj_type); obj_type.interfaces; end
92
114
  end
93
115
 
116
+ def visibility_profile
117
+ @visibility_profile ||= VisibilityProfile.new(self)
118
+ end
119
+
120
+ class VisibilityProfile
121
+ def initialize(warden)
122
+ @warden = warden
123
+ end
124
+
125
+ def directives
126
+ @warden.directives
127
+ end
128
+
129
+ def directive_exists?(dir_name)
130
+ @warden.directives.any? { |d| d.graphql_name == dir_name }
131
+ end
132
+
133
+ def type(name)
134
+ @warden.get_type(name)
135
+ end
136
+
137
+ def field(owner, field_name)
138
+ @warden.get_field(owner, field_name)
139
+ end
140
+
141
+ def argument(owner, arg_name)
142
+ @warden.get_argument(owner, arg_name)
143
+ end
144
+
145
+ def query_root
146
+ @warden.root_type_for_operation("query")
147
+ end
148
+
149
+ def mutation_root
150
+ @warden.root_type_for_operation("mutation")
151
+ end
152
+
153
+ def subscription_root
154
+ @warden.root_type_for_operation("subscription")
155
+ end
156
+
157
+ def arguments(owner)
158
+ @warden.arguments(owner)
159
+ end
160
+
161
+ def fields(owner)
162
+ @warden.fields(owner)
163
+ end
164
+
165
+ def possible_types(type)
166
+ @warden.possible_types(type)
167
+ end
168
+
169
+ def enum_values(enum_type)
170
+ @warden.enum_values(enum_type)
171
+ end
172
+
173
+ def all_types
174
+ @warden.reachable_types
175
+ end
176
+
177
+ def interfaces(obj_type)
178
+ @warden.interfaces(obj_type)
179
+ end
180
+
181
+ def loadable?(t, ctx) # TODO remove ctx here?
182
+ @warden.loadable?(t, ctx)
183
+ end
184
+
185
+ def loadable_possible_types(t, ctx)
186
+ @warden.loadable_possible_types(t, ctx)
187
+ end
188
+
189
+ def reachable_type?(type_name)
190
+ !!@warden.reachable_type?(type_name)
191
+ end
192
+
193
+ def visible_enum_value?(enum_value, ctx = nil)
194
+ @warden.visible_enum_value?(enum_value, ctx)
195
+ end
196
+ end
197
+
94
198
  # @param context [GraphQL::Query::Context]
95
199
  # @param schema [GraphQL::Schema]
96
200
  def initialize(context:, schema:)
@@ -100,17 +204,19 @@ module GraphQL
100
204
  @mutation = @schema.mutation
101
205
  @subscription = @schema.subscription
102
206
  @context = context
103
- @visibility_cache = read_through { |m| schema.visible?(m, context) }
104
- @visibility_cache.compare_by_identity
207
+ @visibility_cache = read_through { |m| check_visible(schema, m) }
105
208
  # Initialize all ivars to improve object shape consistency:
106
209
  @types = @visible_types = @reachable_types = @visible_parent_fields =
107
210
  @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
108
211
  @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
109
212
  @visible_and_reachable_type = @unions = @unfiltered_interfaces =
110
- @reachable_type_set =
213
+ @reachable_type_set = @visibility_profile = @loadable_possible_types =
111
214
  nil
215
+ @skip_warning = schema.plugins.any? { |(plugin, _opts)| plugin == GraphQL::Schema::Warden }
112
216
  end
113
217
 
218
+ attr_writer :skip_warning
219
+
114
220
  # @return [Hash<String, GraphQL::BaseType>] Visible types in the schema
115
221
  def types
116
222
  @types ||= begin
@@ -129,10 +235,17 @@ module GraphQL
129
235
  !reachable_type_set.include?(type) && visible_type?(type)
130
236
  end
131
237
 
238
+ def loadable_possible_types(union_type, _ctx)
239
+ @loadable_possible_types ||= read_through do |t|
240
+ t.possible_types # unfiltered
241
+ end
242
+ @loadable_possible_types[union_type]
243
+ end
244
+
132
245
  # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
133
246
  def get_type(type_name)
134
247
  @visible_types ||= read_through do |name|
135
- type_defn = @schema.get_type(name, @context)
248
+ type_defn = @schema.get_type(name, @context, false)
136
249
  if type_defn && visible_and_reachable_type?(type_defn)
137
250
  type_defn
138
251
  else
@@ -179,7 +292,7 @@ module GraphQL
179
292
  # @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
180
293
  def possible_types(type_defn)
181
294
  @visible_possible_types ||= read_through { |type_defn|
182
- pt = @schema.possible_types(type_defn, @context)
295
+ pt = @schema.possible_types(type_defn, @context, false)
183
296
  pt.select { |t| visible_and_reachable_type?(t) }
184
297
  }
185
298
  @visible_possible_types[type_defn]
@@ -197,7 +310,7 @@ module GraphQL
197
310
  def arguments(argument_owner, ctx = nil)
198
311
  @visible_arguments ||= read_through { |o|
199
312
  args = o.arguments(@context)
200
- if args.any?
313
+ if !args.empty?
201
314
  args = args.values
202
315
  args.select! { |a| visible_argument?(a, @context) }
203
316
  args
@@ -229,7 +342,7 @@ module GraphQL
229
342
  def interfaces(obj_type)
230
343
  @visible_interfaces ||= read_through { |t|
231
344
  ints = t.interfaces(@context)
232
- if ints.any?
345
+ if !ints.empty?
233
346
  ints.select! { |i| visible_type?(i) }
234
347
  end
235
348
  ints
@@ -289,9 +402,9 @@ module GraphQL
289
402
  next true if root_type?(type_defn) || type_defn.introspection?
290
403
 
291
404
  if type_defn.kind.union?
292
- possible_types(type_defn).any? && (referenced?(type_defn) || orphan_type?(type_defn))
405
+ !possible_types(type_defn).empty? && (referenced?(type_defn) || orphan_type?(type_defn))
293
406
  elsif type_defn.kind.interface?
294
- if possible_types(type_defn).any?
407
+ if !possible_types(type_defn).empty?
295
408
  true
296
409
  else
297
410
  if @context.respond_to?(:logger) && (logger = @context.logger)
@@ -376,11 +489,61 @@ module GraphQL
376
489
  end
377
490
 
378
491
  def read_through
379
- h = Hash.new { |h, k| h[k] = yield(k) }
380
- h.compare_by_identity
381
- h
492
+ Hash.new { |h, k| h[k] = yield(k) }.compare_by_identity
382
493
  end
383
494
 
495
+ def check_visible(schema, member)
496
+ if schema.visible?(member, @context)
497
+ true
498
+ elsif @skip_warning
499
+ false
500
+ else
501
+ member_s = member.respond_to?(:path) ? member.path : member.inspect
502
+ member_type = case member
503
+ when Module
504
+ if member.respond_to?(:kind)
505
+ member.kind.name.downcase
506
+ else
507
+ ""
508
+ end
509
+ when GraphQL::Schema::Field
510
+ "field"
511
+ when GraphQL::Schema::EnumValue
512
+ "enum value"
513
+ when GraphQL::Schema::Argument
514
+ "argument"
515
+ else
516
+ ""
517
+ end
518
+
519
+ schema_s = schema.name ? "#{schema.name}'s" : ""
520
+ schema_name = schema.name ? "#{schema.name}" : "your schema"
521
+ warn(ADD_WARDEN_WARNING % { schema_s: schema_s, schema_name: schema_name, member: member_s, member_type: member_type })
522
+ @skip_warning = true # only warn once per query
523
+ # If there's no schema name, add the backtrace for additional context:
524
+ if schema_s == ""
525
+ puts caller.map { |l| " #{l}"}
526
+ end
527
+ false
528
+ end
529
+ end
530
+
531
+ ADD_WARDEN_WARNING = <<~WARNING
532
+ DEPRECATION: %{schema_s} "%{member}" %{member_type} returned `false` for `.visible?` but `GraphQL::Schema::Visibility` isn't configured yet.
533
+
534
+ Address this warning by adding:
535
+
536
+ use GraphQL::Schema::Visibility
537
+
538
+ to the definition for %{schema_name}. (Future GraphQL-Ruby versions won't check `.visible?` methods by default.)
539
+
540
+ Alternatively, for legacy behavior, add:
541
+
542
+ use GraphQL::Schema::Warden # legacy visibility behavior
543
+
544
+ For more information see: https://graphql-ruby.org/authorization/visibility.html
545
+ WARNING
546
+
384
547
  def reachable_type_set
385
548
  return @reachable_type_set if @reachable_type_set
386
549