graphql 1.12.15 → 1.12.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/graphql/analysis/ast/field_usage.rb +1 -1
- data/lib/graphql/dataloader/source.rb +20 -0
- data/lib/graphql/dataloader.rb +39 -16
- data/lib/graphql/define/instance_definable.rb +1 -1
- data/lib/graphql/deprecated_dsl.rb +11 -3
- data/lib/graphql/execution/interpreter/runtime.rb +34 -25
- data/lib/graphql/integer_encoding_error.rb +18 -2
- data/lib/graphql/introspection/input_value_type.rb +6 -0
- data/lib/graphql/pagination/connections.rb +5 -0
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/query.rb +4 -0
- data/lib/graphql/schema/argument.rb +71 -28
- data/lib/graphql/schema/field.rb +14 -4
- data/lib/graphql/schema/input_object.rb +5 -9
- data/lib/graphql/schema/member/has_arguments.rb +90 -44
- data/lib/graphql/schema/resolver.rb +24 -59
- data/lib/graphql/schema/subscription.rb +25 -7
- data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
- data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/format_validator.rb +2 -1
- data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/length_validator.rb +5 -3
- data/lib/graphql/schema/validator/numericality_validator.rb +12 -2
- data/lib/graphql/schema/validator.rb +36 -25
- data/lib/graphql/schema.rb +18 -5
- data/lib/graphql/static_validation/base_visitor.rb +3 -0
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +4 -4
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
- data/lib/graphql/static_validation/validation_context.rb +6 -1
- data/lib/graphql/static_validation/validator.rb +15 -12
- data/lib/graphql/string_encoding_error.rb +13 -3
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +7 -1
- data/lib/graphql/subscriptions/event.rb +6 -21
- data/lib/graphql/subscriptions/serialize.rb +1 -1
- data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
- data/lib/graphql/types/big_int.rb +5 -1
- data/lib/graphql/types/int.rb +1 -1
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +15 -11
- data/readme.md +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92aba0a1935a0022b97ea1630cde64884a727ce714fae6a2bca543666c73bde0
|
4
|
+
data.tar.gz: 5db896ad77a52ec4a025f526bab4f3c4b3d18206e009c2d8c3e14dd434f12ae6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2985936af9bba4d0feec8263dd4a6c60c09110e39aba1a97359e4d02900732f02900e30d798074932bb6fe2a5ed0e00441be859bb3035ab0c819ccec3c9a0f66
|
7
|
+
data.tar.gz: e8e961e540b54a5c54d6d78c444eef422bd55264ee8ffc9c3a231f2614c570ee75de5c2888f3b13974304c48f60072e04bb0d0444ea6e1a13f4a37652dbd2f0b
|
@@ -37,7 +37,7 @@ module GraphQL
|
|
37
37
|
|
38
38
|
if argument.definition.type.kind.input_object?
|
39
39
|
extract_deprecated_arguments(argument.value.arguments.argument_values)
|
40
|
-
elsif argument.definition.type.list?
|
40
|
+
elsif argument.definition.type.list? && !argument.value.nil?
|
41
41
|
argument
|
42
42
|
.value
|
43
43
|
.select { |value| value.respond_to?(:arguments) }
|
@@ -89,6 +89,26 @@ module GraphQL
|
|
89
89
|
nil
|
90
90
|
end
|
91
91
|
|
92
|
+
# These arguments are given to `dataloader.with(source_class, ...)`. The object
|
93
|
+
# returned from this method is used to de-duplicate batch loads under the hood
|
94
|
+
# by using it as a Hash key.
|
95
|
+
#
|
96
|
+
# By default, the arguments are all put in an Array. To customize how this source's
|
97
|
+
# batches are merged, override this method to return something else.
|
98
|
+
#
|
99
|
+
# For example, if you pass `ActiveRecord::Relation`s to `.with(...)`, you could override
|
100
|
+
# this method to call `.to_sql` on them, thus merging `.load(...)` calls when they apply
|
101
|
+
# to equivalent relations.
|
102
|
+
#
|
103
|
+
# @param batch_args [Array<Object>]
|
104
|
+
# @param batch_kwargs [Hash]
|
105
|
+
# @return [Object]
|
106
|
+
def self.batch_key_for(*batch_args, **batch_kwargs)
|
107
|
+
[*batch_args, **batch_kwargs]
|
108
|
+
end
|
109
|
+
|
110
|
+
attr_reader :pending_keys
|
111
|
+
|
92
112
|
private
|
93
113
|
|
94
114
|
# Reads and returns the result for the key from the internal cache, or raises an error if the result was an error
|
data/lib/graphql/dataloader.rb
CHANGED
@@ -27,18 +27,20 @@ module GraphQL
|
|
27
27
|
schema.dataloader_class = self
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
-
source.setup(self)
|
39
|
-
h2[batch_parameters] = source
|
40
|
-
}
|
30
|
+
# Call the block with a Dataloader instance,
|
31
|
+
# then run all enqueued jobs and return the result of the block.
|
32
|
+
def self.with_dataloading(&block)
|
33
|
+
dataloader = self.new
|
34
|
+
result = nil
|
35
|
+
dataloader.append_job {
|
36
|
+
result = block.call(dataloader)
|
41
37
|
}
|
38
|
+
dataloader.run
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@source_cache = Hash.new { |h, k| h[k] = {} }
|
42
44
|
@pending_jobs = []
|
43
45
|
end
|
44
46
|
|
@@ -49,16 +51,24 @@ module GraphQL
|
|
49
51
|
# @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
|
50
52
|
# and cached for the lifetime of this {Multiplex}.
|
51
53
|
if RUBY_VERSION < "3"
|
52
|
-
def with(source_class, *
|
53
|
-
|
54
|
+
def with(source_class, *batch_args)
|
55
|
+
batch_key = source_class.batch_key_for(*batch_args)
|
56
|
+
@source_cache[source_class][batch_key] ||= begin
|
57
|
+
source = source_class.new(*batch_args)
|
58
|
+
source.setup(self)
|
59
|
+
source
|
60
|
+
end
|
54
61
|
end
|
55
62
|
else
|
56
63
|
def with(source_class, *batch_args, **batch_kwargs)
|
57
|
-
|
58
|
-
@source_cache[source_class][
|
64
|
+
batch_key = source_class.batch_key_for(*batch_args, **batch_kwargs)
|
65
|
+
@source_cache[source_class][batch_key] ||= begin
|
66
|
+
source = source_class.new(*batch_args, **batch_kwargs)
|
67
|
+
source.setup(self)
|
68
|
+
source
|
69
|
+
end
|
59
70
|
end
|
60
71
|
end
|
61
|
-
|
62
72
|
# Tell the dataloader that this fiber is waiting for data.
|
63
73
|
#
|
64
74
|
# Dataloader will resume the fiber after the requested data has been loaded (by another Fiber).
|
@@ -80,6 +90,16 @@ module GraphQL
|
|
80
90
|
# Use a self-contained queue for the work in the block.
|
81
91
|
def run_isolated
|
82
92
|
prev_queue = @pending_jobs
|
93
|
+
prev_pending_keys = {}
|
94
|
+
@source_cache.each do |source_class, batched_sources|
|
95
|
+
batched_sources.each do |batch_args, batched_source_instance|
|
96
|
+
if batched_source_instance.pending?
|
97
|
+
prev_pending_keys[batched_source_instance] = batched_source_instance.pending_keys.dup
|
98
|
+
batched_source_instance.pending_keys.clear
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
83
103
|
@pending_jobs = []
|
84
104
|
res = nil
|
85
105
|
# Make sure the block is inside a Fiber, so it can `Fiber.yield`
|
@@ -90,6 +110,9 @@ module GraphQL
|
|
90
110
|
res
|
91
111
|
ensure
|
92
112
|
@pending_jobs = prev_queue
|
113
|
+
prev_pending_keys.each do |source_instance, pending_keys|
|
114
|
+
source_instance.pending_keys.concat(pending_keys)
|
115
|
+
end
|
93
116
|
end
|
94
117
|
|
95
118
|
# @api private Move along, move along
|
@@ -76,7 +76,7 @@ ERR
|
|
76
76
|
# Apply definition from `define(...)` kwargs
|
77
77
|
defn.define_keywords.each do |keyword, value|
|
78
78
|
# Don't splat string hashes, which blows up on Rubies before 2.7
|
79
|
-
if value.is_a?(Hash) && value.each_key.all? { |k| k.is_a?(Symbol) }
|
79
|
+
if value.is_a?(Hash) && !value.empty? && value.each_key.all? { |k| k.is_a?(Symbol) }
|
80
80
|
defn_proxy.public_send(keyword, **value)
|
81
81
|
else
|
82
82
|
defn_proxy.public_send(keyword, value)
|
@@ -38,9 +38,17 @@ module GraphQL
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
if defined?(::Refinement) && Refinement.private_method_defined?(:import_methods)
|
42
|
+
TYPE_CLASSES.each do |type_class|
|
43
|
+
refine type_class.singleton_class do
|
44
|
+
import_methods Methods
|
45
|
+
end
|
46
|
+
end
|
47
|
+
else
|
48
|
+
TYPE_CLASSES.each do |type_class|
|
49
|
+
refine type_class.singleton_class do
|
50
|
+
include Methods
|
51
|
+
end
|
44
52
|
end
|
45
53
|
end
|
46
54
|
end
|
@@ -10,7 +10,19 @@ module GraphQL
|
|
10
10
|
class Runtime
|
11
11
|
|
12
12
|
module GraphQLResult
|
13
|
-
|
13
|
+
def initialize(result_name, parent_result)
|
14
|
+
@graphql_parent = parent_result
|
15
|
+
if parent_result && parent_result.graphql_dead
|
16
|
+
@graphql_dead = true
|
17
|
+
end
|
18
|
+
@graphql_result_name = result_name
|
19
|
+
# Jump through some hoops to avoid creating this duplicate storage if at all possible.
|
20
|
+
@graphql_metadata = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_accessor :graphql_dead
|
24
|
+
attr_reader :graphql_parent, :graphql_result_name
|
25
|
+
|
14
26
|
# Although these are used by only one of the Result classes,
|
15
27
|
# it's handy to have the methods implemented on both (even though they just return `nil`)
|
16
28
|
# because it makes it easy to check if anything is assigned.
|
@@ -24,9 +36,8 @@ module GraphQL
|
|
24
36
|
end
|
25
37
|
|
26
38
|
class GraphQLResultHash
|
27
|
-
def initialize
|
28
|
-
|
29
|
-
@graphql_metadata = nil
|
39
|
+
def initialize(_result_name, _parent_result)
|
40
|
+
super
|
30
41
|
@graphql_result_data = {}
|
31
42
|
end
|
32
43
|
|
@@ -86,10 +97,8 @@ module GraphQL
|
|
86
97
|
class GraphQLResultArray
|
87
98
|
include GraphQLResult
|
88
99
|
|
89
|
-
def initialize
|
90
|
-
|
91
|
-
# but it will require some work to keep it up-to-date if it's created.
|
92
|
-
@graphql_metadata = nil
|
100
|
+
def initialize(_result_name, _parent_result)
|
101
|
+
super
|
93
102
|
@graphql_result_data = []
|
94
103
|
end
|
95
104
|
|
@@ -146,7 +155,7 @@ module GraphQL
|
|
146
155
|
@context = query.context
|
147
156
|
@multiplex_context = query.multiplex.context
|
148
157
|
@interpreter_context = @context.namespace(:interpreter)
|
149
|
-
@response = GraphQLResultHash.new
|
158
|
+
@response = GraphQLResultHash.new(nil, nil)
|
150
159
|
# Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
|
151
160
|
@runtime_directive_names = []
|
152
161
|
noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
|
@@ -208,7 +217,7 @@ module GraphQL
|
|
208
217
|
# directly evaluated and the results can be written right into the main response hash.
|
209
218
|
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
210
219
|
if is_selection_array
|
211
|
-
selection_response = GraphQLResultHash.new
|
220
|
+
selection_response = GraphQLResultHash.new(nil, nil)
|
212
221
|
final_response = @response
|
213
222
|
else
|
214
223
|
selection_response = @response
|
@@ -227,6 +236,7 @@ module GraphQL
|
|
227
236
|
selections,
|
228
237
|
selection_response,
|
229
238
|
final_response,
|
239
|
+
nil,
|
230
240
|
)
|
231
241
|
end
|
232
242
|
}
|
@@ -338,7 +348,7 @@ module GraphQL
|
|
338
348
|
NO_ARGS = {}.freeze
|
339
349
|
|
340
350
|
# @return [void]
|
341
|
-
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result) # rubocop:disable Metrics/ParameterLists
|
351
|
+
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
342
352
|
set_all_interpreter_context(owner_object, nil, nil, path)
|
343
353
|
|
344
354
|
finished_jobs = 0
|
@@ -346,7 +356,7 @@ module GraphQL
|
|
346
356
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
347
357
|
@dataloader.append_job {
|
348
358
|
evaluate_selection(
|
349
|
-
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result
|
359
|
+
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result, parent_object
|
350
360
|
)
|
351
361
|
finished_jobs += 1
|
352
362
|
if target_result && finished_jobs == enqueued_jobs
|
@@ -361,7 +371,8 @@ module GraphQL
|
|
361
371
|
attr_reader :progress_path
|
362
372
|
|
363
373
|
# @return [void]
|
364
|
-
def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field, selections_result) # rubocop:disable Metrics/ParameterLists
|
374
|
+
def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
375
|
+
return if dead_result?(selections_result)
|
365
376
|
# As a performance optimization, the hash key will be a `Node` if
|
366
377
|
# there's only one selection of the field. But if there are multiple
|
367
378
|
# selections of the field, it will be an Array of nodes
|
@@ -411,16 +422,16 @@ module GraphQL
|
|
411
422
|
total_args_count = field_defn.arguments.size
|
412
423
|
if total_args_count == 0
|
413
424
|
kwarg_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
414
|
-
evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result)
|
425
|
+
evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
|
415
426
|
else
|
416
427
|
# TODO remove all arguments(...) usages?
|
417
428
|
@query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
|
418
|
-
evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result)
|
429
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
|
419
430
|
end
|
420
431
|
end
|
421
432
|
end
|
422
433
|
|
423
|
-
def evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
434
|
+
def evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selection_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
424
435
|
context.scoped_context = scoped_context
|
425
436
|
return_type = field_defn.type
|
426
437
|
after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
|
@@ -462,6 +473,8 @@ module GraphQL
|
|
462
473
|
# This is used by `__typename` in order to support the legacy runtime,
|
463
474
|
# but it has no use here (and it's always `nil`).
|
464
475
|
# Stop adding it here to avoid the overhead of `.merge_extras` below.
|
476
|
+
when :parent
|
477
|
+
extra_args[:parent] = parent_object
|
465
478
|
else
|
466
479
|
extra_args[extra] = field_defn.fetch_extra(extra, context)
|
467
480
|
end
|
@@ -693,9 +706,7 @@ module GraphQL
|
|
693
706
|
after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
|
694
707
|
continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
|
695
708
|
if HALT != continue_value
|
696
|
-
response_hash = GraphQLResultHash.new
|
697
|
-
response_hash.graphql_parent = selection_result
|
698
|
-
response_hash.graphql_result_name = result_name
|
709
|
+
response_hash = GraphQLResultHash.new(result_name, selection_result)
|
699
710
|
set_result(selection_result, result_name, response_hash)
|
700
711
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
701
712
|
# There are two possibilities for `gathered_selections`:
|
@@ -708,9 +719,7 @@ module GraphQL
|
|
708
719
|
# (Technically, it's possible that one of those entries _doesn't_ require isolation.)
|
709
720
|
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
710
721
|
if is_selection_array
|
711
|
-
this_result = GraphQLResultHash.new
|
712
|
-
this_result.graphql_parent = selection_result
|
713
|
-
this_result.graphql_result_name = result_name
|
722
|
+
this_result = GraphQLResultHash.new(result_name, selection_result)
|
714
723
|
final_result = response_hash
|
715
724
|
else
|
716
725
|
this_result = response_hash
|
@@ -727,6 +736,7 @@ module GraphQL
|
|
727
736
|
selections,
|
728
737
|
this_result,
|
729
738
|
final_result,
|
739
|
+
owner_object.object,
|
730
740
|
)
|
731
741
|
this_result
|
732
742
|
end
|
@@ -735,16 +745,15 @@ module GraphQL
|
|
735
745
|
end
|
736
746
|
when "LIST"
|
737
747
|
inner_type = current_type.of_type
|
738
|
-
response_list = GraphQLResultArray.new
|
748
|
+
response_list = GraphQLResultArray.new(result_name, selection_result)
|
739
749
|
response_list.graphql_non_null_list_items = inner_type.non_null?
|
740
|
-
response_list.graphql_parent = selection_result
|
741
|
-
response_list.graphql_result_name = result_name
|
742
750
|
set_result(selection_result, result_name, response_list)
|
743
751
|
|
744
752
|
idx = 0
|
745
753
|
scoped_context = context.scoped_context
|
746
754
|
begin
|
747
755
|
value.each do |inner_value|
|
756
|
+
break if dead_result?(response_list)
|
748
757
|
next_path = path.dup
|
749
758
|
next_path << idx
|
750
759
|
this_idx = idx
|
@@ -12,9 +12,25 @@ module GraphQL
|
|
12
12
|
# The value which couldn't be encoded
|
13
13
|
attr_reader :integer_value
|
14
14
|
|
15
|
-
|
15
|
+
# @return [GraphQL::Schema::Field] The field that returned a too-big integer
|
16
|
+
attr_reader :field
|
17
|
+
|
18
|
+
# @return [Array<String, Integer>] Where the field appeared in the GraphQL response
|
19
|
+
attr_reader :path
|
20
|
+
|
21
|
+
def initialize(value, context:)
|
16
22
|
@integer_value = value
|
17
|
-
|
23
|
+
@field = context[:current_field]
|
24
|
+
@path = context[:current_path]
|
25
|
+
message = "Integer out of bounds: #{value}".dup
|
26
|
+
if @path
|
27
|
+
message << " @ #{@path.join(".")}"
|
28
|
+
end
|
29
|
+
if @field
|
30
|
+
message << " (#{@field.path})"
|
31
|
+
end
|
32
|
+
message << ". Consider using ID or GraphQL::Types::BigInt instead."
|
33
|
+
super(message)
|
18
34
|
end
|
19
35
|
end
|
20
36
|
end
|
@@ -23,6 +23,12 @@ module GraphQL
|
|
23
23
|
if value.nil?
|
24
24
|
'null'
|
25
25
|
else
|
26
|
+
if (@object.type.kind.list? || (@object.type.kind.non_null? && @object.type.of_type.kind.list?)) && !value.respond_to?(:map)
|
27
|
+
# This is a bit odd -- we expect the default value to be an application-style value, so we use coerce result below.
|
28
|
+
# But coerce_result doesn't wrap single-item lists, which are valid inputs to list types.
|
29
|
+
# So, apply that wrapper here if needed.
|
30
|
+
value = [value]
|
31
|
+
end
|
26
32
|
coerced_default_value = @object.type.coerce_result(value, @context)
|
27
33
|
serialize_default_value(coerced_default_value, @object.type)
|
28
34
|
end
|
@@ -130,6 +130,11 @@ module GraphQL
|
|
130
130
|
if defined?(Mongoid::Association::Referenced::HasMany::Targets::Enumerable)
|
131
131
|
add(Mongoid::Association::Referenced::HasMany::Targets::Enumerable, Pagination::MongoidRelationConnection)
|
132
132
|
end
|
133
|
+
|
134
|
+
# Mongoid 7.3+
|
135
|
+
if defined?(Mongoid::Association::Referenced::HasMany::Enumerable)
|
136
|
+
add(Mongoid::Association::Referenced::HasMany::Enumerable, Pagination::MongoidRelationConnection)
|
137
|
+
end
|
133
138
|
end
|
134
139
|
end
|
135
140
|
end
|
@@ -72,7 +72,7 @@ module GraphQL
|
|
72
72
|
elsif @operation_name_error
|
73
73
|
@validation_errors << @operation_name_error
|
74
74
|
else
|
75
|
-
validation_result = @schema.static_validator.validate(@query, validate: @validate, timeout: @schema.validate_timeout)
|
75
|
+
validation_result = @schema.static_validator.validate(@query, validate: @validate, timeout: @schema.validate_timeout, max_errors: @schema.validate_max_errors)
|
76
76
|
@validation_errors.concat(validation_result[:errors])
|
77
77
|
@internal_representation = validation_result[:irep]
|
78
78
|
|
data/lib/graphql/query.rb
CHANGED
@@ -117,6 +117,10 @@ module GraphQL
|
|
117
117
|
raise ArgumentError, "Query should only be provided a query string or a document, not both."
|
118
118
|
end
|
119
119
|
|
120
|
+
if @query_string && !@query_string.is_a?(String)
|
121
|
+
raise ArgumentError, "Query string argument should be a String, got #{@query_string.class.name} instead."
|
122
|
+
end
|
123
|
+
|
120
124
|
# A two-layer cache of type resolution:
|
121
125
|
# { abstract_type => { value => resolved_type } }
|
122
126
|
@resolved_types_cache = Hash.new do |h1, k1|
|
@@ -151,7 +151,7 @@ module GraphQL
|
|
151
151
|
input_obj_arg = input_obj_arg.type_class
|
152
152
|
# TODO: this skips input objects whose values were alread replaced with application objects.
|
153
153
|
# See: https://github.com/rmosolgo/graphql-ruby/issues/2633
|
154
|
-
if value.
|
154
|
+
if value.is_a?(InputObject) && value.key?(input_obj_arg.keyword) && !input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
|
155
155
|
return false
|
156
156
|
end
|
157
157
|
end
|
@@ -260,45 +260,88 @@ module GraphQL
|
|
260
260
|
type.coerce_input(value, context)
|
261
261
|
end
|
262
262
|
|
263
|
-
#
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
context.
|
268
|
-
|
269
|
-
context.query.with_error_handling do
|
270
|
-
owner.load_application_object(self, loads, coerced_value, context)
|
263
|
+
# If this isn't lazy, then the block returns eagerly and assigns the result here
|
264
|
+
# If it _is_ lazy, then we write the lazy to the hash, then update it later
|
265
|
+
argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
|
266
|
+
if loads && !from_resolver?
|
267
|
+
loaded_value = context.query.with_error_handling do
|
268
|
+
load_and_authorize_value(owner, coerced_value, context)
|
271
269
|
end
|
272
270
|
end
|
273
|
-
end
|
274
271
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
272
|
+
maybe_loaded_value = loaded_value || resolved_coerced_value
|
273
|
+
context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
|
274
|
+
owner.validate_directive_argument(self, resolved_loaded_value)
|
275
|
+
prepared_value = context.schema.error_handler.with_error_handling(context) do
|
276
|
+
prepare_value(parent_object, resolved_loaded_value, context: context)
|
277
|
+
end
|
280
278
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
279
|
+
# TODO code smell to access such a deeply-nested constant in a distant module
|
280
|
+
argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
|
281
|
+
value: prepared_value,
|
282
|
+
definition: self,
|
283
|
+
default_used: default_used,
|
284
|
+
)
|
287
285
|
end
|
286
|
+
end
|
287
|
+
end
|
288
288
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
289
|
+
def load_and_authorize_value(load_method_owner, coerced_value, context)
|
290
|
+
if coerced_value.nil?
|
291
|
+
return nil
|
292
|
+
end
|
293
|
+
arg_load_method = "load_#{keyword}"
|
294
|
+
if load_method_owner.respond_to?(arg_load_method)
|
295
|
+
custom_loaded_value = if load_method_owner.is_a?(Class)
|
296
|
+
load_method_owner.public_send(arg_load_method, coerced_value, context)
|
297
|
+
else
|
298
|
+
load_method_owner.public_send(arg_load_method, coerced_value)
|
299
|
+
end
|
300
|
+
context.schema.after_lazy(custom_loaded_value) do |custom_value|
|
301
|
+
if loads
|
302
|
+
if type.list?
|
303
|
+
loaded_values = custom_value.each_with_index.map { |custom_val, idx|
|
304
|
+
id = coerced_value[idx]
|
305
|
+
load_method_owner.authorize_application_object(self, id, context, custom_val)
|
306
|
+
}
|
307
|
+
context.schema.after_any_lazies(loaded_values, &:itself)
|
308
|
+
else
|
309
|
+
load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value)
|
310
|
+
end
|
311
|
+
else
|
312
|
+
custom_value
|
313
|
+
end
|
314
|
+
end
|
315
|
+
elsif loads
|
316
|
+
if type.list?
|
317
|
+
loaded_values = coerced_value.map { |val| load_method_owner.load_and_authorize_application_object(self, val, context) }
|
318
|
+
context.schema.after_any_lazies(loaded_values, &:itself)
|
319
|
+
else
|
320
|
+
load_method_owner.load_and_authorize_application_object(self, coerced_value, context)
|
321
|
+
end
|
322
|
+
else
|
323
|
+
coerced_value
|
295
324
|
end
|
296
325
|
end
|
297
326
|
|
298
327
|
# @api private
|
299
328
|
def validate_default_value
|
300
329
|
coerced_default_value = begin
|
301
|
-
type
|
330
|
+
# This is weird, but we should accept single-item default values for list-type arguments.
|
331
|
+
# If we used `coerce_isolated_input` below, it would do this for us, but it's not really
|
332
|
+
# the right thing here because we expect default values in application format (Ruby values)
|
333
|
+
# not GraphQL format (scalar values).
|
334
|
+
#
|
335
|
+
# But I don't think Schema::List#coerce_result should apply wrapping to single-item lists.
|
336
|
+
prepped_default_value = if default_value.nil?
|
337
|
+
nil
|
338
|
+
elsif (type.kind.list? || (type.kind.non_null? && type.of_type.list?)) && !default_value.respond_to?(:map)
|
339
|
+
[default_value]
|
340
|
+
else
|
341
|
+
default_value
|
342
|
+
end
|
343
|
+
|
344
|
+
type.coerce_isolated_result(prepped_default_value) unless prepped_default_value.nil?
|
302
345
|
rescue GraphQL::Schema::Enum::UnresolvedValueError
|
303
346
|
# It raises this, which is helpful at runtime, but not here...
|
304
347
|
default_value
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -122,6 +122,9 @@ module GraphQL
|
|
122
122
|
else
|
123
123
|
kwargs[:type] = type
|
124
124
|
end
|
125
|
+
if type.is_a?(Class) && type < GraphQL::Schema::Mutation
|
126
|
+
raise ArgumentError, "Use `field #{name.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead"
|
127
|
+
end
|
125
128
|
end
|
126
129
|
new(**kwargs, &block)
|
127
130
|
end
|
@@ -510,6 +513,7 @@ module GraphQL
|
|
510
513
|
field_defn
|
511
514
|
end
|
512
515
|
|
516
|
+
class MissingReturnTypeError < GraphQL::Error; end
|
513
517
|
attr_writer :type
|
514
518
|
|
515
519
|
def type
|
@@ -517,14 +521,21 @@ module GraphQL
|
|
517
521
|
Member::BuildType.parse_type(@function.type, null: false)
|
518
522
|
elsif @field
|
519
523
|
Member::BuildType.parse_type(@field.type, null: false)
|
524
|
+
elsif @return_type_expr.nil?
|
525
|
+
# Not enough info to determine type
|
526
|
+
message = "Can't determine the return type for #{self.path}"
|
527
|
+
if @resolver_class
|
528
|
+
message += " (it has `resolver: #{@resolver_class}`, consider configuration a `type ...` for that class)"
|
529
|
+
end
|
530
|
+
raise MissingReturnTypeError, message
|
520
531
|
else
|
521
532
|
Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
|
522
533
|
end
|
523
|
-
rescue GraphQL::Schema::InvalidDocumentError => err
|
534
|
+
rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
|
524
535
|
# Let this propagate up
|
525
536
|
raise err
|
526
537
|
rescue StandardError => err
|
527
|
-
raise
|
538
|
+
raise MissingReturnTypeError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace
|
528
539
|
end
|
529
540
|
|
530
541
|
def visible?(context)
|
@@ -608,8 +619,7 @@ module GraphQL
|
|
608
619
|
if is_authorized
|
609
620
|
public_send_field(object, args, ctx)
|
610
621
|
else
|
611
|
-
|
612
|
-
ctx.schema.unauthorized_field(err)
|
622
|
+
raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
|
613
623
|
end
|
614
624
|
end
|
615
625
|
rescue GraphQL::UnauthorizedFieldError => err
|
@@ -40,11 +40,7 @@ module GraphQL
|
|
40
40
|
# With the interpreter, it's done during `coerce_arguments`
|
41
41
|
if loads && !arg_defn.from_resolver? && !context.interpreter?
|
42
42
|
value = @ruby_style_hash[ruby_kwargs_key]
|
43
|
-
loaded_value =
|
44
|
-
value.map { |val| load_application_object(arg_defn, loads, val, context) }
|
45
|
-
else
|
46
|
-
load_application_object(arg_defn, loads, value, context)
|
47
|
-
end
|
43
|
+
loaded_value = arg_defn.load_and_authorize_value(self, value, context)
|
48
44
|
maybe_lazies << context.schema.after_lazy(loaded_value) do |loaded_value|
|
49
45
|
overwrite_argument(ruby_kwargs_key, loaded_value)
|
50
46
|
end
|
@@ -71,11 +67,11 @@ module GraphQL
|
|
71
67
|
end
|
72
68
|
|
73
69
|
def prepare
|
74
|
-
if context
|
75
|
-
context.schema.after_any_lazies(@maybe_lazies) do
|
76
|
-
object = context[:current_object]
|
70
|
+
if @context
|
71
|
+
@context.schema.after_any_lazies(@maybe_lazies) do
|
72
|
+
object = @context[:current_object]
|
77
73
|
# Pass this object's class with `as` so that messages are rendered correctly from inherited validators
|
78
|
-
Schema::Validator.validate!(self.class.validators, object, context, @ruby_style_hash, as: self.class)
|
74
|
+
Schema::Validator.validate!(self.class.validators, object, @context, @ruby_style_hash, as: self.class)
|
79
75
|
self
|
80
76
|
end
|
81
77
|
else
|