graphql 2.6.9 → 2.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/graphql/dataloader/async_dataloader.rb +35 -9
- data/lib/graphql/execution/field_resolve_step.rb +8 -1
- data/lib/graphql/execution/interpreter.rb +3 -1
- data/lib/graphql/execution/runner.rb +8 -1
- data/lib/graphql/float_decoding_error.rb +13 -0
- data/lib/graphql/float_encoding_error.rb +28 -0
- data/lib/graphql/language/lexer.rb +10 -7
- data/lib/graphql/language.rb +2 -2
- data/lib/graphql/pagination/relation_connection.rb +10 -1
- data/lib/graphql/query/variable_validation_error.rb +16 -1
- data/lib/graphql/schema.rb +2 -2
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +66 -29
- data/lib/graphql/types/float.rb +18 -4
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6c191887acba06a4030f962822e9106e9d3aa9d5550c8cd25ea76f343927f1b
|
|
4
|
+
data.tar.gz: b984ad9a61a391b4b8a42ade3bb9c63dc79ab2b3ee987845d655dc7038df1255
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7deac13ce0bbc579609e28575a309caa46086196f1381cfc5fbd6a5770b2059e123cb047cc835c573ad76be1df264a3b0d52d8692dbf161ad0d0248f8cfed2a3
|
|
7
|
+
data.tar.gz: 1362c0867ad8bbbe086b1999c710f447de6ea16b53b8a05a83dc39cba6c7b7d8d5cccdc388a3dff2f42c1b5135ff59a4c78dd2d53badd34b28718a73aa331b92
|
|
@@ -36,10 +36,14 @@ module GraphQL
|
|
|
36
36
|
run = task.graphql_async_dataloader_run
|
|
37
37
|
trace = run.trace
|
|
38
38
|
trace&.dataloader_fiber_yield(source)
|
|
39
|
-
run.
|
|
39
|
+
if !run.push_task_message(:paused_task, task)
|
|
40
|
+
task.stop
|
|
41
|
+
end
|
|
40
42
|
condition = task.graphql_async_dataloader_condition
|
|
41
43
|
condition.wait
|
|
42
|
-
run.
|
|
44
|
+
if !run.push_task_message(:resumed_task, task)
|
|
45
|
+
task.stop
|
|
46
|
+
end
|
|
43
47
|
trace&.dataloader_fiber_resume(source)
|
|
44
48
|
nil
|
|
45
49
|
end
|
|
@@ -69,7 +73,7 @@ module GraphQL
|
|
|
69
73
|
|
|
70
74
|
attr_accessor :trace, :root_task
|
|
71
75
|
|
|
72
|
-
attr_reader :jobs, :lazies_at_depth, :jobs_fiber_limit, :snoozed_jobs_condition, :snoozed_sources_condition
|
|
76
|
+
attr_reader :dataloader, :jobs, :lazies_at_depth, :jobs_fiber_limit, :snoozed_jobs_condition, :snoozed_sources_condition
|
|
73
77
|
|
|
74
78
|
def jobs_bandwidth?
|
|
75
79
|
running_count < @jobs_fiber_limit
|
|
@@ -84,6 +88,20 @@ module GraphQL
|
|
|
84
88
|
@tasks_channel_task.cancel
|
|
85
89
|
end
|
|
86
90
|
|
|
91
|
+
# Push to the tasks_channel, tolerating a closed channel: on the error path, `run_queue`
|
|
92
|
+
# closes the channel while sibling tasks can still run one more slice before
|
|
93
|
+
# `root_task.cancel` reaches them. Record `:task_error` payloads so they aren't lost, and
|
|
94
|
+
# return false so the caller can stop the task instead of raising `ClosedError` into user code.
|
|
95
|
+
def push_task_message(msg, data)
|
|
96
|
+
@tasks_channel.push([msg, data])
|
|
97
|
+
true
|
|
98
|
+
rescue Async::Queue::ClosedError
|
|
99
|
+
if msg == :task_error
|
|
100
|
+
@task_error ||= data
|
|
101
|
+
end
|
|
102
|
+
false
|
|
103
|
+
end
|
|
104
|
+
|
|
87
105
|
def wait_for_activity
|
|
88
106
|
@activity.wait
|
|
89
107
|
end
|
|
@@ -175,11 +193,19 @@ module GraphQL
|
|
|
175
193
|
end
|
|
176
194
|
|
|
177
195
|
def active_run
|
|
178
|
-
@pending_run ||
|
|
196
|
+
@pending_run || current_task_run || raise(GraphQL::Error, "No available Run to append to, GraphQL-Ruby bug")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# The current task's run, but only if it belongs to this dataloader. A different
|
|
200
|
+
# dataloader may be running inside one of our tasks (or vice versa), e.g. a query
|
|
201
|
+
# executed from a resolver or a subscription trigger; its run must not be reused.
|
|
202
|
+
def current_task_run
|
|
203
|
+
run = Async::Task.current?&.graphql_async_dataloader_run
|
|
204
|
+
run if run&.dataloader.equal?(self)
|
|
179
205
|
end
|
|
180
206
|
|
|
181
207
|
def run_isolated
|
|
182
|
-
previous_run =
|
|
208
|
+
previous_run = current_task_run
|
|
183
209
|
prev_pending_keys = {}
|
|
184
210
|
# Clear pending loads but keep already-cached records
|
|
185
211
|
# in case they are useful to the given block.
|
|
@@ -215,7 +241,7 @@ module GraphQL
|
|
|
215
241
|
|
|
216
242
|
def run(trace_query_lazy: nil)
|
|
217
243
|
trace = Fiber[:__graphql_current_multiplex]&.current_trace
|
|
218
|
-
run = @pending_run ||
|
|
244
|
+
run = @pending_run || current_task_run || raise(GraphQL::Error, "No available Run, GraphQL-Ruby internal bug")
|
|
219
245
|
@pending_run = nil
|
|
220
246
|
run.trace = trace
|
|
221
247
|
first_pass = true
|
|
@@ -336,14 +362,14 @@ module GraphQL
|
|
|
336
362
|
end
|
|
337
363
|
nil
|
|
338
364
|
rescue StandardError => err
|
|
339
|
-
run.
|
|
365
|
+
run.push_task_message(:task_error, err)
|
|
340
366
|
else
|
|
341
|
-
run.
|
|
367
|
+
run.push_task_message(:finished_task, task)
|
|
342
368
|
ensure
|
|
343
369
|
cleanup_fiber
|
|
344
370
|
trace&.dataloader_fiber_exit
|
|
345
371
|
end
|
|
346
|
-
run.
|
|
372
|
+
run.push_task_message(:started_task, new_task)
|
|
347
373
|
end
|
|
348
374
|
end
|
|
349
375
|
end
|
|
@@ -744,7 +744,8 @@ module GraphQL
|
|
|
744
744
|
when :dig
|
|
745
745
|
objects.map { |o| o.dig(*@field_definition.execution_mode_key) }
|
|
746
746
|
when :dataload
|
|
747
|
-
|
|
747
|
+
k = @field_definition.execution_mode_key
|
|
748
|
+
results = if k.is_a?(Class)
|
|
748
749
|
context.dataload_all(k, objects)
|
|
749
750
|
elsif (source_class = k[:with])
|
|
750
751
|
if (batch_args = k[:by])
|
|
@@ -764,6 +765,12 @@ module GraphQL
|
|
|
764
765
|
else
|
|
765
766
|
raise ArgumentError, "Unexpected `dataload: ...` configuration: #{k.inspect}"
|
|
766
767
|
end
|
|
768
|
+
method = k.is_a?(Hash) ? k[:method] : nil
|
|
769
|
+
if method
|
|
770
|
+
results.map { |r| r&.public_send(method) }
|
|
771
|
+
else
|
|
772
|
+
results
|
|
773
|
+
end
|
|
767
774
|
when :resolver_class
|
|
768
775
|
results = Array.new(objects.size, nil)
|
|
769
776
|
ps = @pending_steps ||= []
|
|
@@ -22,6 +22,7 @@ module GraphQL
|
|
|
22
22
|
# @param max_complexity [Integer, nil]
|
|
23
23
|
# @return [Array<GraphQL::Query::Result>] One result per query
|
|
24
24
|
def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
|
|
25
|
+
previous_multiplex = Fiber[:__graphql_current_multiplex]
|
|
25
26
|
queries = query_options.map do |opts|
|
|
26
27
|
query = case opts
|
|
27
28
|
when Hash
|
|
@@ -131,7 +132,6 @@ module GraphQL
|
|
|
131
132
|
queries.map { |q| q.result_values ||= {} }
|
|
132
133
|
raise
|
|
133
134
|
ensure
|
|
134
|
-
Fiber[:__graphql_current_multiplex] = nil
|
|
135
135
|
queries.map { |query|
|
|
136
136
|
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
|
|
137
137
|
if runtime
|
|
@@ -140,6 +140,8 @@ module GraphQL
|
|
|
140
140
|
}
|
|
141
141
|
end
|
|
142
142
|
end
|
|
143
|
+
ensure
|
|
144
|
+
Fiber[:__graphql_current_multiplex] = previous_multiplex
|
|
143
145
|
end
|
|
144
146
|
end
|
|
145
147
|
|
|
@@ -72,6 +72,7 @@ module GraphQL
|
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def execute
|
|
75
|
+
previous_multiplex = Fiber[:__graphql_current_multiplex]
|
|
75
76
|
Fiber[:__graphql_current_multiplex] = @multiplex
|
|
76
77
|
isolated_steps = [[]]
|
|
77
78
|
trace = @multiplex.current_trace
|
|
@@ -155,8 +156,14 @@ module GraphQL
|
|
|
155
156
|
query.result
|
|
156
157
|
end
|
|
157
158
|
end
|
|
159
|
+
rescue SystemStackError => err
|
|
160
|
+
@multiplex.queries.map do |query|
|
|
161
|
+
@schema.query_stack_error(query, err)
|
|
162
|
+
query.result_values ||= { "errors" => query.context.errors.map(&:to_h) }
|
|
163
|
+
query.result
|
|
164
|
+
end
|
|
158
165
|
ensure
|
|
159
|
-
Fiber[:__graphql_current_multiplex] =
|
|
166
|
+
Fiber[:__graphql_current_multiplex] = previous_multiplex
|
|
160
167
|
end
|
|
161
168
|
|
|
162
169
|
def gather_selections(type_defn, ast_selections, selections_step, query, all_selections, prototype_result, into:)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
# This error is raised when `Types::Float` is given a non-finite input value.
|
|
4
|
+
class FloatDecodingError < GraphQL::RuntimeTypeError
|
|
5
|
+
# The value which couldn't be decoded
|
|
6
|
+
attr_reader :float_value
|
|
7
|
+
|
|
8
|
+
def initialize(value)
|
|
9
|
+
@float_value = value
|
|
10
|
+
super("Float is not finite: #{value.inspect}.")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
# This error is raised when `Types::Float` is asked to return a non-finite value.
|
|
4
|
+
class FloatEncodingError < GraphQL::RuntimeTypeError
|
|
5
|
+
# The value which couldn't be encoded
|
|
6
|
+
attr_reader :float_value
|
|
7
|
+
|
|
8
|
+
# @return [GraphQL::Schema::Field] The field that returned a non-finite float
|
|
9
|
+
attr_reader :field
|
|
10
|
+
|
|
11
|
+
# @return [Array<String, Integer>] Where the field appeared in the GraphQL response
|
|
12
|
+
attr_reader :path
|
|
13
|
+
|
|
14
|
+
def initialize(value, context:)
|
|
15
|
+
@float_value = value
|
|
16
|
+
@field = context[:current_field]
|
|
17
|
+
@path = context[:current_path]
|
|
18
|
+
message = "Float is not finite: #{value.inspect}".dup
|
|
19
|
+
if @path
|
|
20
|
+
message << " @ #{@path.join(".")}"
|
|
21
|
+
end
|
|
22
|
+
if @field
|
|
23
|
+
message << " (#{@field.path})"
|
|
24
|
+
end
|
|
25
|
+
super("#{message}.")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -9,6 +9,9 @@ module GraphQL
|
|
|
9
9
|
end
|
|
10
10
|
@string = graphql_str
|
|
11
11
|
@filename = filename
|
|
12
|
+
if !@string.valid_encoding?
|
|
13
|
+
raise_parse_error("Parse error on bad Unicode escape sequence", nil, nil)
|
|
14
|
+
end
|
|
12
15
|
@scanner = StringScanner.new(graphql_str)
|
|
13
16
|
@pos = nil
|
|
14
17
|
@max_tokens = max_tokens || Float::INFINITY
|
|
@@ -110,10 +113,6 @@ module GraphQL
|
|
|
110
113
|
@scanner.pos += 1
|
|
111
114
|
:UNKNOWN_CHAR
|
|
112
115
|
end
|
|
113
|
-
rescue ArgumentError => err
|
|
114
|
-
if err.message == "invalid byte sequence in UTF-8"
|
|
115
|
-
raise_parse_error("Parse error on bad Unicode escape sequence", nil, nil)
|
|
116
|
-
end
|
|
117
116
|
end
|
|
118
117
|
|
|
119
118
|
def token_value
|
|
@@ -147,7 +146,7 @@ module GraphQL
|
|
|
147
146
|
"\\r" => "\r",
|
|
148
147
|
"\\t" => "\t",
|
|
149
148
|
}
|
|
150
|
-
UTF_8 = /\\u(?:([\
|
|
149
|
+
UTF_8 = /\\u(?:([\da-f]{4})|\{([\da-f]+)\})(?:\\u([\da-f]{4}))?/i
|
|
151
150
|
VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o
|
|
152
151
|
ESCAPED = /(?:#{ESCAPES}|#{UTF_8})/o
|
|
153
152
|
|
|
@@ -163,7 +162,11 @@ module GraphQL
|
|
|
163
162
|
if !str.valid_encoding? || !str.match?(VALID_STRING)
|
|
164
163
|
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
|
165
164
|
else
|
|
166
|
-
|
|
165
|
+
begin
|
|
166
|
+
Lexer.replace_escaped_characters_in_place(str)
|
|
167
|
+
rescue RangeError
|
|
168
|
+
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
|
169
|
+
end
|
|
167
170
|
|
|
168
171
|
if !str.valid_encoding?
|
|
169
172
|
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
|
@@ -289,7 +292,7 @@ module GraphQL
|
|
|
289
292
|
QUOTE = '"'
|
|
290
293
|
UNICODE_DIGIT = /[0-9A-Za-z]/
|
|
291
294
|
FOUR_DIGIT_UNICODE = /#{UNICODE_DIGIT}{4}/
|
|
292
|
-
N_DIGIT_UNICODE = %r{#{Punctuation::LCURLY}#{UNICODE_DIGIT}{
|
|
295
|
+
N_DIGIT_UNICODE = %r{#{Punctuation::LCURLY}#{UNICODE_DIGIT}+#{Punctuation::RCURLY}}x
|
|
293
296
|
UNICODE_ESCAPE = %r{\\u(?:#{FOUR_DIGIT_UNICODE}|#{N_DIGIT_UNICODE})}
|
|
294
297
|
STRING_ESCAPE = %r{[\\][\\/bfnrt]}
|
|
295
298
|
BLOCK_QUOTE = '"""'
|
data/lib/graphql/language.rb
CHANGED
|
@@ -221,7 +221,16 @@ module GraphQL
|
|
|
221
221
|
# returns an array of nodes
|
|
222
222
|
def load_nodes
|
|
223
223
|
# Return an array so we can consistently use `.index(node)` on it
|
|
224
|
-
@nodes
|
|
224
|
+
return @nodes if @nodes
|
|
225
|
+
if (@context[:dataloader].is_a?(GraphQL::Dataloader::AsyncDataloader))
|
|
226
|
+
# `AsyncDataloader` may resolve sibling fields (eg, `edges` and `pageInfo`)
|
|
227
|
+
# in separate Fibers, so several callers can get here before `@nodes` is set.
|
|
228
|
+
(@load_lock ||= Mutex.new).synchronize do
|
|
229
|
+
@nodes ||= limited_nodes.to_a
|
|
230
|
+
end
|
|
231
|
+
else
|
|
232
|
+
@nodes = limited_nodes.to_a
|
|
233
|
+
end
|
|
225
234
|
end
|
|
226
235
|
end
|
|
227
236
|
end
|
|
@@ -22,7 +22,7 @@ module GraphQL
|
|
|
22
22
|
# It is possible there are other extension items in this error, so handle
|
|
23
23
|
# a one level deep merge explicitly. However beyond that only show the
|
|
24
24
|
# latest value and problems.
|
|
25
|
-
super.merge({ "extensions" => { "value" =>
|
|
25
|
+
super.merge({ "extensions" => { "value" => value_for_extensions, "problems" => validation_result.problems }}) do |key, oldValue, newValue|
|
|
26
26
|
if oldValue.respond_to?(:merge)
|
|
27
27
|
oldValue.merge(newValue)
|
|
28
28
|
else
|
|
@@ -33,6 +33,21 @@ module GraphQL
|
|
|
33
33
|
|
|
34
34
|
private
|
|
35
35
|
|
|
36
|
+
def value_for_extensions(value = @value)
|
|
37
|
+
case value
|
|
38
|
+
when Array
|
|
39
|
+
value.map { |item| value_for_extensions(item) }
|
|
40
|
+
when Hash
|
|
41
|
+
value.each_with_object({}) do |(key, item), result|
|
|
42
|
+
result[key] = value_for_extensions(item)
|
|
43
|
+
end
|
|
44
|
+
when Float
|
|
45
|
+
value.finite? ? value : value.to_s
|
|
46
|
+
else
|
|
47
|
+
value
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
36
51
|
def problem_fields
|
|
37
52
|
@problem_fields ||= @validation_result
|
|
38
53
|
.problems
|
data/lib/graphql/schema.rb
CHANGED
|
@@ -1338,9 +1338,9 @@ module GraphQL
|
|
|
1338
1338
|
|
|
1339
1339
|
context.errors << execution_error
|
|
1340
1340
|
execution_error
|
|
1341
|
-
when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
|
|
1341
|
+
when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::FloatEncodingError, GraphQL::IntegerEncodingError
|
|
1342
1342
|
raise type_error
|
|
1343
|
-
when GraphQL::IntegerDecodingError
|
|
1343
|
+
when GraphQL::FloatDecodingError, GraphQL::IntegerDecodingError
|
|
1344
1344
|
nil
|
|
1345
1345
|
end
|
|
1346
1346
|
end
|
|
@@ -15,6 +15,8 @@ module GraphQL
|
|
|
15
15
|
# separately (which leads to exponential recursion through nested fragments),
|
|
16
16
|
# we flatten all fragment spreads into a single field map and compare within it.
|
|
17
17
|
NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
|
|
18
|
+
EXCLUSIVE_COMPARISON = 1
|
|
19
|
+
NONEXCLUSIVE_COMPARISON = 2
|
|
18
20
|
|
|
19
21
|
class Field
|
|
20
22
|
attr_reader :node, :definition, :owner_type, :parents
|
|
@@ -33,6 +35,10 @@ module GraphQL
|
|
|
33
35
|
def unwrapped_return_type
|
|
34
36
|
@unwrapped_return_type ||= return_type&.unwrap
|
|
35
37
|
end
|
|
38
|
+
|
|
39
|
+
def comparison_key
|
|
40
|
+
@comparison_key ||= [@node, @parents]
|
|
41
|
+
end
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
def initialize(*)
|
|
@@ -43,6 +49,9 @@ module GraphQL
|
|
|
43
49
|
# Track which sub-selection node pairs have been compared to prevent
|
|
44
50
|
# infinite recursion with cyclic fragments
|
|
45
51
|
@compared_sub_selections = {}.compare_by_identity
|
|
52
|
+
@compared_field_groups = {}
|
|
53
|
+
@field_group_signatures = {}.compare_by_identity
|
|
54
|
+
@field_selection_signatures = {}.compare_by_identity
|
|
46
55
|
# Cache mutually_exclusive? results for type pairs
|
|
47
56
|
@mutually_exclusive_cache = {}.compare_by_identity
|
|
48
57
|
# Cache collect_fields results for sub-selection comparison
|
|
@@ -215,21 +224,7 @@ module GraphQL
|
|
|
215
224
|
end
|
|
216
225
|
|
|
217
226
|
if all_same
|
|
218
|
-
|
|
219
|
-
# sub-selections. Deduplicate by AST node identity — fields from
|
|
220
|
-
# the same node always have identical sub-selections.
|
|
221
|
-
unique_nodes = fields.uniq { |f| f.node.object_id }
|
|
222
|
-
i = 0
|
|
223
|
-
while i < unique_nodes.size
|
|
224
|
-
j = i + 1
|
|
225
|
-
while j < unique_nodes.size
|
|
226
|
-
if unique_nodes[i].node.selections.size > 0 || unique_nodes[j].node.selections.size > 0
|
|
227
|
-
find_conflict(key, unique_nodes[i], unique_nodes[j])
|
|
228
|
-
end
|
|
229
|
-
j += 1
|
|
230
|
-
end
|
|
231
|
-
i += 1
|
|
232
|
-
end
|
|
227
|
+
find_conflicts_between_selection_groups(key, fields)
|
|
233
228
|
else
|
|
234
229
|
groups = fields.group_by { |f| field_signature(f) }
|
|
235
230
|
unique_groups = groups.values
|
|
@@ -243,22 +238,10 @@ module GraphQL
|
|
|
243
238
|
gj += 1
|
|
244
239
|
end
|
|
245
240
|
|
|
246
|
-
# Within same group,
|
|
247
|
-
# pairs for sub-selection conflicts
|
|
241
|
+
# Within the same group, fields can only conflict on sub-selections.
|
|
248
242
|
group = unique_groups[gi]
|
|
249
243
|
if group.size >= 2
|
|
250
|
-
|
|
251
|
-
ui = 0
|
|
252
|
-
while ui < unique_in_group.size
|
|
253
|
-
uj = ui + 1
|
|
254
|
-
while uj < unique_in_group.size
|
|
255
|
-
if unique_in_group[ui].node.selections.size > 0 || unique_in_group[uj].node.selections.size > 0
|
|
256
|
-
find_conflict(key, unique_in_group[ui], unique_in_group[uj])
|
|
257
|
-
end
|
|
258
|
-
uj += 1
|
|
259
|
-
end
|
|
260
|
-
ui += 1
|
|
261
|
-
end
|
|
244
|
+
find_conflicts_between_selection_groups(key, group)
|
|
262
245
|
end
|
|
263
246
|
|
|
264
247
|
gi += 1
|
|
@@ -279,6 +262,29 @@ module GraphQL
|
|
|
279
262
|
end
|
|
280
263
|
end
|
|
281
264
|
|
|
265
|
+
def find_conflicts_between_selection_groups(response_key, fields)
|
|
266
|
+
fields_by_selection = {}
|
|
267
|
+
fields.each do |field|
|
|
268
|
+
fields_by_selection[field_selection_signature(field)] ||= field
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
representatives = fields_by_selection.values
|
|
272
|
+
i = 0
|
|
273
|
+
while i < representatives.size
|
|
274
|
+
j = i + 1
|
|
275
|
+
while j < representatives.size
|
|
276
|
+
find_conflict(response_key, representatives[i], representatives[j])
|
|
277
|
+
j += 1
|
|
278
|
+
end
|
|
279
|
+
i += 1
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def field_selection_signature(field)
|
|
284
|
+
node = field.node
|
|
285
|
+
@field_selection_signatures[node] ||= node.selections.map(&:to_query_string)
|
|
286
|
+
end
|
|
287
|
+
|
|
282
288
|
def fields_same_signature?(f1, f2)
|
|
283
289
|
n1 = f1.node
|
|
284
290
|
n2 = f2.node
|
|
@@ -458,6 +464,7 @@ module GraphQL
|
|
|
458
464
|
response_keys.each do |key, fields|
|
|
459
465
|
fields2 = response_keys2[key]
|
|
460
466
|
next unless fields2
|
|
467
|
+
next if field_groups_already_compared?(fields, fields2, mutually_exclusive)
|
|
461
468
|
|
|
462
469
|
fields_arr = fields.is_a?(Field) ? [fields] : fields
|
|
463
470
|
fields2_arr = fields2.is_a?(Field) ? [fields2] : fields2
|
|
@@ -475,6 +482,36 @@ module GraphQL
|
|
|
475
482
|
end
|
|
476
483
|
end
|
|
477
484
|
|
|
485
|
+
def field_groups_already_compared?(fields, fields2, mutually_exclusive)
|
|
486
|
+
signature1 = field_group_signature(fields)
|
|
487
|
+
signature2 = field_group_signature(fields2)
|
|
488
|
+
previous_comparisons = @compared_field_groups[signature1]
|
|
489
|
+
comparison_state = previous_comparisons && previous_comparisons[signature2]
|
|
490
|
+
|
|
491
|
+
if mutually_exclusive
|
|
492
|
+
return true if comparison_state
|
|
493
|
+
new_state = EXCLUSIVE_COMPARISON
|
|
494
|
+
else
|
|
495
|
+
return true if comparison_state == NONEXCLUSIVE_COMPARISON
|
|
496
|
+
new_state = NONEXCLUSIVE_COMPARISON
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
previous_comparisons ||= (@compared_field_groups[signature1] = {})
|
|
500
|
+
previous_comparisons[signature2] = new_state
|
|
501
|
+
|
|
502
|
+
reverse_comparisons = @compared_field_groups[signature2] ||= {}
|
|
503
|
+
reverse_comparisons[signature1] = new_state
|
|
504
|
+
false
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def field_group_signature(fields)
|
|
508
|
+
if fields.is_a?(Field)
|
|
509
|
+
fields.comparison_key
|
|
510
|
+
else
|
|
511
|
+
@field_group_signatures[fields] ||= fields.map(&:comparison_key)
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
478
515
|
def same_arguments?(field1, field2)
|
|
479
516
|
arguments1 = field1.arguments
|
|
480
517
|
arguments2 = field2.arguments
|
data/lib/graphql/types/float.rb
CHANGED
|
@@ -5,12 +5,26 @@ module GraphQL
|
|
|
5
5
|
class Float < GraphQL::Schema::Scalar
|
|
6
6
|
description "Represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point)."
|
|
7
7
|
|
|
8
|
-
def self.coerce_input(value,
|
|
9
|
-
value.is_a?(Numeric)
|
|
8
|
+
def self.coerce_input(value, ctx)
|
|
9
|
+
return if !value.is_a?(Numeric)
|
|
10
|
+
|
|
11
|
+
value = value.to_f
|
|
12
|
+
if value.finite?
|
|
13
|
+
value
|
|
14
|
+
else
|
|
15
|
+
err = GraphQL::FloatDecodingError.new(value)
|
|
16
|
+
ctx.schema.type_error(err, ctx)
|
|
17
|
+
end
|
|
10
18
|
end
|
|
11
19
|
|
|
12
|
-
def self.coerce_result(value,
|
|
13
|
-
value.to_f
|
|
20
|
+
def self.coerce_result(value, ctx)
|
|
21
|
+
value = value.to_f
|
|
22
|
+
if value.finite?
|
|
23
|
+
value
|
|
24
|
+
else
|
|
25
|
+
err = GraphQL::FloatEncodingError.new(value, context: ctx)
|
|
26
|
+
ctx.schema.type_error(err, ctx)
|
|
27
|
+
end
|
|
14
28
|
end
|
|
15
29
|
|
|
16
30
|
default_scalar true
|
data/lib/graphql/version.rb
CHANGED
data/lib/graphql.rb
CHANGED
|
@@ -93,6 +93,8 @@ This is probably a bug in GraphQL-Ruby, please report this error on GitHub: http
|
|
|
93
93
|
autoload :AnalysisError, "graphql/analysis_error"
|
|
94
94
|
autoload :CoercionError, "graphql/coercion_error"
|
|
95
95
|
autoload :InvalidNameError, "graphql/invalid_name_error"
|
|
96
|
+
autoload :FloatDecodingError, "graphql/float_decoding_error"
|
|
97
|
+
autoload :FloatEncodingError, "graphql/float_encoding_error"
|
|
96
98
|
autoload :IntegerDecodingError, "graphql/integer_decoding_error"
|
|
97
99
|
autoload :IntegerEncodingError, "graphql/integer_encoding_error"
|
|
98
100
|
autoload :StringEncodingError, "graphql/string_encoding_error"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graphql
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.6.
|
|
4
|
+
version: 2.6.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Mosolgo
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-08-
|
|
10
|
+
date: 2026-08-27 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: base64
|
|
@@ -460,6 +460,8 @@ files:
|
|
|
460
460
|
- lib/graphql/execution/runner.rb
|
|
461
461
|
- lib/graphql/execution/selections_step.rb
|
|
462
462
|
- lib/graphql/execution_error.rb
|
|
463
|
+
- lib/graphql/float_decoding_error.rb
|
|
464
|
+
- lib/graphql/float_encoding_error.rb
|
|
463
465
|
- lib/graphql/integer_decoding_error.rb
|
|
464
466
|
- lib/graphql/integer_encoding_error.rb
|
|
465
467
|
- lib/graphql/introspection.rb
|