graphql 2.3.7 → 2.4.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install_generator.rb +46 -0
- data/lib/generators/graphql/orm_mutations_base.rb +1 -1
- data/lib/generators/graphql/templates/base_resolver.erb +2 -0
- data/lib/generators/graphql/type_generator.rb +1 -1
- data/lib/graphql/analysis/field_usage.rb +1 -1
- data/lib/graphql/analysis/query_complexity.rb +3 -3
- data/lib/graphql/analysis/visitor.rb +8 -7
- data/lib/graphql/analysis.rb +4 -4
- data/lib/graphql/autoload.rb +37 -0
- data/lib/graphql/current.rb +52 -0
- data/lib/graphql/dataloader/async_dataloader.rb +7 -6
- data/lib/graphql/dataloader/source.rb +7 -4
- data/lib/graphql/dataloader.rb +40 -19
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
- data/lib/graphql/execution/interpreter/resolve.rb +13 -9
- data/lib/graphql/execution/interpreter/runtime.rb +35 -31
- data/lib/graphql/execution/interpreter.rb +6 -4
- data/lib/graphql/execution/lookahead.rb +18 -11
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +1 -1
- data/lib/graphql/introspection/schema_type.rb +6 -11
- data/lib/graphql/introspection/type_type.rb +5 -5
- data/lib/graphql/invalid_null_error.rb +1 -1
- data/lib/graphql/language/cache.rb +13 -0
- data/lib/graphql/language/comment.rb +18 -0
- data/lib/graphql/language/document_from_schema_definition.rb +62 -34
- data/lib/graphql/language/lexer.rb +18 -15
- data/lib/graphql/language/nodes.rb +24 -16
- data/lib/graphql/language/parser.rb +14 -1
- data/lib/graphql/language/printer.rb +31 -15
- data/lib/graphql/language/sanitized_printer.rb +1 -1
- data/lib/graphql/language.rb +6 -6
- data/lib/graphql/pagination/connection.rb +1 -1
- data/lib/graphql/query/context/scoped_context.rb +1 -1
- data/lib/graphql/query/context.rb +13 -6
- data/lib/graphql/query/null_context.rb +3 -5
- data/lib/graphql/query/variable_validation_error.rb +1 -1
- data/lib/graphql/query.rb +72 -18
- data/lib/graphql/railtie.rb +7 -0
- data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
- data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
- data/lib/graphql/rubocop.rb +2 -0
- data/lib/graphql/schema/addition.rb +2 -1
- data/lib/graphql/schema/always_visible.rb +6 -2
- data/lib/graphql/schema/argument.rb +14 -1
- data/lib/graphql/schema/build_from_definition.rb +9 -1
- data/lib/graphql/schema/directive/flagged.rb +2 -2
- data/lib/graphql/schema/directive.rb +1 -1
- data/lib/graphql/schema/enum.rb +71 -23
- data/lib/graphql/schema/enum_value.rb +10 -2
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field/scope_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +102 -47
- data/lib/graphql/schema/field_extension.rb +1 -1
- data/lib/graphql/schema/has_single_input_argument.rb +5 -2
- data/lib/graphql/schema/input_object.rb +90 -39
- data/lib/graphql/schema/interface.rb +22 -5
- data/lib/graphql/schema/introspection_system.rb +5 -16
- data/lib/graphql/schema/loader.rb +1 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
- data/lib/graphql/schema/member/has_arguments.rb +25 -20
- data/lib/graphql/schema/member/has_directives.rb +3 -3
- data/lib/graphql/schema/member/has_fields.rb +26 -6
- data/lib/graphql/schema/member/has_interfaces.rb +4 -4
- data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
- data/lib/graphql/schema/member/has_validators.rb +1 -1
- data/lib/graphql/schema/object.rb +8 -0
- data/lib/graphql/schema/printer.rb +1 -0
- data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
- data/lib/graphql/schema/resolver.rb +12 -14
- data/lib/graphql/schema/subscription.rb +2 -2
- data/lib/graphql/schema/type_expression.rb +2 -2
- data/lib/graphql/schema/union.rb +1 -1
- data/lib/graphql/schema/validator/all_validator.rb +62 -0
- data/lib/graphql/schema/validator/required_validator.rb +28 -4
- data/lib/graphql/schema/validator.rb +3 -1
- data/lib/graphql/schema/visibility/migration.rb +187 -0
- data/lib/graphql/schema/visibility/profile.rb +353 -0
- data/lib/graphql/schema/visibility/visit.rb +190 -0
- data/lib/graphql/schema/visibility.rb +294 -0
- data/lib/graphql/schema/warden.rb +166 -16
- data/lib/graphql/schema.rb +348 -94
- data/lib/graphql/static_validation/base_visitor.rb +6 -5
- data/lib/graphql/static_validation/literal_validator.rb +4 -4
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +2 -2
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +8 -7
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
- data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
- data/lib/graphql/static_validation/validation_context.rb +18 -2
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
- data/lib/graphql/subscriptions/event.rb +1 -1
- data/lib/graphql/subscriptions.rb +6 -4
- data/lib/graphql/testing/helpers.rb +10 -6
- data/lib/graphql/tracing/notifications_trace.rb +2 -2
- data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
- data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
- data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
- data/lib/graphql/types.rb +18 -11
- data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +81 -45
- metadata +31 -8
- data/lib/graphql/language/token.rb +0 -34
- 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);
|
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
|
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.
|
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.
|
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).
|
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).
|
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
|
-
|
380
|
-
|
381
|
-
|
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
|
|