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