graphql 2.3.7 → 2.4.7

Sign up to get free protection for your applications and to get access to all the features.
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