graphql 1.9.16 → 1.9.17
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.
- checksums.yaml +4 -4
- data/lib/graphql/execution/errors.rb +15 -14
- data/lib/graphql/execution/interpreter/runtime.rb +39 -17
- data/lib/graphql/introspection/entry_points.rb +2 -1
- data/lib/graphql/introspection/schema_type.rb +2 -1
- data/lib/graphql/query.rb +7 -0
- data/lib/graphql/query/context.rb +29 -4
- data/lib/graphql/query/null_context.rb +4 -0
- data/lib/graphql/query/variables.rb +3 -1
- data/lib/graphql/schema.rb +12 -2
- data/lib/graphql/schema/argument.rb +4 -0
- data/lib/graphql/schema/base_64_bp.rb +3 -2
- data/lib/graphql/schema/member/has_fields.rb +1 -4
- data/lib/graphql/schema/object.rb +6 -4
- data/lib/graphql/schema/possible_types.rb +3 -3
- data/lib/graphql/schema/type_membership.rb +34 -0
- data/lib/graphql/schema/union.rb +26 -6
- data/lib/graphql/schema/warden.rb +77 -3
- data/lib/graphql/union_type.rb +51 -23
- data/lib/graphql/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0486697e594e11affbd5f7d0a661023e2a649abfa654e8faa26b5849fce0b473'
|
4
|
+
data.tar.gz: 3943f0dcdeb83dc593ddf675cee9143180d61ea25f454ac1ba6e8f098f4bfaf3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5075b2a02ca5dc3d219736b64b768ef6bbb46f09cebb92aac5ab5d3249b4675b229cb058fe44a328543328497f91658078919e04019857802f42e219585d11fa
|
7
|
+
data.tar.gz: 8236278511482220a05461616be330b7c629241f2cb092b67392412d4710c6f5ab2c8e75d342c598bb64bec0fa02b003972d9b1af971562ccaf765daac444492
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module GraphQL
|
4
4
|
module Execution
|
5
|
-
# A
|
5
|
+
# A plugin that wraps query execution with error handling.
|
6
6
|
# Supports class-based schemas and the new {Interpreter} runtime only.
|
7
7
|
#
|
8
8
|
# @example Handling ActiveRecord::NotFound
|
@@ -19,34 +19,35 @@ module GraphQL
|
|
19
19
|
class Errors
|
20
20
|
def self.use(schema)
|
21
21
|
schema_class = schema.is_a?(Class) ? schema : schema.target.class
|
22
|
-
|
22
|
+
schema_class.error_handler = self.new(schema_class)
|
23
23
|
end
|
24
24
|
|
25
25
|
def initialize(schema)
|
26
26
|
@schema = schema
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
when "execute_field", "execute_field_lazy"
|
32
|
-
with_error_handling(data) { yield }
|
33
|
-
else
|
29
|
+
class NullErrorHandler
|
30
|
+
def self.with_error_handling(_ctx)
|
34
31
|
yield
|
35
32
|
end
|
36
33
|
end
|
37
34
|
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
# Call the given block with the schema's configured error handlers.
|
36
|
+
#
|
37
|
+
# If the block returns a lazy value, it's not wrapped with error handling. That area will have to be wrapped itself.
|
38
|
+
#
|
39
|
+
# @param ctx [GraphQL::Query::Context]
|
40
|
+
# @return [Object] Either the result of the given block, or some object to replace the result, in case of error handling.
|
41
|
+
def with_error_handling(ctx)
|
41
42
|
yield
|
42
43
|
rescue StandardError => err
|
43
44
|
rescues = @schema.rescues
|
44
45
|
_err_class, handler = rescues.find { |err_class, handler| err.is_a?(err_class) }
|
45
46
|
if handler
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
field =
|
47
|
+
runtime_info = ctx.namespace(:interpreter) || {}
|
48
|
+
obj = runtime_info[:current_object]
|
49
|
+
args = runtime_info[:current_arguments]
|
50
|
+
field = runtime_info[:current_field]
|
50
51
|
if obj.is_a?(GraphQL::Schema::Object)
|
51
52
|
obj = obj.object
|
52
53
|
end
|
@@ -11,7 +11,7 @@ module GraphQL
|
|
11
11
|
# @return [GraphQL::Query]
|
12
12
|
attr_reader :query
|
13
13
|
|
14
|
-
# @return [Class]
|
14
|
+
# @return [Class<GraphQL::Schema>]
|
15
15
|
attr_reader :schema
|
16
16
|
|
17
17
|
# @return [GraphQL::Query::Context]
|
@@ -43,19 +43,22 @@ module GraphQL
|
|
43
43
|
# might be stored up in lazies.
|
44
44
|
# @return [void]
|
45
45
|
def run_eager
|
46
|
+
|
46
47
|
root_operation = query.selected_operation
|
47
48
|
root_op_type = root_operation.operation_type || "query"
|
48
49
|
legacy_root_type = schema.root_type_for_operation(root_op_type)
|
49
50
|
root_type = legacy_root_type.metadata[:type_class] || raise("Invariant: type must be class-based: #{legacy_root_type}")
|
51
|
+
path = []
|
52
|
+
@interpreter_context[:current_object] = query.root_value
|
53
|
+
@interpreter_context[:current_path] = path
|
50
54
|
object_proxy = root_type.authorized_new(query.root_value, context)
|
51
55
|
object_proxy = schema.sync_lazy(object_proxy)
|
52
56
|
if object_proxy.nil?
|
53
57
|
# Root .authorized? returned false.
|
54
|
-
write_in_response(
|
58
|
+
write_in_response(path, nil)
|
55
59
|
nil
|
56
60
|
else
|
57
|
-
path
|
58
|
-
evaluate_selections(path, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
|
61
|
+
evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
|
59
62
|
nil
|
60
63
|
end
|
61
64
|
end
|
@@ -115,7 +118,9 @@ module GraphQL
|
|
115
118
|
end
|
116
119
|
end
|
117
120
|
|
118
|
-
def evaluate_selections(path, owner_object, owner_type, selections, root_operation_type: nil)
|
121
|
+
def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
|
122
|
+
@interpreter_context[:current_object] = owner_object
|
123
|
+
@interpreter_context[:current_path] = path
|
119
124
|
selections_by_name = {}
|
120
125
|
gather_selections(owner_object, owner_type, selections, selections_by_name)
|
121
126
|
selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
|
@@ -158,6 +163,7 @@ module GraphQL
|
|
158
163
|
@interpreter_context[:current_path] = next_path
|
159
164
|
@interpreter_context[:current_field] = field_defn
|
160
165
|
|
166
|
+
context.scoped_context = scoped_context
|
161
167
|
object = owner_object
|
162
168
|
|
163
169
|
if is_introspection
|
@@ -195,6 +201,8 @@ module GraphQL
|
|
195
201
|
end
|
196
202
|
end
|
197
203
|
|
204
|
+
@interpreter_context[:current_arguments] = kwarg_arguments
|
205
|
+
|
198
206
|
# Optimize for the case that field is selected only once
|
199
207
|
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
200
208
|
next_selections = ast_node.selections
|
@@ -206,13 +214,15 @@ module GraphQL
|
|
206
214
|
field_result = resolve_with_directives(object, ast_node) do
|
207
215
|
# Actually call the field resolver and capture the result
|
208
216
|
app_result = begin
|
209
|
-
query.
|
210
|
-
|
217
|
+
query.with_error_handling do
|
218
|
+
query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
|
219
|
+
field_defn.resolve(object, kwarg_arguments, context)
|
220
|
+
end
|
211
221
|
end
|
212
222
|
rescue GraphQL::ExecutionError => err
|
213
223
|
err
|
214
224
|
end
|
215
|
-
after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, owner_object: object, arguments: kwarg_arguments) do |inner_result|
|
225
|
+
after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
|
216
226
|
continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
|
217
227
|
if HALT != continue_value
|
218
228
|
continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
|
@@ -286,7 +296,7 @@ module GraphQL
|
|
286
296
|
r
|
287
297
|
when "UNION", "INTERFACE"
|
288
298
|
resolved_type_or_lazy = query.resolve_type(type, value)
|
289
|
-
after_lazy(resolved_type_or_lazy, owner: type, path: path, field: field, owner_object: owner_object, arguments: arguments) do |resolved_type|
|
299
|
+
after_lazy(resolved_type_or_lazy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |resolved_type|
|
290
300
|
possible_types = query.possible_types(type)
|
291
301
|
|
292
302
|
if !possible_types.include?(resolved_type)
|
@@ -306,12 +316,12 @@ module GraphQL
|
|
306
316
|
rescue GraphQL::ExecutionError => err
|
307
317
|
err
|
308
318
|
end
|
309
|
-
after_lazy(object_proxy, owner: type, path: path, field: field, owner_object: owner_object, arguments: arguments) do |inner_object|
|
319
|
+
after_lazy(object_proxy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_object|
|
310
320
|
continue_value = continue_value(path, inner_object, field, is_non_null, ast_node)
|
311
321
|
if HALT != continue_value
|
312
322
|
response_hash = {}
|
313
323
|
write_in_response(path, response_hash)
|
314
|
-
evaluate_selections(path, continue_value, type, next_selections)
|
324
|
+
evaluate_selections(path, context.scoped_context, continue_value, type, next_selections)
|
315
325
|
response_hash
|
316
326
|
end
|
317
327
|
end
|
@@ -320,6 +330,7 @@ module GraphQL
|
|
320
330
|
write_in_response(path, response_list)
|
321
331
|
inner_type = type.of_type
|
322
332
|
idx = 0
|
333
|
+
scoped_context = context.scoped_context
|
323
334
|
value.each do |inner_value|
|
324
335
|
next_path = path.dup
|
325
336
|
next_path << idx
|
@@ -327,7 +338,7 @@ module GraphQL
|
|
327
338
|
idx += 1
|
328
339
|
set_type_at_path(next_path, inner_type)
|
329
340
|
# This will update `response_list` with the lazy
|
330
|
-
after_lazy(inner_value, owner: inner_type, path: next_path, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
|
341
|
+
after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
|
331
342
|
# reset `is_non_null` here and below, because the inner type will have its own nullability constraint
|
332
343
|
continue_value = continue_value(next_path, inner_inner_value, field, false, ast_node)
|
333
344
|
if HALT != continue_value
|
@@ -393,23 +404,30 @@ module GraphQL
|
|
393
404
|
# @param field [GraphQL::Schema::Field]
|
394
405
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
395
406
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
396
|
-
def after_lazy(lazy_obj, owner:, field:, path:, owner_object:, arguments:, eager: false)
|
407
|
+
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false)
|
408
|
+
@interpreter_context[:current_object] = owner_object
|
409
|
+
@interpreter_context[:current_arguments] = arguments
|
397
410
|
@interpreter_context[:current_path] = path
|
398
411
|
@interpreter_context[:current_field] = field
|
399
412
|
if schema.lazy?(lazy_obj)
|
400
413
|
lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
|
401
414
|
@interpreter_context[:current_path] = path
|
402
415
|
@interpreter_context[:current_field] = field
|
416
|
+
@interpreter_context[:current_object] = owner_object
|
417
|
+
@interpreter_context[:current_arguments] = arguments
|
418
|
+
context.scoped_context = scoped_context
|
403
419
|
# Wrap the execution of _this_ method with tracing,
|
404
420
|
# but don't wrap the continuation below
|
405
421
|
inner_obj = begin
|
406
|
-
query.
|
407
|
-
|
422
|
+
query.with_error_handling do
|
423
|
+
query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
|
424
|
+
schema.sync_lazy(lazy_obj)
|
425
|
+
end
|
408
426
|
end
|
409
427
|
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
410
428
|
yield(err)
|
411
429
|
end
|
412
|
-
after_lazy(inner_obj, owner: owner, field: field, path: path, owner_object: owner_object, arguments: arguments, eager: eager) do |really_inner_obj|
|
430
|
+
after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager) do |really_inner_obj|
|
413
431
|
yield(really_inner_obj)
|
414
432
|
end
|
415
433
|
end
|
@@ -500,7 +518,11 @@ module GraphQL
|
|
500
518
|
args = arguments(nil, arg_type, ast_value)
|
501
519
|
# We're not tracking defaults_used, but for our purposes
|
502
520
|
# we compare the value to the default value.
|
503
|
-
|
521
|
+
|
522
|
+
input_obj = query.with_error_handling do
|
523
|
+
arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil)
|
524
|
+
end
|
525
|
+
return true, input_obj
|
504
526
|
else
|
505
527
|
flat_value = flatten_ast_value(ast_value)
|
506
528
|
return true, arg_type.coerce_input(flat_value, context)
|
@@ -15,8 +15,9 @@ module GraphQL
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def __type(name:)
|
18
|
-
|
18
|
+
return unless context.warden.reachable_type?(name)
|
19
19
|
|
20
|
+
type = context.warden.get_type(name)
|
20
21
|
if type && context.interpreter?
|
21
22
|
type = type.metadata[:type_class] || raise("Invariant: interpreter requires class-based type for #{name}")
|
22
23
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module GraphQL
|
3
4
|
module Introspection
|
4
5
|
class SchemaType < Introspection::BaseObject
|
@@ -14,7 +15,7 @@ module GraphQL
|
|
14
15
|
field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false
|
15
16
|
|
16
17
|
def types
|
17
|
-
types = @context.warden.
|
18
|
+
types = @context.warden.reachable_types.sort_by(&:graphql_name)
|
18
19
|
if context.interpreter?
|
19
20
|
types.map { |t| t.metadata[:type_class] || raise("Invariant: can't introspect non-class-based type: #{t}") }
|
20
21
|
else
|
data/lib/graphql/query.rb
CHANGED
@@ -300,6 +300,13 @@ module GraphQL
|
|
300
300
|
with_prepared_ast { @subscription }
|
301
301
|
end
|
302
302
|
|
303
|
+
# @api private
|
304
|
+
def with_error_handling
|
305
|
+
schema.error_handler.with_error_handling(context) do
|
306
|
+
yield
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
303
310
|
private
|
304
311
|
|
305
312
|
def find_operation(operations, operation_name)
|
@@ -155,6 +155,7 @@ module GraphQL
|
|
155
155
|
@path = []
|
156
156
|
@value = nil
|
157
157
|
@context = self # for SharedMethods
|
158
|
+
@scoped_context = {}
|
158
159
|
end
|
159
160
|
|
160
161
|
# @api private
|
@@ -163,15 +164,30 @@ module GraphQL
|
|
163
164
|
# @api private
|
164
165
|
attr_writer :value
|
165
166
|
|
166
|
-
|
167
|
-
|
167
|
+
# @api private
|
168
|
+
attr_accessor :scoped_context
|
168
169
|
|
169
|
-
|
170
|
-
|
170
|
+
def_delegators :@provided_values, :[]=
|
171
|
+
def_delegators :to_h, :fetch, :dig
|
172
|
+
def_delegators :@query, :trace, :interpreter?
|
171
173
|
|
172
174
|
# @!method []=(key, value)
|
173
175
|
# Reassign `key` to the hash passed to {Schema#execute} as `context:`
|
174
176
|
|
177
|
+
# Lookup `key` from the hash passed to {Schema#execute} as `context:`
|
178
|
+
def [](key)
|
179
|
+
return @scoped_context[key] if @scoped_context.key?(key)
|
180
|
+
@provided_values[key]
|
181
|
+
end
|
182
|
+
|
183
|
+
def to_h
|
184
|
+
@provided_values.merge(@scoped_context)
|
185
|
+
end
|
186
|
+
alias :to_hash :to_h
|
187
|
+
|
188
|
+
def key?(key)
|
189
|
+
@scoped_context.key?(key) || @provided_values.key?(key)
|
190
|
+
end
|
175
191
|
|
176
192
|
# @return [GraphQL::Schema::Warden]
|
177
193
|
def warden
|
@@ -195,6 +211,15 @@ module GraphQL
|
|
195
211
|
@value = nil
|
196
212
|
end
|
197
213
|
|
214
|
+
def scoped_merge!(hash)
|
215
|
+
@scoped_context = @scoped_context.merge(hash)
|
216
|
+
end
|
217
|
+
|
218
|
+
def scoped_set!(key, value)
|
219
|
+
scoped_merge!(key => value)
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
|
198
223
|
class FieldResolutionContext
|
199
224
|
include SharedMethods
|
200
225
|
include Tracing::Traceable
|
@@ -35,7 +35,9 @@ module GraphQL
|
|
35
35
|
if validation_result.valid?
|
36
36
|
if value_was_provided
|
37
37
|
# Add the variable if a value was provided
|
38
|
-
memo[variable_name] =
|
38
|
+
memo[variable_name] = schema.error_handler.with_error_handling(context) do
|
39
|
+
variable_type.coerce_input(provided_value, ctx)
|
40
|
+
end
|
39
41
|
elsif default_value != nil
|
40
42
|
# Add the variable if it wasn't provided but it has a default value (including `null`)
|
41
43
|
memo[variable_name] = GraphQL::Query::LiteralInput.coerce(variable_type, default_value, self)
|
data/lib/graphql/schema.rb
CHANGED
@@ -40,6 +40,7 @@ require "graphql/schema/directive/include"
|
|
40
40
|
require "graphql/schema/directive/skip"
|
41
41
|
require "graphql/schema/directive/feature"
|
42
42
|
require "graphql/schema/directive/transform"
|
43
|
+
require "graphql/schema/type_membership"
|
43
44
|
|
44
45
|
require "graphql/schema/resolver"
|
45
46
|
require "graphql/schema/mutation"
|
@@ -448,10 +449,11 @@ module GraphQL
|
|
448
449
|
|
449
450
|
# @see [GraphQL::Schema::Warden] Restricted access to members of a schema
|
450
451
|
# @param type_defn [GraphQL::InterfaceType, GraphQL::UnionType] the type whose members you want to retrieve
|
452
|
+
# @param context [GraphQL::Query::Context] The context for the current query
|
451
453
|
# @return [Array<GraphQL::ObjectType>] types which belong to `type_defn` in this schema
|
452
|
-
def possible_types(type_defn)
|
454
|
+
def possible_types(type_defn, context = GraphQL::Query::NullContext)
|
453
455
|
@possible_types ||= GraphQL::Schema::PossibleTypes.new(self)
|
454
|
-
@possible_types.possible_types(type_defn)
|
456
|
+
@possible_types.possible_types(type_defn, context)
|
455
457
|
end
|
456
458
|
|
457
459
|
# @see [GraphQL::Schema::Warden] Resticted access to root types
|
@@ -597,6 +599,7 @@ module GraphQL
|
|
597
599
|
alias :_schema_class :class
|
598
600
|
def_delegators :_schema_class, :visible?, :accessible?, :authorized?, :unauthorized_object, :unauthorized_field, :inaccessible_fields
|
599
601
|
def_delegators :_schema_class, :directive
|
602
|
+
def_delegators :_schema_class, :error_handler
|
600
603
|
|
601
604
|
# A function to call when {#execute} receives an invalid query string
|
602
605
|
#
|
@@ -1027,6 +1030,13 @@ module GraphQL
|
|
1027
1030
|
DefaultTypeError.call(type_err, ctx)
|
1028
1031
|
end
|
1029
1032
|
|
1033
|
+
attr_writer :error_handler
|
1034
|
+
|
1035
|
+
# @return [GraphQL::Execution::Errors, Class<GraphQL::Execution::Errors::NullErrorHandler>]
|
1036
|
+
def error_handler
|
1037
|
+
@error_handler ||= GraphQL::Execution::Errors::NullErrorHandler
|
1038
|
+
end
|
1039
|
+
|
1030
1040
|
def lazy_resolve(lazy_class, value_method)
|
1031
1041
|
lazy_classes[lazy_class] = value_method
|
1032
1042
|
end
|
@@ -121,6 +121,10 @@ module GraphQL
|
|
121
121
|
# Used by the runtime.
|
122
122
|
# @api private
|
123
123
|
def prepare_value(obj, value)
|
124
|
+
if value.is_a?(GraphQL::Schema::InputObject)
|
125
|
+
value = value.prepare
|
126
|
+
end
|
127
|
+
|
124
128
|
if @prepare.nil?
|
125
129
|
value
|
126
130
|
elsif @prepare.is_a?(String) || @prepare.is_a?(Symbol)
|
@@ -10,8 +10,9 @@ module Base64Bp
|
|
10
10
|
module_function
|
11
11
|
|
12
12
|
def urlsafe_encode64(bin, padding:)
|
13
|
-
str = strict_encode64(bin)
|
14
|
-
str
|
13
|
+
str = strict_encode64(bin)
|
14
|
+
str.tr!("+/", "-_")
|
15
|
+
str.delete!("=") unless padding
|
15
16
|
str
|
16
17
|
end
|
17
18
|
|
@@ -1,5 +1,4 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'irb/ruby-token'
|
3
2
|
|
4
3
|
module GraphQL
|
5
4
|
class Schema
|
@@ -43,9 +42,7 @@ module GraphQL
|
|
43
42
|
# A list of Ruby keywords.
|
44
43
|
#
|
45
44
|
# @api private
|
46
|
-
RUBY_KEYWORDS =
|
47
|
-
.map { |definition| definition[2] }
|
48
|
-
.compact
|
45
|
+
RUBY_KEYWORDS = [:class, :module, :def, :undef, :begin, :rescue, :ensure, :end, :if, :unless, :then, :elsif, :else, :case, :when, :while, :until, :for, :break, :next, :redo, :retry, :in, :do, :return, :yield, :super, :self, :nil, :true, :false, :and, :or, :not, :alias, :defined?, :BEGIN, :END, :__LINE__, :__FILE__]
|
49
46
|
|
50
47
|
# A list of GraphQL-Ruby keywords.
|
51
48
|
#
|
@@ -35,10 +35,12 @@ module GraphQL
|
|
35
35
|
# @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
|
36
36
|
# @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
|
37
37
|
def authorized_new(object, context)
|
38
|
-
auth_val =
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
auth_val = context.query.with_error_handling do
|
39
|
+
begin
|
40
|
+
authorized?(object, context)
|
41
|
+
rescue GraphQL::UnauthorizedError => err
|
42
|
+
context.schema.unauthorized_object(err)
|
43
|
+
end
|
42
44
|
end
|
43
45
|
|
44
46
|
context.schema.after_lazy(auth_val) do |is_authorized|
|
@@ -20,12 +20,12 @@ module GraphQL
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def possible_types(type_defn)
|
23
|
+
def possible_types(type_defn, ctx)
|
24
24
|
case type_defn
|
25
25
|
when Module
|
26
|
-
possible_types(type_defn.graphql_definition)
|
26
|
+
possible_types(type_defn.graphql_definition, ctx)
|
27
27
|
when GraphQL::UnionType
|
28
|
-
type_defn.possible_types
|
28
|
+
type_defn.possible_types(ctx)
|
29
29
|
when GraphQL::InterfaceType
|
30
30
|
@interface_implementers[type_defn]
|
31
31
|
when GraphQL::BaseType
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
# This class joins an object type to an abstract type (interface or union) of which
|
6
|
+
# it is a member.
|
7
|
+
#
|
8
|
+
# TODO: Not yet implemented for interfaces.
|
9
|
+
class TypeMembership
|
10
|
+
# @return [Class<GraphQL::Schema::Object>]
|
11
|
+
attr_reader :object_type
|
12
|
+
|
13
|
+
# @return [Class<GraphQL::Schema::Union>, Module<GraphQL::Schema::Interface>]
|
14
|
+
attr_reader :abstract_type
|
15
|
+
|
16
|
+
# Called when an object is hooked up to an abstract type, such as {Schema::Union.possible_types}
|
17
|
+
# or {Schema::Object.implements} (for interfaces).
|
18
|
+
#
|
19
|
+
# @param abstract_type [Class<GraphQL::Schema::Union>, Module<GraphQL::Schema::Interface>]
|
20
|
+
# @param object_type [Class<GraphQL::Schema::Object>]
|
21
|
+
# @param options [Hash] Any options passed to `.possible_types` or `.implements`
|
22
|
+
def initialize(abstract_type, object_type, options)
|
23
|
+
@abstract_type = abstract_type
|
24
|
+
@object_type = object_type
|
25
|
+
@options = options
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type}
|
29
|
+
def visible?(_ctx)
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/graphql/schema/union.rb
CHANGED
@@ -5,13 +5,19 @@ module GraphQL
|
|
5
5
|
extend GraphQL::Schema::Member::AcceptsDefinition
|
6
6
|
|
7
7
|
class << self
|
8
|
-
def possible_types(*types)
|
8
|
+
def possible_types(*types, context: GraphQL::Query::NullContext, **options)
|
9
9
|
if types.any?
|
10
|
-
|
10
|
+
types.each do |t|
|
11
|
+
type_memberships << type_membership_class.new(self, t, options)
|
12
|
+
end
|
11
13
|
else
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
visible_types = []
|
15
|
+
type_memberships.each do |type_membership|
|
16
|
+
if type_membership.visible?(context)
|
17
|
+
visible_types << type_membership.object_type
|
18
|
+
end
|
19
|
+
end
|
20
|
+
visible_types
|
15
21
|
end
|
16
22
|
end
|
17
23
|
|
@@ -19,7 +25,7 @@ module GraphQL
|
|
19
25
|
type_defn = GraphQL::UnionType.new
|
20
26
|
type_defn.name = graphql_name
|
21
27
|
type_defn.description = description
|
22
|
-
type_defn.
|
28
|
+
type_defn.type_memberships = type_memberships
|
23
29
|
if respond_to?(:resolve_type)
|
24
30
|
type_defn.resolve_type = method(:resolve_type)
|
25
31
|
end
|
@@ -27,9 +33,23 @@ module GraphQL
|
|
27
33
|
type_defn
|
28
34
|
end
|
29
35
|
|
36
|
+
def type_membership_class(membership_class = nil)
|
37
|
+
if membership_class
|
38
|
+
@type_membership_class = membership_class
|
39
|
+
else
|
40
|
+
@type_membership_class || find_inherited_value(:type_membership_class, GraphQL::Schema::TypeMembership)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
30
44
|
def kind
|
31
45
|
GraphQL::TypeKinds::UNION
|
32
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def type_memberships
|
51
|
+
@type_memberships ||= []
|
52
|
+
end
|
33
53
|
end
|
34
54
|
end
|
35
55
|
end
|
@@ -1,4 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
2
5
|
module GraphQL
|
3
6
|
class Schema
|
4
7
|
# Restrict access to a {GraphQL::Schema} with a user-defined filter.
|
@@ -40,7 +43,8 @@ module GraphQL
|
|
40
43
|
# @param deep_check [Boolean]
|
41
44
|
def initialize(filter, context:, schema:)
|
42
45
|
@schema = schema
|
43
|
-
@
|
46
|
+
@context = context
|
47
|
+
@visibility_cache = read_through { |m| filter.call(m, @context) }
|
44
48
|
end
|
45
49
|
|
46
50
|
# @return [Array<GraphQL::BaseType>] Visible types in the schema
|
@@ -62,6 +66,17 @@ module GraphQL
|
|
62
66
|
@visible_types[type_name]
|
63
67
|
end
|
64
68
|
|
69
|
+
# @return [Array<GraphQL::BaseType>] Visible and reachable types in the schema
|
70
|
+
def reachable_types
|
71
|
+
@reachable_types ||= reachable_type_set.to_a
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return Boolean True if the type is visible and reachable in the schema
|
75
|
+
def reachable_type?(type_name)
|
76
|
+
type = get_type(type_name)
|
77
|
+
type && reachable_type_set.include?(type)
|
78
|
+
end
|
79
|
+
|
65
80
|
# @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`, if it exists
|
66
81
|
def get_field(parent_type, field_name)
|
67
82
|
|
@@ -81,7 +96,7 @@ module GraphQL
|
|
81
96
|
|
82
97
|
# @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
|
83
98
|
def possible_types(type_defn)
|
84
|
-
@visible_possible_types ||= read_through { |type_defn| @schema.possible_types(type_defn).select { |t| visible_type?(t) } }
|
99
|
+
@visible_possible_types ||= read_through { |type_defn| @schema.possible_types(type_defn, @context).select { |t| visible_type?(t) } }
|
85
100
|
@visible_possible_types[type_defn]
|
86
101
|
end
|
87
102
|
|
@@ -170,7 +185,7 @@ module GraphQL
|
|
170
185
|
end
|
171
186
|
|
172
187
|
def visible_possible_types?(type_defn)
|
173
|
-
@schema.possible_types(type_defn).any? { |t| visible_type?(t) }
|
188
|
+
@schema.possible_types(type_defn, @context).any? { |t| visible_type?(t) }
|
174
189
|
end
|
175
190
|
|
176
191
|
def visible?(member)
|
@@ -180,6 +195,65 @@ module GraphQL
|
|
180
195
|
def read_through
|
181
196
|
Hash.new { |h, k| h[k] = yield(k) }
|
182
197
|
end
|
198
|
+
|
199
|
+
def reachable_type_set
|
200
|
+
return @reachable_type_set if defined?(@reachable_type_set)
|
201
|
+
|
202
|
+
@reachable_type_set = Set.new
|
203
|
+
|
204
|
+
unvisited_types = []
|
205
|
+
['query', 'mutation', 'subscription'].each do |op_name|
|
206
|
+
root_type = root_type_for_operation(op_name)
|
207
|
+
unvisited_types << root_type if root_type
|
208
|
+
end
|
209
|
+
unvisited_types.concat(@schema.introspection_system.object_types)
|
210
|
+
@schema.orphan_types.each do |orphan_type|
|
211
|
+
unvisited_types << orphan_type.graphql_definition if get_type(orphan_type.graphql_name)
|
212
|
+
end
|
213
|
+
|
214
|
+
until unvisited_types.empty?
|
215
|
+
type = unvisited_types.pop
|
216
|
+
if @reachable_type_set.add?(type)
|
217
|
+
if type.kind.input_object?
|
218
|
+
# recurse into visible arguments
|
219
|
+
arguments(type).each do |argument|
|
220
|
+
argument_type = argument.type.unwrap
|
221
|
+
unvisited_types << argument_type
|
222
|
+
end
|
223
|
+
elsif type.kind.union?
|
224
|
+
# recurse into visible possible types
|
225
|
+
possible_types(type).each do |possible_type|
|
226
|
+
unvisited_types << possible_type
|
227
|
+
end
|
228
|
+
elsif type.kind.fields?
|
229
|
+
if type.kind.interface?
|
230
|
+
# recurse into visible possible types
|
231
|
+
possible_types(type).each do |possible_type|
|
232
|
+
unvisited_types << possible_type
|
233
|
+
end
|
234
|
+
elsif type.kind.object?
|
235
|
+
# recurse into visible implemented interfaces
|
236
|
+
interfaces(type).each do |interface|
|
237
|
+
unvisited_types << interface
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# recurse into visible fields
|
242
|
+
fields(type).each do |field|
|
243
|
+
field_type = field.type.unwrap
|
244
|
+
unvisited_types << field_type
|
245
|
+
# recurse into visible arguments
|
246
|
+
arguments(field).each do |argument|
|
247
|
+
argument_type = argument.type.unwrap
|
248
|
+
unvisited_types << argument_type
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
@reachable_type_set
|
256
|
+
end
|
183
257
|
end
|
184
258
|
end
|
185
259
|
end
|
data/lib/graphql/union_type.rb
CHANGED
@@ -24,22 +24,27 @@ module GraphQL
|
|
24
24
|
# }
|
25
25
|
#
|
26
26
|
class UnionType < GraphQL::BaseType
|
27
|
-
accepts_definitions :
|
28
|
-
|
27
|
+
accepts_definitions :resolve_type, :type_membership_class,
|
28
|
+
possible_types: ->(target, possible_types, options = {}) { target.add_possible_types(possible_types, options) }
|
29
|
+
ensure_defined :possible_types, :resolve_type, :resolve_type_proc, :type_membership_class
|
29
30
|
|
30
31
|
attr_accessor :resolve_type_proc
|
32
|
+
attr_reader :type_memberships
|
33
|
+
attr_accessor :type_membership_class
|
31
34
|
|
32
35
|
def initialize
|
33
36
|
super
|
34
|
-
@
|
35
|
-
@
|
37
|
+
@type_membership_class = GraphQL::Schema::TypeMembership
|
38
|
+
@type_memberships = []
|
39
|
+
@cached_possible_types = nil
|
36
40
|
@resolve_type_proc = nil
|
37
41
|
end
|
38
42
|
|
39
43
|
def initialize_copy(other)
|
40
44
|
super
|
41
|
-
@
|
42
|
-
@
|
45
|
+
@type_membership_class = other.type_membership_class
|
46
|
+
@type_memberships = other.type_memberships.dup
|
47
|
+
@cached_possible_types = nil
|
43
48
|
end
|
44
49
|
|
45
50
|
def kind
|
@@ -47,33 +52,44 @@ module GraphQL
|
|
47
52
|
end
|
48
53
|
|
49
54
|
# @return [Boolean] True if `child_type_defn` is a member of this {UnionType}
|
50
|
-
def include?(child_type_defn)
|
51
|
-
possible_types.include?(child_type_defn)
|
52
|
-
end
|
53
|
-
|
54
|
-
def possible_types=(new_possible_types)
|
55
|
-
@clean_possible_types = nil
|
56
|
-
@dirty_possible_types = new_possible_types
|
55
|
+
def include?(child_type_defn, ctx = GraphQL::Query::NullContext)
|
56
|
+
possible_types(ctx).include?(child_type_defn)
|
57
57
|
end
|
58
58
|
|
59
59
|
# @return [Array<GraphQL::ObjectType>] Types which may be found in this union
|
60
|
-
def possible_types
|
61
|
-
|
62
|
-
if
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
60
|
+
def possible_types(ctx = GraphQL::Query::NullContext)
|
61
|
+
if ctx == GraphQL::Query::NullContext
|
62
|
+
# Only cache the default case; if we cached for every `ctx`, it would be a memory leak
|
63
|
+
# (The warden should cache calls to this method, so it's called only once per query,
|
64
|
+
# unless user code calls it directly.)
|
65
|
+
@cached_possible_types ||= possible_types_for_context(ctx)
|
66
|
+
else
|
67
|
+
possible_types_for_context(ctx)
|
67
68
|
end
|
68
69
|
end
|
69
70
|
|
71
|
+
def possible_types=(types)
|
72
|
+
# This is a re-assignment, so clear the previous values
|
73
|
+
@type_memberships = []
|
74
|
+
@cached_possible_types = nil
|
75
|
+
add_possible_types(types, {})
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_possible_types(types, options)
|
79
|
+
@type_memberships ||= []
|
80
|
+
Array(types).each { |t|
|
81
|
+
@type_memberships << self.type_membership_class.new(self, t, options)
|
82
|
+
}
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
70
86
|
# Get a possible type of this {UnionType} by type name
|
71
87
|
# @param type_name [String]
|
72
88
|
# @param ctx [GraphQL::Query::Context] The context for the current query
|
73
89
|
# @return [GraphQL::ObjectType, nil] The type named `type_name` if it exists and is a member of this {UnionType}, (else `nil`)
|
74
90
|
def get_possible_type(type_name, ctx)
|
75
91
|
type = ctx.query.get_type(type_name)
|
76
|
-
type if type && ctx.query.schema.possible_types(self).include?(type)
|
92
|
+
type if type && ctx.query.schema.possible_types(self, ctx).include?(type)
|
77
93
|
end
|
78
94
|
|
79
95
|
# Check if a type is a possible type of this {UnionType}
|
@@ -93,8 +109,20 @@ module GraphQL
|
|
93
109
|
@resolve_type_proc = new_resolve_type_proc
|
94
110
|
end
|
95
111
|
|
96
|
-
|
112
|
+
def type_memberships=(type_memberships)
|
113
|
+
@type_memberships = type_memberships
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
97
117
|
|
98
|
-
|
118
|
+
def possible_types_for_context(ctx)
|
119
|
+
visible_types = []
|
120
|
+
@type_memberships.each do |type_membership|
|
121
|
+
if type_membership.visible?(ctx)
|
122
|
+
visible_types << BaseType.resolve_related_type(type_membership.object_type)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
visible_types
|
126
|
+
end
|
99
127
|
end
|
100
128
|
end
|
data/lib/graphql/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.9.
|
4
|
+
version: 1.9.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-12-
|
11
|
+
date: 2019-12-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|
@@ -588,6 +588,7 @@ files:
|
|
588
588
|
- lib/graphql/schema/timeout_middleware.rb
|
589
589
|
- lib/graphql/schema/traversal.rb
|
590
590
|
- lib/graphql/schema/type_expression.rb
|
591
|
+
- lib/graphql/schema/type_membership.rb
|
591
592
|
- lib/graphql/schema/union.rb
|
592
593
|
- lib/graphql/schema/unique_within_type.rb
|
593
594
|
- lib/graphql/schema/validation.rb
|