graphql 2.3.7 → 2.4.5

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 (125) 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 +37 -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 +25 -20
  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 +187 -0
  80. data/lib/graphql/schema/visibility/profile.rb +353 -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 +166 -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.rb +6 -4
  114. data/lib/graphql/testing/helpers.rb +10 -6
  115. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  116. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  117. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  118. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  119. data/lib/graphql/types.rb +18 -11
  120. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  121. data/lib/graphql/version.rb +1 -1
  122. data/lib/graphql.rb +81 -45
  123. metadata +31 -8
  124. data/lib/graphql/language/token.rb +0 -34
  125. 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,32 @@ 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 visibility_profile
76
+ @visibility_profile ||= Warden::VisibilityProfile.new(self)
77
+ end
64
78
  end
65
79
  end
66
80
 
67
81
  class NullWarden
68
82
  def initialize(_filter = nil, context:, schema:)
69
83
  @schema = schema
84
+ @visibility_profile = Warden::VisibilityProfile.new(self)
70
85
  end
71
86
 
87
+ # No-op, but for compatibility:
88
+ attr_writer :skip_warning
89
+
90
+ attr_reader :visibility_profile
91
+
72
92
  def visible_field?(field_defn, _ctx = nil, owner = nil); true; end
73
93
  def visible_argument?(arg_defn, _ctx = nil); true; end
74
94
  def visible_type?(type_defn, _ctx = nil); true; end
75
- def visible_enum_value?(enum_value, _ctx = nil); true; end
95
+ def visible_enum_value?(enum_value, _ctx = nil); enum_value.visible?(Query::NullContext.instance); end
76
96
  def visible_type_membership?(type_membership, _ctx = nil); true; end
77
97
  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
98
+ def get_type(type_name); @schema.get_type(type_name, Query::NullContext.instance, false); end # rubocop:disable Development/ContextIsPassedCop
79
99
  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
100
+ def enum_values(enum_defn); enum_defn.enum_values(Query::NullContext.instance); end # rubocop:disable Development/ContextIsPassedCop
81
101
  def get_argument(parent_type, argument_name); parent_type.get_argument(argument_name); end # rubocop:disable Development/ContextIsPassedCop
82
102
  def types; @schema.types; end # rubocop:disable Development/ContextIsPassedCop
83
103
  def root_type_for_operation(op_name); @schema.root_type_for_operation(op_name); end
@@ -87,10 +107,88 @@ module GraphQL
87
107
  def reachable_type?(type_name); true; end
88
108
  def loadable?(type, _ctx); true; end
89
109
  def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop
90
- def possible_types(type_defn); @schema.possible_types(type_defn); end
110
+ def possible_types(type_defn); @schema.possible_types(type_defn, Query::NullContext.instance, false); end
91
111
  def interfaces(obj_type); obj_type.interfaces; end
92
112
  end
93
113
 
114
+ def visibility_profile
115
+ @visibility_profile ||= VisibilityProfile.new(self)
116
+ end
117
+
118
+ class VisibilityProfile
119
+ def initialize(warden)
120
+ @warden = warden
121
+ end
122
+
123
+ def directives
124
+ @warden.directives
125
+ end
126
+
127
+ def directive_exists?(dir_name)
128
+ @warden.directives.any? { |d| d.graphql_name == dir_name }
129
+ end
130
+
131
+ def type(name)
132
+ @warden.get_type(name)
133
+ end
134
+
135
+ def field(owner, field_name)
136
+ @warden.get_field(owner, field_name)
137
+ end
138
+
139
+ def argument(owner, arg_name)
140
+ @warden.get_argument(owner, arg_name)
141
+ end
142
+
143
+ def query_root
144
+ @warden.root_type_for_operation("query")
145
+ end
146
+
147
+ def mutation_root
148
+ @warden.root_type_for_operation("mutation")
149
+ end
150
+
151
+ def subscription_root
152
+ @warden.root_type_for_operation("subscription")
153
+ end
154
+
155
+ def arguments(owner)
156
+ @warden.arguments(owner)
157
+ end
158
+
159
+ def fields(owner)
160
+ @warden.fields(owner)
161
+ end
162
+
163
+ def possible_types(type)
164
+ @warden.possible_types(type)
165
+ end
166
+
167
+ def enum_values(enum_type)
168
+ @warden.enum_values(enum_type)
169
+ end
170
+
171
+ def all_types
172
+ @warden.reachable_types
173
+ end
174
+
175
+ def interfaces(obj_type)
176
+ @warden.interfaces(obj_type)
177
+ end
178
+
179
+ def loadable?(t, ctx) # TODO remove ctx here?
180
+ @warden.loadable?(t, ctx)
181
+ end
182
+
183
+ def reachable_type?(type_name)
184
+ !!@warden.reachable_type?(type_name)
185
+ end
186
+
187
+ def visible_enum_value?(enum_value, ctx = nil)
188
+ @warden.visible_enum_value?(enum_value, ctx)
189
+ end
190
+ end
191
+
94
192
  # @param context [GraphQL::Query::Context]
95
193
  # @param schema [GraphQL::Schema]
96
194
  def initialize(context:, schema:)
@@ -100,17 +198,19 @@ module GraphQL
100
198
  @mutation = @schema.mutation
101
199
  @subscription = @schema.subscription
102
200
  @context = context
103
- @visibility_cache = read_through { |m| schema.visible?(m, context) }
104
- @visibility_cache.compare_by_identity
201
+ @visibility_cache = read_through { |m| check_visible(schema, m) }
105
202
  # Initialize all ivars to improve object shape consistency:
106
203
  @types = @visible_types = @reachable_types = @visible_parent_fields =
107
204
  @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
108
205
  @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
109
206
  @visible_and_reachable_type = @unions = @unfiltered_interfaces =
110
- @reachable_type_set =
207
+ @reachable_type_set = @visibility_profile =
111
208
  nil
209
+ @skip_warning = schema.plugins.any? { |(plugin, _opts)| plugin == GraphQL::Schema::Warden }
112
210
  end
113
211
 
212
+ attr_writer :skip_warning
213
+
114
214
  # @return [Hash<String, GraphQL::BaseType>] Visible types in the schema
115
215
  def types
116
216
  @types ||= begin
@@ -132,7 +232,7 @@ module GraphQL
132
232
  # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
133
233
  def get_type(type_name)
134
234
  @visible_types ||= read_through do |name|
135
- type_defn = @schema.get_type(name, @context)
235
+ type_defn = @schema.get_type(name, @context, false)
136
236
  if type_defn && visible_and_reachable_type?(type_defn)
137
237
  type_defn
138
238
  else
@@ -179,7 +279,7 @@ module GraphQL
179
279
  # @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
180
280
  def possible_types(type_defn)
181
281
  @visible_possible_types ||= read_through { |type_defn|
182
- pt = @schema.possible_types(type_defn, @context)
282
+ pt = @schema.possible_types(type_defn, @context, false)
183
283
  pt.select { |t| visible_and_reachable_type?(t) }
184
284
  }
185
285
  @visible_possible_types[type_defn]
@@ -197,7 +297,7 @@ module GraphQL
197
297
  def arguments(argument_owner, ctx = nil)
198
298
  @visible_arguments ||= read_through { |o|
199
299
  args = o.arguments(@context)
200
- if args.any?
300
+ if !args.empty?
201
301
  args = args.values
202
302
  args.select! { |a| visible_argument?(a, @context) }
203
303
  args
@@ -229,7 +329,7 @@ module GraphQL
229
329
  def interfaces(obj_type)
230
330
  @visible_interfaces ||= read_through { |t|
231
331
  ints = t.interfaces(@context)
232
- if ints.any?
332
+ if !ints.empty?
233
333
  ints.select! { |i| visible_type?(i) }
234
334
  end
235
335
  ints
@@ -289,9 +389,9 @@ module GraphQL
289
389
  next true if root_type?(type_defn) || type_defn.introspection?
290
390
 
291
391
  if type_defn.kind.union?
292
- possible_types(type_defn).any? && (referenced?(type_defn) || orphan_type?(type_defn))
392
+ !possible_types(type_defn).empty? && (referenced?(type_defn) || orphan_type?(type_defn))
293
393
  elsif type_defn.kind.interface?
294
- if possible_types(type_defn).any?
394
+ if !possible_types(type_defn).empty?
295
395
  true
296
396
  else
297
397
  if @context.respond_to?(:logger) && (logger = @context.logger)
@@ -376,11 +476,61 @@ module GraphQL
376
476
  end
377
477
 
378
478
  def read_through
379
- h = Hash.new { |h, k| h[k] = yield(k) }
380
- h.compare_by_identity
381
- h
479
+ Hash.new { |h, k| h[k] = yield(k) }.compare_by_identity
480
+ end
481
+
482
+ def check_visible(schema, member)
483
+ if schema.visible?(member, @context)
484
+ true
485
+ elsif @skip_warning
486
+ false
487
+ else
488
+ member_s = member.respond_to?(:path) ? member.path : member.inspect
489
+ member_type = case member
490
+ when Module
491
+ if member.respond_to?(:kind)
492
+ member.kind.name.downcase
493
+ else
494
+ ""
495
+ end
496
+ when GraphQL::Schema::Field
497
+ "field"
498
+ when GraphQL::Schema::EnumValue
499
+ "enum value"
500
+ when GraphQL::Schema::Argument
501
+ "argument"
502
+ else
503
+ ""
504
+ end
505
+
506
+ schema_s = schema.name ? "#{schema.name}'s" : ""
507
+ schema_name = schema.name ? "#{schema.name}" : "your schema"
508
+ warn(ADD_WARDEN_WARNING % { schema_s: schema_s, schema_name: schema_name, member: member_s, member_type: member_type })
509
+ @skip_warning = true # only warn once per query
510
+ # If there's no schema name, add the backtrace for additional context:
511
+ if schema_s == ""
512
+ puts caller.map { |l| " #{l}"}
513
+ end
514
+ false
515
+ end
382
516
  end
383
517
 
518
+ ADD_WARDEN_WARNING = <<~WARNING
519
+ DEPRECATION: %{schema_s} "%{member}" %{member_type} returned `false` for `.visible?` but `GraphQL::Schema::Visibility` isn't configured yet.
520
+
521
+ Address this warning by adding:
522
+
523
+ use GraphQL::Schema::Visibility
524
+
525
+ to the definition for %{schema_name}. (Future GraphQL-Ruby versions won't check `.visible?` methods by default.)
526
+
527
+ Alternatively, for legacy behavior, add:
528
+
529
+ use GraphQL::Schema::Warden # legacy visibility behavior
530
+
531
+ For more information see: https://graphql-ruby.org/authorization/visibility.html
532
+ WARNING
533
+
384
534
  def reachable_type_set
385
535
  return @reachable_type_set if @reachable_type_set
386
536