graphql 1.12.13 → 1.12.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +24 -1
- data/lib/graphql/backtrace/tracer.rb +6 -3
- data/lib/graphql/dataloader/source.rb +18 -0
- data/lib/graphql/dataloader.rb +26 -16
- data/lib/graphql/define/instance_definable.rb +1 -1
- data/lib/graphql/execution/interpreter/runtime.rb +34 -25
- data/lib/graphql/introspection/input_value_type.rb +6 -0
- data/lib/graphql/pagination/connections.rb +5 -0
- data/lib/graphql/query.rb +5 -1
- data/lib/graphql/schema/argument.rb +16 -2
- data/lib/graphql/schema/build_from_definition.rb +8 -2
- data/lib/graphql/schema/input_object.rb +4 -4
- data/lib/graphql/schema/member/build_type.rb +1 -0
- data/lib/graphql/schema/resolver.rb +4 -2
- data/lib/graphql/schema/subscription.rb +22 -4
- data/lib/graphql/schema/validator/numericality_validator.rb +5 -1
- data/lib/graphql/schema.rb +8 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -1
- data/lib/graphql/subscriptions/event.rb +4 -20
- data/lib/graphql/types/big_int.rb +5 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +15 -11
- data/readme.md +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c9089e4578454f473996553a4771e4086e51b2b2db17fc31a579ab28019bd39
|
4
|
+
data.tar.gz: f45a70c81394f35c86d1610491f106e39899bc2c927a6a6d13e9e6533bc3bc85
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7231b5e00a336439d15d469a383cd7e211e305961d4a4b431e8a97f5e483819bbd87de3d7cfffc9b6677b8175f700e012cd9ee4a0c92fa47005455a14893d3c
|
7
|
+
data.tar.gz: 98495c2b24d65ef90d7f5e1036956349ea79d03ea192de6ed06e517657f49b4e0e9c748c8a28d54aac24e96b42f52eea5d4cbfbca801a4389b1515804296831a
|
@@ -7,6 +7,7 @@ module GraphQL
|
|
7
7
|
super
|
8
8
|
@used_fields = Set.new
|
9
9
|
@used_deprecated_fields = Set.new
|
10
|
+
@used_deprecated_arguments = Set.new
|
10
11
|
end
|
11
12
|
|
12
13
|
def on_leave_field(node, parent, visitor)
|
@@ -14,14 +15,36 @@ module GraphQL
|
|
14
15
|
field = "#{visitor.parent_type_definition.graphql_name}.#{field_defn.graphql_name}"
|
15
16
|
@used_fields << field
|
16
17
|
@used_deprecated_fields << field if field_defn.deprecation_reason
|
18
|
+
|
19
|
+
extract_deprecated_arguments(visitor.query.arguments_for(node, visitor.field_definition).argument_values)
|
17
20
|
end
|
18
21
|
|
19
22
|
def result
|
20
23
|
{
|
21
24
|
used_fields: @used_fields.to_a,
|
22
|
-
used_deprecated_fields: @used_deprecated_fields.to_a
|
25
|
+
used_deprecated_fields: @used_deprecated_fields.to_a,
|
26
|
+
used_deprecated_arguments: @used_deprecated_arguments.to_a,
|
23
27
|
}
|
24
28
|
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def extract_deprecated_arguments(argument_values)
|
33
|
+
argument_values.each_pair do |_argument_name, argument|
|
34
|
+
if argument.definition.deprecation_reason
|
35
|
+
@used_deprecated_arguments << argument.definition.path
|
36
|
+
end
|
37
|
+
|
38
|
+
if argument.definition.type.kind.input_object?
|
39
|
+
extract_deprecated_arguments(argument.value.arguments.argument_values)
|
40
|
+
elsif argument.definition.type.list?
|
41
|
+
argument
|
42
|
+
.value
|
43
|
+
.select { |value| value.respond_to?(:arguments) }
|
44
|
+
.each { |value| extract_deprecated_arguments(value.arguments.argument_values) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
25
48
|
end
|
26
49
|
end
|
27
50
|
end
|
@@ -15,10 +15,13 @@ module GraphQL
|
|
15
15
|
# No query context yet
|
16
16
|
nil
|
17
17
|
when "validate", "analyze_query", "execute_query", "execute_query_lazy"
|
18
|
-
query = metadata[:query] || metadata[:queries].first
|
19
18
|
push_key = []
|
20
|
-
|
21
|
-
|
19
|
+
if (query = metadata[:query]) || ((queries = metadata[:queries]) && (query = queries.first))
|
20
|
+
push_data = query
|
21
|
+
multiplex = query.multiplex
|
22
|
+
elsif (multiplex = metadata[:multiplex])
|
23
|
+
push_data = multiplex.queries.first
|
24
|
+
end
|
22
25
|
when "execute_field", "execute_field_lazy"
|
23
26
|
query = metadata[:query] || raise(ArgumentError, "Add `legacy: true` to use GraphQL::Backtrace without the interpreter runtime.")
|
24
27
|
multiplex = query.multiplex
|
@@ -89,6 +89,24 @@ 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
|
+
|
92
110
|
private
|
93
111
|
|
94
112
|
# 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).
|
@@ -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)
|
@@ -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
|
@@ -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
|
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|
|
@@ -270,7 +274,7 @@ module GraphQL
|
|
270
274
|
# @return [String, nil] Returns nil if the query is invalid.
|
271
275
|
def sanitized_query_string(inline_variables: true)
|
272
276
|
with_prepared_ast {
|
273
|
-
|
277
|
+
schema.sanitized_printer.new(self, inline_variables: inline_variables).sanitized_query_string
|
274
278
|
}
|
275
279
|
end
|
276
280
|
|
@@ -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
|
@@ -298,7 +298,21 @@ module GraphQL
|
|
298
298
|
# @api private
|
299
299
|
def validate_default_value
|
300
300
|
coerced_default_value = begin
|
301
|
-
type
|
301
|
+
# This is weird, but we should accept single-item default values for list-type arguments.
|
302
|
+
# If we used `coerce_isolated_input` below, it would do this for us, but it's not really
|
303
|
+
# the right thing here because we expect default values in application format (Ruby values)
|
304
|
+
# not GraphQL format (scalar values).
|
305
|
+
#
|
306
|
+
# But I don't think Schema::List#coerce_result should apply wrapping to single-item lists.
|
307
|
+
prepped_default_value = if default_value.nil?
|
308
|
+
nil
|
309
|
+
elsif (type.kind.list? || (type.kind.non_null? && type.of_type.list?)) && !default_value.respond_to?(:map)
|
310
|
+
[default_value]
|
311
|
+
else
|
312
|
+
default_value
|
313
|
+
end
|
314
|
+
|
315
|
+
type.coerce_isolated_result(prepped_default_value) unless prepped_default_value.nil?
|
302
316
|
rescue GraphQL::Schema::Enum::UnresolvedValueError
|
303
317
|
# It raises this, which is helpful at runtime, but not here...
|
304
318
|
default_value
|
@@ -47,10 +47,16 @@ module GraphQL
|
|
47
47
|
# _while_ building the schema.
|
48
48
|
# It will dig for a type if it encounters a custom type. This could be a problem if there are cycles.
|
49
49
|
directive_type_resolver = nil
|
50
|
-
directive_type_resolver = build_resolve_type(
|
50
|
+
directive_type_resolver = build_resolve_type(types, directives, ->(type_name) {
|
51
51
|
types[type_name] ||= begin
|
52
52
|
defn = document.definitions.find { |d| d.respond_to?(:name) && d.name == type_name }
|
53
|
-
|
53
|
+
if defn
|
54
|
+
build_definition_from_node(defn, directive_type_resolver, default_resolve)
|
55
|
+
elsif (built_in_defn = GraphQL::Schema::BUILT_IN_TYPES[type_name])
|
56
|
+
built_in_defn
|
57
|
+
else
|
58
|
+
raise "No definition for #{type_name.inspect} found in schema document or built-in types. Add a definition for it or remove it."
|
59
|
+
end
|
54
60
|
end
|
55
61
|
})
|
56
62
|
|
@@ -71,11 +71,11 @@ module GraphQL
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def prepare
|
74
|
-
if context
|
75
|
-
context.schema.after_any_lazies(@maybe_lazies) do
|
76
|
-
object = context[:current_object]
|
74
|
+
if @context
|
75
|
+
@context.schema.after_any_lazies(@maybe_lazies) do
|
76
|
+
object = @context[:current_object]
|
77
77
|
# 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)
|
78
|
+
Schema::Validator.validate!(self.class.validators, object, @context, @ruby_style_hash, as: self.class)
|
79
79
|
self
|
80
80
|
end
|
81
81
|
else
|
@@ -218,8 +218,10 @@ module GraphQL
|
|
218
218
|
own_extras + (superclass.respond_to?(:extras) ? superclass.extras : [])
|
219
219
|
end
|
220
220
|
|
221
|
-
#
|
222
|
-
#
|
221
|
+
# If `true` (default), then the return type for this resolver will be nullable.
|
222
|
+
# If `false`, then the return type is non-null.
|
223
|
+
#
|
224
|
+
# @see #type which sets the return type of this field and accepts a `null:` option
|
223
225
|
# @param allow_null [Boolean] Whether or not the response can be null
|
224
226
|
def null(allow_null = nil)
|
225
227
|
if !allow_null.nil?
|
@@ -58,11 +58,9 @@ module GraphQL
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
#
|
61
|
+
# The default implementation returns nothing on subscribe.
|
62
62
|
# Override it to return an object or
|
63
|
-
# `:no_response` to return nothing.
|
64
|
-
#
|
65
|
-
# The default is `:no_response`.
|
63
|
+
# `:no_response` to (explicitly) return nothing.
|
66
64
|
def subscribe(args = {})
|
67
65
|
:no_response
|
68
66
|
end
|
@@ -116,6 +114,26 @@ module GraphQL
|
|
116
114
|
end
|
117
115
|
end
|
118
116
|
|
117
|
+
# This is called during initial subscription to get a "name" for this subscription.
|
118
|
+
# Later, when `.trigger` is called, this will be called again to build another "name".
|
119
|
+
# Any subscribers with matching topic will begin the update flow.
|
120
|
+
#
|
121
|
+
# The default implementation creates a string using the field name, subscription scope, and argument keys and values.
|
122
|
+
# In that implementation, only `.trigger` calls with _exact matches_ result in updates to subscribers.
|
123
|
+
#
|
124
|
+
# To implement a filtered stream-type subscription flow, override this method to return a string with field name and subscription scope.
|
125
|
+
# Then, implement {#update} to compare its arguments to the current `object` and return `:no_update` when an
|
126
|
+
# update should be filtered out.
|
127
|
+
#
|
128
|
+
# @see {#update} for how to skip updates when an event comes with a matching topic.
|
129
|
+
# @param arguments [Hash<String => Object>] The arguments for this topic, in GraphQL-style (camelized strings)
|
130
|
+
# @param field [GraphQL::Schema::Field]
|
131
|
+
# @param scope [Object, nil] A value corresponding to `.trigger(... scope:)` (for updates) or the `subscription_scope` found in `context` (for initial subscriptions).
|
132
|
+
# @return [String] An identifier corresponding to a stream of updates
|
133
|
+
def self.topic_for(arguments:, field:, scope:)
|
134
|
+
Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments])
|
135
|
+
end
|
136
|
+
|
119
137
|
# Overriding Resolver#field_options to include subscription_scope
|
120
138
|
def self.field_options
|
121
139
|
super.merge(
|
@@ -24,12 +24,13 @@ module GraphQL
|
|
24
24
|
# @param other_than [Integer]
|
25
25
|
# @param odd [Boolean]
|
26
26
|
# @param even [Boolean]
|
27
|
+
# @param within [Range]
|
27
28
|
# @param message [String] used for all validation failures
|
28
29
|
def initialize(
|
29
30
|
greater_than: nil, greater_than_or_equal_to: nil,
|
30
31
|
less_than: nil, less_than_or_equal_to: nil,
|
31
32
|
equal_to: nil, other_than: nil,
|
32
|
-
odd: nil, even: nil,
|
33
|
+
odd: nil, even: nil, within: nil,
|
33
34
|
message: "%{validated} must be %{comparison} %{target}",
|
34
35
|
**default_options
|
35
36
|
)
|
@@ -42,6 +43,7 @@ module GraphQL
|
|
42
43
|
@other_than = other_than
|
43
44
|
@odd = odd
|
44
45
|
@even = even
|
46
|
+
@within = within
|
45
47
|
@message = message
|
46
48
|
super(**default_options)
|
47
49
|
end
|
@@ -63,6 +65,8 @@ module GraphQL
|
|
63
65
|
(partial_format(@message, { comparison: "even", target: "" })).strip
|
64
66
|
elsif @odd && !value.odd?
|
65
67
|
(partial_format(@message, { comparison: "odd", target: "" })).strip
|
68
|
+
elsif @within && !@within.include?(value)
|
69
|
+
partial_format(@message, { comparison: "within", target: @within })
|
66
70
|
end
|
67
71
|
end
|
68
72
|
end
|
data/lib/graphql/schema.rb
CHANGED
@@ -1631,6 +1631,14 @@ module GraphQL
|
|
1631
1631
|
find_inherited_value(:multiplex_analyzers, EMPTY_ARRAY) + own_multiplex_analyzers
|
1632
1632
|
end
|
1633
1633
|
|
1634
|
+
def sanitized_printer(new_sanitized_printer = nil)
|
1635
|
+
if new_sanitized_printer
|
1636
|
+
@own_sanitized_printer = new_sanitized_printer
|
1637
|
+
else
|
1638
|
+
@own_sanitized_printer || GraphQL::Language::SanitizedPrinter
|
1639
|
+
end
|
1640
|
+
end
|
1641
|
+
|
1634
1642
|
# Execute a query on itself.
|
1635
1643
|
# @see {Query#initialize} for arguments.
|
1636
1644
|
# @return [Hash] query result, ready to be serialized as JSON
|
@@ -95,6 +95,14 @@ module GraphQL
|
|
95
95
|
@action_cable = action_cable
|
96
96
|
@action_cable_coder = action_cable_coder
|
97
97
|
@serializer = serializer
|
98
|
+
@serialize_with_context = case @serializer.method(:load).arity
|
99
|
+
when 1
|
100
|
+
false
|
101
|
+
when 2
|
102
|
+
true
|
103
|
+
else
|
104
|
+
raise ArgumentError, "#{@serializer} must repond to `.load` accepting one or two arguments"
|
105
|
+
end
|
98
106
|
@transmit_ns = namespace
|
99
107
|
super
|
100
108
|
end
|
@@ -154,7 +162,7 @@ module GraphQL
|
|
154
162
|
# so just run it once, then deliver the result to every subscriber
|
155
163
|
first_event = events.first
|
156
164
|
first_subscription_id = first_event.context.fetch(:subscription_id)
|
157
|
-
object ||=
|
165
|
+
object ||= load_action_cable_message(message, first_event.context)
|
158
166
|
result = execute_update(first_subscription_id, first_event, object)
|
159
167
|
# Having calculated the result _once_, send the same payload to all subscribers
|
160
168
|
events.each do |event|
|
@@ -167,6 +175,18 @@ module GraphQL
|
|
167
175
|
end
|
168
176
|
end
|
169
177
|
|
178
|
+
# This is called to turn an ActionCable-broadcasted string (JSON)
|
179
|
+
# into a query-ready application object.
|
180
|
+
# @param message [String] n ActionCable-broadcasted string (JSON)
|
181
|
+
# @param context [GraphQL::Query::Context] the context of the first event for a given subscription fingerprint
|
182
|
+
def load_action_cable_message(message, context)
|
183
|
+
if @serialize_with_context
|
184
|
+
@serializer.load(message, context)
|
185
|
+
else
|
186
|
+
@serializer.load(message)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
170
190
|
# Return the query from "storage" (in memory)
|
171
191
|
def read_subscription(subscription_id)
|
172
192
|
query = @subscriptions[subscription_id]
|
@@ -29,26 +29,10 @@ module GraphQL
|
|
29
29
|
end
|
30
30
|
|
31
31
|
# @return [String] an identifier for this unit of subscription
|
32
|
-
def self.serialize(
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
when Hash
|
37
|
-
if field.is_a?(GraphQL::Schema::Field)
|
38
|
-
stringify_args(field, arguments)
|
39
|
-
else
|
40
|
-
GraphQL::Query::LiteralInput.from_arguments(
|
41
|
-
arguments,
|
42
|
-
field,
|
43
|
-
nil,
|
44
|
-
)
|
45
|
-
end
|
46
|
-
else
|
47
|
-
raise ArgumentError, "Unexpected arguments: #{arguments}, must be Hash or GraphQL::Arguments"
|
48
|
-
end
|
49
|
-
|
50
|
-
sorted_h = stringify_args(field, normalized_args.to_h)
|
51
|
-
Serialize.dump_recursive([scope, name, sorted_h])
|
32
|
+
def self.serialize(_name, arguments, field, scope:)
|
33
|
+
subscription = field.resolver || GraphQL::Schema::Subscription
|
34
|
+
normalized_args = stringify_args(field, arguments.to_h)
|
35
|
+
subscription.topic_for(arguments: normalized_args, field: field, scope: scope)
|
52
36
|
end
|
53
37
|
|
54
38
|
# @return [String] a logical identifier for this event. (Stable when the query is broadcastable.)
|
@@ -6,7 +6,7 @@ module GraphQL
|
|
6
6
|
description "Represents non-fractional signed whole numeric values. Since the value may exceed the size of a 32-bit integer, it's encoded as a string."
|
7
7
|
|
8
8
|
def self.coerce_input(value, _ctx)
|
9
|
-
value &&
|
9
|
+
value && parse_int(value)
|
10
10
|
rescue ArgumentError
|
11
11
|
nil
|
12
12
|
end
|
@@ -14,6 +14,10 @@ module GraphQL
|
|
14
14
|
def self.coerce_result(value, _ctx)
|
15
15
|
value.to_i.to_s
|
16
16
|
end
|
17
|
+
|
18
|
+
def self.parse_int(value)
|
19
|
+
value.is_a?(Numeric) ? value : Integer(value, 10)
|
20
|
+
end
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
data/lib/graphql/version.rb
CHANGED
data/lib/graphql.rb
CHANGED
@@ -57,22 +57,26 @@ module GraphQL
|
|
57
57
|
end
|
58
58
|
|
59
59
|
# Support Ruby 2.2 by implementing `-"str"`. If we drop 2.2 support, we can remove this backport.
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
60
|
+
if !String.method_defined?(:-@)
|
61
|
+
module StringDedupBackport
|
62
|
+
refine String do
|
63
|
+
def -@
|
64
|
+
if frozen?
|
65
|
+
self
|
66
|
+
else
|
67
|
+
self.dup.freeze
|
68
|
+
end
|
67
69
|
end
|
68
70
|
end
|
69
71
|
end
|
70
72
|
end
|
71
73
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
74
|
+
if !String.method_defined?(:match?)
|
75
|
+
module StringMatchBackport
|
76
|
+
refine String do
|
77
|
+
def match?(pattern)
|
78
|
+
self =~ pattern
|
79
|
+
end
|
76
80
|
end
|
77
81
|
end
|
78
82
|
end
|
data/readme.md
CHANGED
@@ -44,6 +44,6 @@ I also sell [GraphQL::Pro](https://graphql.pro) which provides several features
|
|
44
44
|
|
45
45
|
## Getting Involved
|
46
46
|
|
47
|
-
- __Say hi & ask questions__ in the
|
47
|
+
- __Say hi & ask questions__ in the #graphql-ruby channel on [Discord](https://discord.com/invite/xud7bH9) or [on Twitter](https://twitter.com/rmosolgo)!
|
48
48
|
- __Report bugs__ by posting a description, full stack trace, and all relevant code in a [GitHub issue](https://github.com/rmosolgo/graphql-ruby/issues).
|
49
49
|
- __Start hacking__ with the [Development guide](https://graphql-ruby.org/development).
|
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.12.
|
4
|
+
version: 1.12.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|