graphql 1.10.0.pre2 → 1.10.0.pre3
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 +20 -16
- data/lib/graphql/execution/interpreter/runtime.rb +44 -19
- data/lib/graphql/introspection/entry_points.rb +1 -0
- 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 +30 -14
- data/lib/graphql/schema/argument.rb +5 -2
- data/lib/graphql/schema/base_64_bp.rb +3 -2
- data/lib/graphql/schema/field.rb +7 -7
- 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 +24 -6
- data/lib/graphql/schema/warden.rb +79 -9
- data/lib/graphql/union_type.rb +50 -22
- 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: 78e01c4a75da2ab94a931c38e49c2e697de00b28837045807d1c7c9f02a9d831
|
4
|
+
data.tar.gz: eb281e483efaafef9824d8e41f0dc0558fe2c2351a4d675d2b875cc2de630a8f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 893b327d435f43a7ab1e97940662a9d786b27446b07280df6fe171e1271cc07048ed1098e7bf6cf7bd643d9c77765be4b1abd175f771faefdcc4cc6e49e0724a
|
7
|
+
data.tar.gz: 7ca9736e3fd003b784f2a5c3dfa62a02b44675623c0cddfb4064d26924e77694b6f1f05a9f6ab795d2e6f6ced93f8e57542e4b63123485a7f4599c6d92c36db7
|
@@ -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
|
@@ -18,31 +18,35 @@ module GraphQL
|
|
18
18
|
#
|
19
19
|
class Errors
|
20
20
|
def self.use(schema)
|
21
|
-
schema.
|
21
|
+
schema.error_handler = self.new(schema)
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
def initialize(schema)
|
25
|
+
@schema = schema
|
26
|
+
end
|
27
|
+
|
28
|
+
class NullErrorHandler
|
29
|
+
def self.with_error_handling(_ctx)
|
29
30
|
yield
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
# Call the given block with the schema's configured error handlers.
|
35
|
+
#
|
36
|
+
# If the block returns a lazy value, it's not wrapped with error handling. That area will have to be wrapped itself.
|
37
|
+
#
|
38
|
+
# @param ctx [GraphQL::Query::Context]
|
39
|
+
# @return [Object] Either the result of the given block, or some object to replace the result, in case of error handling.
|
40
|
+
def with_error_handling(ctx)
|
36
41
|
yield
|
37
42
|
rescue StandardError => err
|
38
|
-
|
39
|
-
schema = ctx.schema
|
40
|
-
rescues = schema.rescues
|
43
|
+
rescues = ctx.schema.rescues
|
41
44
|
_err_class, handler = rescues.find { |err_class, handler| err.is_a?(err_class) }
|
42
45
|
if handler
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
+
runtime_info = ctx.namespace(:interpreter) || {}
|
47
|
+
obj = runtime_info[:current_object]
|
48
|
+
args = runtime_info[:current_arguments]
|
49
|
+
field = runtime_info[:current_field]
|
46
50
|
if obj.is_a?(GraphQL::Schema::Object)
|
47
51
|
obj = obj.object
|
48
52
|
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,18 +43,21 @@ 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
|
root_type = schema.root_type_for_operation(root_op_type)
|
50
|
+
path = []
|
51
|
+
@interpreter_context[:current_object] = query.root_value
|
52
|
+
@interpreter_context[:current_path] = path
|
49
53
|
object_proxy = root_type.authorized_new(query.root_value, context)
|
50
54
|
object_proxy = schema.sync_lazy(object_proxy)
|
51
55
|
if object_proxy.nil?
|
52
56
|
# Root .authorized? returned false.
|
53
|
-
write_in_response(
|
57
|
+
write_in_response(path, nil)
|
54
58
|
nil
|
55
59
|
else
|
56
|
-
path
|
57
|
-
evaluate_selections(path, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
|
60
|
+
evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
|
58
61
|
nil
|
59
62
|
end
|
60
63
|
end
|
@@ -100,7 +103,8 @@ module GraphQL
|
|
100
103
|
when GraphQL::Language::Nodes::FragmentSpread
|
101
104
|
fragment_def = query.fragments[node.name]
|
102
105
|
type_defn = schema.get_type(fragment_def.type.name)
|
103
|
-
query.warden.possible_types(type_defn)
|
106
|
+
possible_types = query.warden.possible_types(type_defn)
|
107
|
+
possible_types.each do |t|
|
104
108
|
if t == owner_type
|
105
109
|
gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
|
106
110
|
break
|
@@ -112,7 +116,9 @@ module GraphQL
|
|
112
116
|
end
|
113
117
|
end
|
114
118
|
|
115
|
-
def evaluate_selections(path, owner_object, owner_type, selections, root_operation_type: nil)
|
119
|
+
def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
|
120
|
+
@interpreter_context[:current_object] = owner_object
|
121
|
+
@interpreter_context[:current_path] = path
|
116
122
|
selections_by_name = {}
|
117
123
|
gather_selections(owner_object, owner_type, selections, selections_by_name)
|
118
124
|
selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
|
@@ -155,6 +161,7 @@ module GraphQL
|
|
155
161
|
@interpreter_context[:current_path] = next_path
|
156
162
|
@interpreter_context[:current_field] = field_defn
|
157
163
|
|
164
|
+
context.scoped_context = scoped_context
|
158
165
|
object = owner_object
|
159
166
|
|
160
167
|
if is_introspection
|
@@ -192,6 +199,8 @@ module GraphQL
|
|
192
199
|
end
|
193
200
|
end
|
194
201
|
|
202
|
+
@interpreter_context[:current_arguments] = kwarg_arguments
|
203
|
+
|
195
204
|
# Optimize for the case that field is selected only once
|
196
205
|
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
197
206
|
next_selections = ast_node.selections
|
@@ -203,13 +212,15 @@ module GraphQL
|
|
203
212
|
field_result = resolve_with_directives(object, ast_node) do
|
204
213
|
# Actually call the field resolver and capture the result
|
205
214
|
app_result = begin
|
206
|
-
query.
|
207
|
-
|
215
|
+
query.with_error_handling do
|
216
|
+
query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
|
217
|
+
field_defn.resolve(object, kwarg_arguments, context)
|
218
|
+
end
|
208
219
|
end
|
209
220
|
rescue GraphQL::ExecutionError => err
|
210
221
|
err
|
211
222
|
end
|
212
|
-
after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, owner_object: object, arguments: kwarg_arguments) do |inner_result|
|
223
|
+
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|
|
213
224
|
continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
|
214
225
|
if HALT != continue_value
|
215
226
|
continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
|
@@ -283,7 +294,7 @@ module GraphQL
|
|
283
294
|
r
|
284
295
|
when "UNION", "INTERFACE"
|
285
296
|
resolved_type_or_lazy = query.resolve_type(type, value)
|
286
|
-
after_lazy(resolved_type_or_lazy, owner: type, path: path, field: field, owner_object: owner_object, arguments: arguments) do |resolved_type|
|
297
|
+
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|
|
287
298
|
possible_types = query.possible_types(type)
|
288
299
|
|
289
300
|
if !possible_types.include?(resolved_type)
|
@@ -302,12 +313,12 @@ module GraphQL
|
|
302
313
|
rescue GraphQL::ExecutionError => err
|
303
314
|
err
|
304
315
|
end
|
305
|
-
after_lazy(object_proxy, owner: type, path: path, field: field, owner_object: owner_object, arguments: arguments) do |inner_object|
|
316
|
+
after_lazy(object_proxy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_object|
|
306
317
|
continue_value = continue_value(path, inner_object, field, is_non_null, ast_node)
|
307
318
|
if HALT != continue_value
|
308
319
|
response_hash = {}
|
309
320
|
write_in_response(path, response_hash)
|
310
|
-
evaluate_selections(path, continue_value, type, next_selections)
|
321
|
+
evaluate_selections(path, context.scoped_context, continue_value, type, next_selections)
|
311
322
|
response_hash
|
312
323
|
end
|
313
324
|
end
|
@@ -316,6 +327,7 @@ module GraphQL
|
|
316
327
|
write_in_response(path, response_list)
|
317
328
|
inner_type = type.of_type
|
318
329
|
idx = 0
|
330
|
+
scoped_context = context.scoped_context
|
319
331
|
value.each do |inner_value|
|
320
332
|
next_path = path.dup
|
321
333
|
next_path << idx
|
@@ -323,7 +335,7 @@ module GraphQL
|
|
323
335
|
idx += 1
|
324
336
|
set_type_at_path(next_path, inner_type)
|
325
337
|
# This will update `response_list` with the lazy
|
326
|
-
after_lazy(inner_value, owner: inner_type, path: next_path, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
|
338
|
+
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|
|
327
339
|
# reset `is_non_null` here and below, because the inner type will have its own nullability constraint
|
328
340
|
continue_value = continue_value(next_path, inner_inner_value, field, false, ast_node)
|
329
341
|
if HALT != continue_value
|
@@ -379,23 +391,30 @@ module GraphQL
|
|
379
391
|
# @param field [GraphQL::Schema::Field]
|
380
392
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
381
393
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
382
|
-
def after_lazy(lazy_obj, owner:, field:, path:, owner_object:, arguments:, eager: false)
|
394
|
+
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false)
|
395
|
+
@interpreter_context[:current_object] = owner_object
|
396
|
+
@interpreter_context[:current_arguments] = arguments
|
383
397
|
@interpreter_context[:current_path] = path
|
384
398
|
@interpreter_context[:current_field] = field
|
385
399
|
if schema.lazy?(lazy_obj)
|
386
400
|
lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
|
387
401
|
@interpreter_context[:current_path] = path
|
388
402
|
@interpreter_context[:current_field] = field
|
403
|
+
@interpreter_context[:current_object] = owner_object
|
404
|
+
@interpreter_context[:current_arguments] = arguments
|
405
|
+
context.scoped_context = scoped_context
|
389
406
|
# Wrap the execution of _this_ method with tracing,
|
390
407
|
# but don't wrap the continuation below
|
391
408
|
inner_obj = begin
|
392
|
-
query.
|
393
|
-
|
409
|
+
query.with_error_handling do
|
410
|
+
query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
|
411
|
+
schema.sync_lazy(lazy_obj)
|
412
|
+
end
|
394
413
|
end
|
395
414
|
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
396
415
|
yield(err)
|
397
416
|
end
|
398
|
-
after_lazy(inner_obj, owner: owner, field: field, path: path, owner_object: owner_object, arguments: arguments, eager: eager) do |really_inner_obj|
|
417
|
+
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|
|
399
418
|
yield(really_inner_obj)
|
400
419
|
end
|
401
420
|
end
|
@@ -494,15 +513,21 @@ module GraphQL
|
|
494
513
|
if ast_value.nil?
|
495
514
|
return false, nil
|
496
515
|
else
|
497
|
-
|
516
|
+
args = ast_value
|
498
517
|
end
|
499
518
|
else
|
500
519
|
# For these, `prepare` is applied during `#initialize`.
|
501
520
|
# Pass `nil` so it will be skipped in `#arguments`.
|
502
521
|
# What a mess.
|
503
522
|
args = arguments(nil, arg_type, ast_value)
|
504
|
-
return true, arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil)
|
505
523
|
end
|
524
|
+
|
525
|
+
input_obj = query.with_error_handling do
|
526
|
+
# We're not tracking defaults_used, but for our purposes
|
527
|
+
# we compare the value to the default value.
|
528
|
+
arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil)
|
529
|
+
end
|
530
|
+
return true, input_obj
|
506
531
|
else
|
507
532
|
flat_value = if already_arguments
|
508
533
|
# It was coerced by variable handling
|
@@ -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
|
-
@context.warden.
|
18
|
+
@context.warden.reachable_types.sort_by(&:graphql_name)
|
18
19
|
end
|
19
20
|
|
20
21
|
def query_type
|
data/lib/graphql/query.rb
CHANGED
@@ -306,6 +306,13 @@ module GraphQL
|
|
306
306
|
with_prepared_ast { @subscription }
|
307
307
|
end
|
308
308
|
|
309
|
+
# @api private
|
310
|
+
def with_error_handling
|
311
|
+
schema.error_handler.with_error_handling(context) do
|
312
|
+
yield
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
309
316
|
private
|
310
317
|
|
311
318
|
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
|
@@ -38,7 +38,9 @@ module GraphQL
|
|
38
38
|
memo[variable_name] = if provided_value.nil?
|
39
39
|
nil
|
40
40
|
else
|
41
|
-
|
41
|
+
schema.error_handler.with_error_handling(context) do
|
42
|
+
variable_type.coerce_input(provided_value, ctx)
|
43
|
+
end
|
42
44
|
end
|
43
45
|
elsif default_value != nil
|
44
46
|
# Add the variable if it wasn't provided but it has a default value (including `null`)
|
data/lib/graphql/schema.rb
CHANGED
@@ -41,6 +41,7 @@ require "graphql/schema/directive/include"
|
|
41
41
|
require "graphql/schema/directive/skip"
|
42
42
|
require "graphql/schema/directive/feature"
|
43
43
|
require "graphql/schema/directive/transform"
|
44
|
+
require "graphql/schema/type_membership"
|
44
45
|
|
45
46
|
require "graphql/schema/resolver"
|
46
47
|
require "graphql/schema/mutation"
|
@@ -466,10 +467,11 @@ module GraphQL
|
|
466
467
|
|
467
468
|
# @see [GraphQL::Schema::Warden] Restricted access to members of a schema
|
468
469
|
# @param type_defn [GraphQL::InterfaceType, GraphQL::UnionType] the type whose members you want to retrieve
|
470
|
+
# @param context [GraphQL::Query::Context] The context for the current query
|
469
471
|
# @return [Array<GraphQL::ObjectType>] types which belong to `type_defn` in this schema
|
470
|
-
def possible_types(type_defn)
|
472
|
+
def possible_types(type_defn, context = GraphQL::Query::NullContext)
|
471
473
|
@possible_types ||= GraphQL::Schema::PossibleTypes.new(self)
|
472
|
-
@possible_types.possible_types(type_defn)
|
474
|
+
@possible_types.possible_types(type_defn, context)
|
473
475
|
end
|
474
476
|
|
475
477
|
# @see [GraphQL::Schema::Warden] Resticted access to root types
|
@@ -615,6 +617,7 @@ module GraphQL
|
|
615
617
|
alias :_schema_class :class
|
616
618
|
def_delegators :_schema_class, :unauthorized_object, :unauthorized_field, :inaccessible_fields
|
617
619
|
def_delegators :_schema_class, :directive
|
620
|
+
def_delegators :_schema_class, :error_handler
|
618
621
|
|
619
622
|
|
620
623
|
# Given this schema member, find the class-based definition object
|
@@ -987,11 +990,20 @@ module GraphQL
|
|
987
990
|
# @param type [Module] The type definition whose possible types you want to see
|
988
991
|
# @return [Hash<String, Module>] All possible types, if no `type` is given.
|
989
992
|
# @return [Array<Module>] Possible types for `type`, if it's given.
|
990
|
-
def possible_types(type = nil)
|
993
|
+
def possible_types(type = nil, context = GraphQL::Query::NullContext)
|
991
994
|
if type
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
+
# TODO duck-typing `.possible_types` would probably be nicer here
|
996
|
+
if type.kind.union?
|
997
|
+
type.possible_types(context: context)
|
998
|
+
else
|
999
|
+
own_possible_types[type.graphql_name] ||
|
1000
|
+
introspection_system.possible_types[type.graphql_name] ||
|
1001
|
+
(
|
1002
|
+
superclass.respond_to?(:possible_types) ?
|
1003
|
+
superclass.possible_types(type, context) :
|
1004
|
+
EMPTY_ARRAY
|
1005
|
+
)
|
1006
|
+
end
|
995
1007
|
else
|
996
1008
|
find_inherited_value(:possible_types, EMPTY_HASH)
|
997
1009
|
.merge(own_possible_types)
|
@@ -1347,6 +1359,12 @@ module GraphQL
|
|
1347
1359
|
def parse_error(parse_err, ctx)
|
1348
1360
|
ctx.errors.push(parse_err)
|
1349
1361
|
end
|
1362
|
+
attr_writer :error_handler
|
1363
|
+
|
1364
|
+
# @return [GraphQL::Execution::Errors, Class<GraphQL::Execution::Errors::NullErrorHandler>]
|
1365
|
+
def error_handler
|
1366
|
+
@error_handler ||= GraphQL::Execution::Errors::NullErrorHandler
|
1367
|
+
end
|
1350
1368
|
|
1351
1369
|
def lazy_resolve(lazy_class, value_method)
|
1352
1370
|
lazy_methods.set(lazy_class, value_method)
|
@@ -1641,18 +1659,16 @@ module GraphQL
|
|
1641
1659
|
if owner.kind.union?
|
1642
1660
|
# It's a union with possible_types
|
1643
1661
|
# Replace the item by class name
|
1644
|
-
|
1645
|
-
|
1662
|
+
owner.type_memberships.each { |tm|
|
1663
|
+
possible_type = tm.object_type
|
1664
|
+
if possible_type.is_a?(String) && (possible_type == type.name)
|
1646
1665
|
# This is a match of Ruby class names, not graphql names,
|
1647
1666
|
# since strings are used to refer to constants.
|
1648
|
-
type
|
1649
|
-
elsif
|
1650
|
-
type
|
1651
|
-
else
|
1652
|
-
t
|
1667
|
+
tm.object_type = type
|
1668
|
+
elsif possible_type.is_a?(LateBoundType) && possible_type.graphql_name == type.graphql_name
|
1669
|
+
tm.object_type = type
|
1653
1670
|
end
|
1654
1671
|
}
|
1655
|
-
owner.possible_types(*new_possible_types)
|
1656
1672
|
own_possible_types[owner.graphql_name] = owner.possible_types
|
1657
1673
|
elsif type.kind.interface? && owner.kind.object?
|
1658
1674
|
new_interfaces = owner.interfaces.map do |t|
|
@@ -2,6 +2,10 @@
|
|
2
2
|
module GraphQL
|
3
3
|
class Schema
|
4
4
|
class Argument
|
5
|
+
if !String.method_defined?(:-@)
|
6
|
+
using GraphQL::StringDedupBackport
|
7
|
+
end
|
8
|
+
|
5
9
|
include GraphQL::Schema::Member::CachedGraphQLDefinition
|
6
10
|
include GraphQL::Schema::Member::AcceptsDefinition
|
7
11
|
include GraphQL::Schema::Member::HasPath
|
@@ -43,8 +47,7 @@ module GraphQL
|
|
43
47
|
# @param method_access [Boolean] If false, don't build method access on legacy {Query::Arguments} instances.
|
44
48
|
def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, &definition_block)
|
45
49
|
arg_name ||= name
|
46
|
-
|
47
|
-
@name = name_str.freeze
|
50
|
+
@name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
|
48
51
|
@type_expr = type_expr || type
|
49
52
|
@description = desc || description
|
50
53
|
@null = !required
|
@@ -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
|
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -16,6 +16,7 @@ module GraphQL
|
|
16
16
|
include GraphQL::Schema::Member::HasAstNode
|
17
17
|
include GraphQL::Schema::Member::HasPath
|
18
18
|
extend GraphQL::Schema::FindInheritedValue
|
19
|
+
include GraphQL::Schema::FindInheritedValue::EmptyObjects
|
19
20
|
|
20
21
|
# @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
|
21
22
|
attr_reader :name
|
@@ -186,7 +187,7 @@ module GraphQL
|
|
186
187
|
# @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
|
187
188
|
# @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
|
188
189
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
189
|
-
def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: nil, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions:
|
190
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: nil, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, arguments: EMPTY_HASH, &definition_block)
|
190
191
|
if name.nil?
|
191
192
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
192
193
|
end
|
@@ -202,8 +203,9 @@ module GraphQL
|
|
202
203
|
raise ArgumentError, "keyword `extras:` may only be used with method-based resolve and class-based field such as mutation class, please remove `field:`, `function:` or `resolve:`"
|
203
204
|
end
|
204
205
|
@original_name = name
|
205
|
-
|
206
|
-
@
|
206
|
+
name_s = -name.to_s
|
207
|
+
@underscored_name = -Member::BuildType.underscore(name_s)
|
208
|
+
@name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
|
207
209
|
@description = description
|
208
210
|
if field.is_a?(GraphQL::Schema::Field)
|
209
211
|
raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field."
|
@@ -250,13 +252,11 @@ module GraphQL
|
|
250
252
|
@ast_node = ast_node
|
251
253
|
@method_conflict_warning = method_conflict_warning
|
252
254
|
|
253
|
-
# Override the default from HasArguments
|
254
|
-
@own_arguments = {}
|
255
255
|
arguments.each do |name, arg|
|
256
256
|
if arg.is_a?(Hash)
|
257
257
|
argument(name: name, **arg)
|
258
258
|
else
|
259
|
-
|
259
|
+
own_arguments[name] = arg
|
260
260
|
end
|
261
261
|
end
|
262
262
|
|
@@ -689,7 +689,7 @@ module GraphQL
|
|
689
689
|
# Written iteratively to avoid big stack traces.
|
690
690
|
# @return [Object] Whatever the
|
691
691
|
def with_extensions(obj, args, ctx)
|
692
|
-
if @extensions.
|
692
|
+
if @extensions.nil?
|
693
693
|
yield(obj, args)
|
694
694
|
else
|
695
695
|
# Save these so that the originals can be re-given to `after_resolve` handlers.
|
@@ -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_accessor :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,8 +25,8 @@ 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.possible_types = possible_types
|
23
28
|
type_defn.ast_node = ast_node
|
29
|
+
type_defn.type_memberships = type_memberships
|
24
30
|
if respond_to?(:resolve_type)
|
25
31
|
type_defn.resolve_type = method(:resolve_type)
|
26
32
|
end
|
@@ -28,9 +34,21 @@ module GraphQL
|
|
28
34
|
type_defn
|
29
35
|
end
|
30
36
|
|
37
|
+
def type_membership_class(membership_class = nil)
|
38
|
+
if membership_class
|
39
|
+
@type_membership_class = membership_class
|
40
|
+
else
|
41
|
+
@type_membership_class || find_inherited_value(:type_membership_class, GraphQL::Schema::TypeMembership)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
31
45
|
def kind
|
32
46
|
GraphQL::TypeKinds::UNION
|
33
47
|
end
|
48
|
+
|
49
|
+
def type_memberships
|
50
|
+
@type_memberships ||= []
|
51
|
+
end
|
34
52
|
end
|
35
53
|
end
|
36
54
|
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.
|
@@ -44,6 +47,7 @@ module GraphQL
|
|
44
47
|
@query = @schema.query
|
45
48
|
@mutation = @schema.mutation
|
46
49
|
@subscription = @schema.subscription
|
50
|
+
@context = context
|
47
51
|
@visibility_cache = read_through { |m| filter.call(m, context) }
|
48
52
|
end
|
49
53
|
|
@@ -74,6 +78,17 @@ module GraphQL
|
|
74
78
|
@visible_types[type_name]
|
75
79
|
end
|
76
80
|
|
81
|
+
# @return [Array<GraphQL::BaseType>] Visible and reachable types in the schema
|
82
|
+
def reachable_types
|
83
|
+
@reachable_types ||= reachable_type_set.to_a
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return Boolean True if the type is visible and reachable in the schema
|
87
|
+
def reachable_type?(type_name)
|
88
|
+
type = get_type(type_name)
|
89
|
+
type && reachable_type_set.include?(type)
|
90
|
+
end
|
91
|
+
|
77
92
|
# @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`, if it exists
|
78
93
|
def get_field(parent_type, field_name)
|
79
94
|
|
@@ -93,15 +108,9 @@ module GraphQL
|
|
93
108
|
|
94
109
|
# @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
|
95
110
|
def possible_types(type_defn)
|
96
|
-
@visible_possible_types ||=
|
97
|
-
|
98
|
-
|
99
|
-
pt = all_possible_types[type_defn.graphql_name] || []
|
100
|
-
pt.select { |t| visible_type?(t) }
|
101
|
-
}
|
102
|
-
else
|
103
|
-
read_through { |type_defn| @schema.possible_types(type_defn).select { |t| visible_type?(t) } }
|
104
|
-
end
|
111
|
+
@visible_possible_types ||= read_through { |type_defn|
|
112
|
+
@schema.possible_types(type_defn, @context).select { |t| visible_type?(t) }
|
113
|
+
}
|
105
114
|
@visible_possible_types[type_defn]
|
106
115
|
end
|
107
116
|
|
@@ -209,6 +218,67 @@ module GraphQL
|
|
209
218
|
def read_through
|
210
219
|
Hash.new { |h, k| h[k] = yield(k) }
|
211
220
|
end
|
221
|
+
|
222
|
+
def reachable_type_set
|
223
|
+
return @reachable_type_set if defined?(@reachable_type_set)
|
224
|
+
|
225
|
+
@reachable_type_set = Set.new
|
226
|
+
|
227
|
+
unvisited_types = []
|
228
|
+
['query', 'mutation', 'subscription'].each do |op_name|
|
229
|
+
root_type = root_type_for_operation(op_name)
|
230
|
+
unvisited_types << root_type if root_type
|
231
|
+
end
|
232
|
+
unvisited_types.concat(@schema.introspection_system.types.values)
|
233
|
+
@schema.orphan_types.each do |orphan_type|
|
234
|
+
if get_type(orphan_type.graphql_name)
|
235
|
+
unvisited_types << orphan_type
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
until unvisited_types.empty?
|
240
|
+
type = unvisited_types.pop
|
241
|
+
if @reachable_type_set.add?(type)
|
242
|
+
if type.kind.input_object?
|
243
|
+
# recurse into visible arguments
|
244
|
+
arguments(type).each do |argument|
|
245
|
+
argument_type = argument.type.unwrap
|
246
|
+
unvisited_types << argument_type
|
247
|
+
end
|
248
|
+
elsif type.kind.union?
|
249
|
+
# recurse into visible possible types
|
250
|
+
possible_types(type).each do |possible_type|
|
251
|
+
unvisited_types << possible_type
|
252
|
+
end
|
253
|
+
elsif type.kind.fields?
|
254
|
+
if type.kind.interface?
|
255
|
+
# recurse into visible possible types
|
256
|
+
possible_types(type).each do |possible_type|
|
257
|
+
unvisited_types << possible_type
|
258
|
+
end
|
259
|
+
elsif type.kind.object?
|
260
|
+
# recurse into visible implemented interfaces
|
261
|
+
interfaces(type).each do |interface|
|
262
|
+
unvisited_types << interface
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# recurse into visible fields
|
267
|
+
fields(type).each do |field|
|
268
|
+
field_type = field.type.unwrap
|
269
|
+
unvisited_types << field_type
|
270
|
+
# recurse into visible arguments
|
271
|
+
arguments(field).each do |argument|
|
272
|
+
argument_type = argument.type.unwrap
|
273
|
+
unvisited_types << argument_type
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
@reachable_type_set
|
281
|
+
end
|
212
282
|
end
|
213
283
|
end
|
214
284
|
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,26 +52,37 @@ 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
|
@@ -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.10.0.
|
4
|
+
version: 1.10.0.pre3
|
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
|
@@ -599,6 +599,7 @@ files:
|
|
599
599
|
- lib/graphql/schema/timeout_middleware.rb
|
600
600
|
- lib/graphql/schema/traversal.rb
|
601
601
|
- lib/graphql/schema/type_expression.rb
|
602
|
+
- lib/graphql/schema/type_membership.rb
|
602
603
|
- lib/graphql/schema/union.rb
|
603
604
|
- lib/graphql/schema/unique_within_type.rb
|
604
605
|
- lib/graphql/schema/validation.rb
|