graphql 2.2.5 → 2.3.2
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/generators/graphql/templates/schema.erb +3 -0
- data/lib/graphql/analysis/ast/field_usage.rb +36 -9
- data/lib/graphql/analysis/ast/visitor.rb +8 -0
- data/lib/graphql/analysis/ast.rb +10 -1
- data/lib/graphql/backtrace/inspect_result.rb +0 -12
- data/lib/graphql/coercion_error.rb +1 -9
- data/lib/graphql/dataloader/request.rb +5 -0
- data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
- data/lib/graphql/execution/interpreter/runtime.rb +9 -0
- data/lib/graphql/execution/interpreter.rb +90 -150
- data/lib/graphql/introspection/entry_points.rb +9 -3
- data/lib/graphql/introspection/schema_type.rb +3 -1
- data/lib/graphql/language/document_from_schema_definition.rb +2 -3
- data/lib/graphql/language/lexer.rb +48 -30
- data/lib/graphql/language/nodes.rb +2 -1
- data/lib/graphql/language/parser.rb +25 -11
- data/lib/graphql/language/printer.rb +4 -0
- data/lib/graphql/language.rb +60 -0
- data/lib/graphql/pagination/array_connection.rb +6 -6
- data/lib/graphql/query/context.rb +30 -33
- data/lib/graphql/query/validation_pipeline.rb +2 -2
- data/lib/graphql/query/variables.rb +3 -3
- data/lib/graphql/query.rb +3 -3
- data/lib/graphql/schema/argument.rb +18 -2
- data/lib/graphql/schema/base_64_encoder.rb +3 -5
- data/lib/graphql/schema/build_from_definition.rb +9 -1
- data/lib/graphql/schema/field.rb +33 -30
- data/lib/graphql/schema/input_object.rb +1 -2
- data/lib/graphql/schema/interface.rb +5 -1
- data/lib/graphql/schema/loader.rb +2 -1
- data/lib/graphql/schema/member/has_arguments.rb +2 -2
- data/lib/graphql/schema/mutation.rb +7 -0
- data/lib/graphql/schema/resolver.rb +19 -10
- data/lib/graphql/schema/unique_within_type.rb +1 -1
- data/lib/graphql/schema.rb +119 -28
- data/lib/graphql/static_validation/literal_validator.rb +1 -2
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
- data/lib/graphql/static_validation/validator.rb +3 -0
- data/lib/graphql/subscriptions/serialize.rb +2 -0
- data/lib/graphql/subscriptions.rb +0 -3
- data/lib/graphql/testing/helpers.rb +32 -6
- data/lib/graphql/tracing/data_dog_trace.rb +21 -34
- data/lib/graphql/tracing/data_dog_tracing.rb +7 -21
- data/lib/graphql/tracing/legacy_hooks_trace.rb +74 -0
- data/lib/graphql/tracing/platform_tracing.rb +3 -1
- data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
- data/lib/graphql/tracing/prometheus_trace.rb +2 -2
- data/lib/graphql/tracing/sentry_trace.rb +112 -0
- data/lib/graphql/tracing.rb +3 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +10 -2
- metadata +38 -23
- data/lib/graphql/schema/base_64_bp.rb +0 -26
- data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -3,7 +3,7 @@ module GraphQL
|
|
3
3
|
module Language
|
4
4
|
|
5
5
|
class Lexer
|
6
|
-
def initialize(graphql_str, filename: nil)
|
6
|
+
def initialize(graphql_str, filename: nil, max_tokens: nil)
|
7
7
|
if !(graphql_str.encoding == Encoding::UTF_8 || graphql_str.ascii_only?)
|
8
8
|
graphql_str = graphql_str.dup.force_encoding(Encoding::UTF_8)
|
9
9
|
end
|
@@ -11,6 +11,8 @@ module GraphQL
|
|
11
11
|
@filename = filename
|
12
12
|
@scanner = StringScanner.new(graphql_str)
|
13
13
|
@pos = nil
|
14
|
+
@max_tokens = max_tokens || Float::INFINITY
|
15
|
+
@tokens_count = 0
|
14
16
|
end
|
15
17
|
|
16
18
|
def eos?
|
@@ -22,6 +24,10 @@ module GraphQL
|
|
22
24
|
def advance
|
23
25
|
@scanner.skip(IGNORE_REGEXP)
|
24
26
|
return false if @scanner.eos?
|
27
|
+
@tokens_count += 1
|
28
|
+
if @tokens_count > @max_tokens
|
29
|
+
raise_parse_error("This query is too large to execute.")
|
30
|
+
end
|
25
31
|
@pos = @scanner.pos
|
26
32
|
next_byte = @string.getbyte(@pos)
|
27
33
|
next_byte_is_for = FIRST_BYTES[next_byte]
|
@@ -52,6 +58,17 @@ module GraphQL
|
|
52
58
|
:IDENTIFIER
|
53
59
|
when ByteFor::NUMBER
|
54
60
|
@scanner.skip(NUMERIC_REGEXP)
|
61
|
+
|
62
|
+
if GraphQL.reject_numbers_followed_by_names
|
63
|
+
new_pos = @scanner.pos
|
64
|
+
peek_byte = @string.getbyte(new_pos)
|
65
|
+
next_first_byte = FIRST_BYTES[peek_byte]
|
66
|
+
if next_first_byte == ByteFor::NAME || next_first_byte == ByteFor::IDENTIFIER
|
67
|
+
number_part = token_value
|
68
|
+
name_part = @scanner.scan(IDENTIFIER_REGEXP)
|
69
|
+
raise_parse_error("Name after number is not allowed (in `#{number_part}#{name_part}`)")
|
70
|
+
end
|
71
|
+
end
|
55
72
|
# Check for a matched decimal:
|
56
73
|
@scanner[1] ? :FLOAT : :INT
|
57
74
|
when ByteFor::ELLIPSIS
|
@@ -89,6 +106,8 @@ module GraphQL
|
|
89
106
|
"..."
|
90
107
|
elsif token_name == :STRING
|
91
108
|
string_value
|
109
|
+
elsif @scanner.matched_size.nil?
|
110
|
+
@scanner.peek(1)
|
92
111
|
else
|
93
112
|
token_value
|
94
113
|
end
|
@@ -107,29 +126,27 @@ module GraphQL
|
|
107
126
|
}
|
108
127
|
UTF_8 = /\\u(?:([\dAa-f]{4})|\{([\da-f]{4,})\})(?:\\u([\dAa-f]{4}))?/i
|
109
128
|
VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o
|
129
|
+
ESCAPED = /(?:#{ESCAPES}|#{UTF_8})/o
|
110
130
|
|
111
131
|
def string_value
|
112
132
|
str = token_value
|
113
133
|
is_block = str.start_with?('"""')
|
114
134
|
if is_block
|
115
135
|
str.gsub!(/\A"""|"""\z/, '')
|
136
|
+
return Language::BlockString.trim_whitespace(str)
|
116
137
|
else
|
117
138
|
str.gsub!(/\A"|"\z/, '')
|
118
|
-
end
|
119
|
-
|
120
|
-
if is_block
|
121
|
-
str = Language::BlockString.trim_whitespace(str)
|
122
|
-
end
|
123
|
-
|
124
|
-
if !str.valid_encoding? || !str.match?(VALID_STRING)
|
125
|
-
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
126
|
-
else
|
127
|
-
Lexer.replace_escaped_characters_in_place(str)
|
128
139
|
|
129
|
-
if !str.valid_encoding?
|
140
|
+
if !str.valid_encoding? || !str.match?(VALID_STRING)
|
130
141
|
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
131
142
|
else
|
132
|
-
str
|
143
|
+
Lexer.replace_escaped_characters_in_place(str)
|
144
|
+
|
145
|
+
if !str.valid_encoding?
|
146
|
+
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
147
|
+
else
|
148
|
+
str
|
149
|
+
end
|
133
150
|
end
|
134
151
|
end
|
135
152
|
end
|
@@ -156,6 +173,7 @@ module GraphQL
|
|
156
173
|
INT_REGEXP = /-?(?:[0]|[1-9][0-9]*)/
|
157
174
|
FLOAT_DECIMAL_REGEXP = /[.][0-9]+/
|
158
175
|
FLOAT_EXP_REGEXP = /[eE][+-]?[0-9]+/
|
176
|
+
# TODO: FLOAT_EXP_REGEXP should not be allowed to follow INT_REGEXP, integers are not allowed to have exponent parts.
|
159
177
|
NUMERIC_REGEXP = /#{INT_REGEXP}(#{FLOAT_DECIMAL_REGEXP}#{FLOAT_EXP_REGEXP}|#{FLOAT_DECIMAL_REGEXP}|#{FLOAT_EXP_REGEXP})?/
|
160
178
|
|
161
179
|
KEYWORDS = [
|
@@ -250,11 +268,10 @@ module GraphQL
|
|
250
268
|
FOUR_DIGIT_UNICODE = /#{UNICODE_DIGIT}{4}/
|
251
269
|
N_DIGIT_UNICODE = %r{#{Punctuation::LCURLY}#{UNICODE_DIGIT}{4,}#{Punctuation::RCURLY}}x
|
252
270
|
UNICODE_ESCAPE = %r{\\u(?:#{FOUR_DIGIT_UNICODE}|#{N_DIGIT_UNICODE})}
|
253
|
-
# # https://graphql.github.io/graphql-spec/June2018/#sec-String-Value
|
254
271
|
STRING_ESCAPE = %r{[\\][\\/bfnrt]}
|
255
272
|
BLOCK_QUOTE = '"""'
|
256
273
|
ESCAPED_QUOTE = /\\"/;
|
257
|
-
STRING_CHAR = /#{ESCAPED_QUOTE}|[^"
|
274
|
+
STRING_CHAR = /#{ESCAPED_QUOTE}|[^"\\\n\r]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/
|
258
275
|
QUOTED_STRING_REGEXP = %r{#{QUOTE} (?:#{STRING_CHAR})* #{QUOTE}}x
|
259
276
|
BLOCK_STRING_REGEXP = %r{
|
260
277
|
#{BLOCK_QUOTE}
|
@@ -299,24 +316,25 @@ module GraphQL
|
|
299
316
|
# Replace any escaped unicode or whitespace with the _actual_ characters
|
300
317
|
# To avoid allocating more strings, this modifies the string passed into it
|
301
318
|
def self.replace_escaped_characters_in_place(raw_string)
|
302
|
-
raw_string.gsub!(
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
(
|
311
|
-
|
312
|
-
|
313
|
-
|
319
|
+
raw_string.gsub!(ESCAPED) do |matched_str|
|
320
|
+
if (point_str_1 = $1 || $2)
|
321
|
+
codepoint_1 = point_str_1.to_i(16)
|
322
|
+
if (codepoint_2 = $3)
|
323
|
+
codepoint_2 = codepoint_2.to_i(16)
|
324
|
+
if (codepoint_1 >= 0xD800 && codepoint_1 <= 0xDBFF) && # leading surrogate
|
325
|
+
(codepoint_2 >= 0xDC00 && codepoint_2 <= 0xDFFF) # trailing surrogate
|
326
|
+
# A surrogate pair
|
327
|
+
combined = ((codepoint_1 - 0xD800) * 0x400) + (codepoint_2 - 0xDC00) + 0x10000
|
328
|
+
[combined].pack('U'.freeze)
|
329
|
+
else
|
330
|
+
# Two separate code points
|
331
|
+
[codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze)
|
332
|
+
end
|
314
333
|
else
|
315
|
-
|
316
|
-
[codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze)
|
334
|
+
[codepoint_1].pack('U'.freeze)
|
317
335
|
end
|
318
336
|
else
|
319
|
-
[
|
337
|
+
ESCAPES_REPLACE[matched_str]
|
320
338
|
end
|
321
339
|
end
|
322
340
|
nil
|
@@ -362,6 +362,7 @@ module GraphQL
|
|
362
362
|
arguments: Nodes::Argument,
|
363
363
|
locations: Nodes::DirectiveLocation,
|
364
364
|
)
|
365
|
+
self.children_method_name = :definitions
|
365
366
|
end
|
366
367
|
|
367
368
|
# An enum value. The string is available as {#name}.
|
@@ -512,7 +513,7 @@ module GraphQL
|
|
512
513
|
# An operation-level query variable
|
513
514
|
class VariableDefinition < AbstractNode
|
514
515
|
scalar_methods :name, :type, :default_value
|
515
|
-
children_methods
|
516
|
+
children_methods(directives: Directive)
|
516
517
|
# @!attribute default_value
|
517
518
|
# @return [String, Integer, Float, Boolean, Array, NullValue] A Ruby value to use if no other value is provided
|
518
519
|
|
@@ -12,8 +12,8 @@ module GraphQL
|
|
12
12
|
class << self
|
13
13
|
attr_accessor :cache
|
14
14
|
|
15
|
-
def parse(graphql_str, filename: nil, trace: Tracing::NullTrace)
|
16
|
-
self.new(graphql_str, filename: filename, trace: trace).parse
|
15
|
+
def parse(graphql_str, filename: nil, trace: Tracing::NullTrace, max_tokens: nil)
|
16
|
+
self.new(graphql_str, filename: filename, trace: trace, max_tokens: max_tokens).parse
|
17
17
|
end
|
18
18
|
|
19
19
|
def parse_file(filename, trace: Tracing::NullTrace)
|
@@ -27,14 +27,15 @@ module GraphQL
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def initialize(graphql_str, filename: nil, trace: Tracing::NullTrace)
|
30
|
+
def initialize(graphql_str, filename: nil, trace: Tracing::NullTrace, max_tokens: nil)
|
31
31
|
if graphql_str.nil?
|
32
32
|
raise GraphQL::ParseError.new("No query string was present", nil, nil, nil)
|
33
33
|
end
|
34
|
-
@lexer = Lexer.new(graphql_str, filename: filename)
|
34
|
+
@lexer = Lexer.new(graphql_str, filename: filename, max_tokens: max_tokens)
|
35
35
|
@graphql_str = graphql_str
|
36
36
|
@filename = filename
|
37
37
|
@trace = trace
|
38
|
+
@dedup_identifiers = false
|
38
39
|
end
|
39
40
|
|
40
41
|
def parse
|
@@ -121,7 +122,17 @@ module GraphQL
|
|
121
122
|
value
|
122
123
|
end
|
123
124
|
|
124
|
-
|
125
|
+
directives = parse_directives
|
126
|
+
|
127
|
+
defs << Nodes::VariableDefinition.new(
|
128
|
+
pos: loc,
|
129
|
+
name: var_name,
|
130
|
+
type: var_type,
|
131
|
+
default_value: default_value,
|
132
|
+
directives: directives,
|
133
|
+
filename: @filename,
|
134
|
+
source_string: @graphql_str
|
135
|
+
)
|
125
136
|
end
|
126
137
|
expect_token(:RPAREN)
|
127
138
|
defs
|
@@ -722,6 +733,9 @@ module GraphQL
|
|
722
733
|
# Only use when we care about the expected token's value
|
723
734
|
def expect_token_value(tok)
|
724
735
|
token_value = @lexer.token_value
|
736
|
+
if @dedup_identifiers
|
737
|
+
token_value = -token_value
|
738
|
+
end
|
725
739
|
expect_token(tok)
|
726
740
|
token_value
|
727
741
|
end
|
@@ -729,12 +743,12 @@ module GraphQL
|
|
729
743
|
# token_value works for when the scanner matched something
|
730
744
|
# which is usually fine and it's good for it to be fast at that.
|
731
745
|
def debug_token_value
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
@
|
746
|
+
@lexer.debug_token_value(token_name)
|
747
|
+
end
|
748
|
+
class SchemaParser < Parser
|
749
|
+
def initialize(*args, **kwargs)
|
750
|
+
super
|
751
|
+
@dedup_identifiers = true
|
738
752
|
end
|
739
753
|
end
|
740
754
|
end
|
@@ -208,6 +208,10 @@ module GraphQL
|
|
208
208
|
print_string(" = ")
|
209
209
|
print_node(variable_definition.default_value)
|
210
210
|
end
|
211
|
+
variable_definition.directives.each do |dir|
|
212
|
+
print_string(" ")
|
213
|
+
print_directive(dir)
|
214
|
+
end
|
211
215
|
end
|
212
216
|
|
213
217
|
def print_variable_identifier(variable_identifier)
|
data/lib/graphql/language.rb
CHANGED
@@ -12,6 +12,7 @@ require "graphql/language/static_visitor"
|
|
12
12
|
require "graphql/language/token"
|
13
13
|
require "graphql/language/visitor"
|
14
14
|
require "graphql/language/definition_slice"
|
15
|
+
require "strscan"
|
15
16
|
|
16
17
|
module GraphQL
|
17
18
|
module Language
|
@@ -32,6 +33,65 @@ module GraphQL
|
|
32
33
|
else
|
33
34
|
JSON.generate(value, quirks_mode: true)
|
34
35
|
end
|
36
|
+
rescue JSON::GeneratorError
|
37
|
+
if Float::INFINITY == value
|
38
|
+
"Infinity"
|
39
|
+
else
|
40
|
+
raise
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns a new string if any single-quoted newlines were escaped.
|
45
|
+
# Otherwise, returns `query_str` unchanged.
|
46
|
+
# @return [String]
|
47
|
+
def self.escape_single_quoted_newlines(query_str)
|
48
|
+
scanner = StringScanner.new(query_str)
|
49
|
+
inside_single_quoted_string = false
|
50
|
+
new_query_str = nil
|
51
|
+
while !scanner.eos?
|
52
|
+
if (match = scanner.scan(/(?:\\"|[^"\n\r]|""")+/m)) && new_query_str
|
53
|
+
new_query_str << match
|
54
|
+
elsif scanner.scan('"')
|
55
|
+
new_query_str && (new_query_str << '"')
|
56
|
+
inside_single_quoted_string = !inside_single_quoted_string
|
57
|
+
elsif scanner.scan("\n")
|
58
|
+
if inside_single_quoted_string
|
59
|
+
new_query_str ||= query_str[0, scanner.pos - 1]
|
60
|
+
new_query_str << '\\n'
|
61
|
+
else
|
62
|
+
new_query_str && (new_query_str << "\n")
|
63
|
+
end
|
64
|
+
elsif scanner.scan("\r")
|
65
|
+
if inside_single_quoted_string
|
66
|
+
new_query_str ||= query_str[0, scanner.pos - 1]
|
67
|
+
new_query_str << '\\r'
|
68
|
+
else
|
69
|
+
new_query_str && (new_query_str << "\r")
|
70
|
+
end
|
71
|
+
elsif scanner.eos?
|
72
|
+
break
|
73
|
+
else
|
74
|
+
raise ArgumentError, "Unmatchable string scanner segment: #{scanner.rest.inspect}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
new_query_str || query_str
|
78
|
+
end
|
79
|
+
|
80
|
+
INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP = %r{
|
81
|
+
(
|
82
|
+
((?<num>#{Lexer::INT_REGEXP}(#{Lexer::FLOAT_EXP_REGEXP})?)(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
|
83
|
+
|
|
84
|
+
((?<num>#{Lexer::INT_REGEXP}#{Lexer::FLOAT_DECIMAL_REGEXP}#{Lexer::FLOAT_EXP_REGEXP})(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
|
85
|
+
|
|
86
|
+
((?<num>#{Lexer::INT_REGEXP}#{Lexer::FLOAT_DECIMAL_REGEXP})(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
|
87
|
+
)}x
|
88
|
+
|
89
|
+
def self.add_space_between_numbers_and_names(query_str)
|
90
|
+
if query_str.match?(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP)
|
91
|
+
query_str.gsub(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP, "\\k<num> \\k<name>:")
|
92
|
+
else
|
93
|
+
query_str
|
94
|
+
end
|
35
95
|
end
|
36
96
|
end
|
37
97
|
end
|
@@ -35,10 +35,10 @@ module GraphQL
|
|
35
35
|
def load_nodes
|
36
36
|
@nodes ||= begin
|
37
37
|
sliced_nodes = if before && after
|
38
|
-
end_idx = index_from_cursor(before)-
|
38
|
+
end_idx = index_from_cursor(before) - 2
|
39
39
|
end_idx < 0 ? [] : items[index_from_cursor(after)..end_idx] || []
|
40
40
|
elsif before
|
41
|
-
end_idx = index_from_cursor(before)-2
|
41
|
+
end_idx = index_from_cursor(before) - 2
|
42
42
|
end_idx < 0 ? [] : items[0..end_idx] || []
|
43
43
|
elsif after
|
44
44
|
items[index_from_cursor(after)..-1] || []
|
@@ -56,12 +56,12 @@ module GraphQL
|
|
56
56
|
false
|
57
57
|
end
|
58
58
|
|
59
|
-
@has_next_page = if
|
60
|
-
# There are more items after these items
|
61
|
-
sliced_nodes.count > first
|
62
|
-
elsif before
|
59
|
+
@has_next_page = if before
|
63
60
|
# The original array is longer than the `before` index
|
64
61
|
index_from_cursor(before) < items.length + 1
|
62
|
+
elsif first
|
63
|
+
# There are more items after these items
|
64
|
+
sliced_nodes.count > first
|
65
65
|
else
|
66
66
|
false
|
67
67
|
end
|
@@ -6,36 +6,6 @@ module GraphQL
|
|
6
6
|
# Expose some query-specific info to field resolve functions.
|
7
7
|
# It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
|
8
8
|
class Context
|
9
|
-
module SharedMethods
|
10
|
-
# Return this value to tell the runtime
|
11
|
-
# to exclude this field from the response altogether
|
12
|
-
def skip
|
13
|
-
GraphQL::Execution::SKIP
|
14
|
-
end
|
15
|
-
|
16
|
-
# Add error at query-level.
|
17
|
-
# @param error [GraphQL::ExecutionError] an execution error
|
18
|
-
# @return [void]
|
19
|
-
def add_error(error)
|
20
|
-
if !error.is_a?(ExecutionError)
|
21
|
-
raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
|
22
|
-
end
|
23
|
-
errors << error
|
24
|
-
nil
|
25
|
-
end
|
26
|
-
|
27
|
-
# @example Print the GraphQL backtrace during field resolution
|
28
|
-
# puts ctx.backtrace
|
29
|
-
#
|
30
|
-
# @return [GraphQL::Backtrace] The backtrace for this point in query execution
|
31
|
-
def backtrace
|
32
|
-
GraphQL::Backtrace.new(self)
|
33
|
-
end
|
34
|
-
|
35
|
-
def execution_errors
|
36
|
-
@execution_errors ||= ExecutionErrors.new(self)
|
37
|
-
end
|
38
|
-
end
|
39
9
|
|
40
10
|
class ExecutionErrors
|
41
11
|
def initialize(ctx)
|
@@ -59,7 +29,6 @@ module GraphQL
|
|
59
29
|
alias :push :add
|
60
30
|
end
|
61
31
|
|
62
|
-
include SharedMethods
|
63
32
|
extend Forwardable
|
64
33
|
|
65
34
|
# @return [Array<GraphQL::ExecutionError>] errors returned during execution
|
@@ -77,11 +46,10 @@ module GraphQL
|
|
77
46
|
# Make a new context which delegates key lookup to `values`
|
78
47
|
# @param query [GraphQL::Query] the query who owns this context
|
79
48
|
# @param values [Hash] A hash of arbitrary values which will be accessible at query-time
|
80
|
-
def initialize(query:, schema: query.schema, values
|
49
|
+
def initialize(query:, schema: query.schema, values:)
|
81
50
|
@query = query
|
82
51
|
@schema = schema
|
83
52
|
@provided_values = values || {}
|
84
|
-
@object = object
|
85
53
|
# Namespaced storage, where user-provided values are in `nil` namespace:
|
86
54
|
@storage = Hash.new { |h, k| h[k] = {} }
|
87
55
|
@storage[nil] = @provided_values
|
@@ -140,6 +108,35 @@ module GraphQL
|
|
140
108
|
end
|
141
109
|
end
|
142
110
|
|
111
|
+
# Return this value to tell the runtime
|
112
|
+
# to exclude this field from the response altogether
|
113
|
+
def skip
|
114
|
+
GraphQL::Execution::SKIP
|
115
|
+
end
|
116
|
+
|
117
|
+
# Add error at query-level.
|
118
|
+
# @param error [GraphQL::ExecutionError] an execution error
|
119
|
+
# @return [void]
|
120
|
+
def add_error(error)
|
121
|
+
if !error.is_a?(ExecutionError)
|
122
|
+
raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
|
123
|
+
end
|
124
|
+
errors << error
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
|
128
|
+
# @example Print the GraphQL backtrace during field resolution
|
129
|
+
# puts ctx.backtrace
|
130
|
+
#
|
131
|
+
# @return [GraphQL::Backtrace] The backtrace for this point in query execution
|
132
|
+
def backtrace
|
133
|
+
GraphQL::Backtrace.new(self)
|
134
|
+
end
|
135
|
+
|
136
|
+
def execution_errors
|
137
|
+
@execution_errors ||= ExecutionErrors.new(self)
|
138
|
+
end
|
139
|
+
|
143
140
|
def current_path
|
144
141
|
current_runtime_state = Thread.current[:__graphql_runtime_info]
|
145
142
|
query_runtime_state = current_runtime_state && current_runtime_state[@query]
|
@@ -14,7 +14,7 @@ module GraphQL
|
|
14
14
|
#
|
15
15
|
# @api private
|
16
16
|
class ValidationPipeline
|
17
|
-
attr_reader :max_depth, :max_complexity
|
17
|
+
attr_reader :max_depth, :max_complexity, :validate_timeout_remaining
|
18
18
|
|
19
19
|
def initialize(query:, parse_error:, operation_name_error:, max_depth:, max_complexity:)
|
20
20
|
@validation_errors = []
|
@@ -71,7 +71,7 @@ module GraphQL
|
|
71
71
|
validator = @query.static_validator || @schema.static_validator
|
72
72
|
validation_result = validator.validate(@query, validate: @query.validate, timeout: @schema.validate_timeout, max_errors: @schema.validate_max_errors)
|
73
73
|
@validation_errors.concat(validation_result[:errors])
|
74
|
-
|
74
|
+
@validate_timeout_remaining = validation_result[:remaining_timeout]
|
75
75
|
if @validation_errors.empty?
|
76
76
|
@validation_errors.concat(@query.variables.errors)
|
77
77
|
end
|
@@ -26,7 +26,7 @@ module GraphQL
|
|
26
26
|
# - Then, fall back to the default value from the query string
|
27
27
|
# If it's still nil, raise an error if it's required.
|
28
28
|
variable_type = schema.type_from_ast(ast_variable.type, context: ctx)
|
29
|
-
if variable_type.nil?
|
29
|
+
if variable_type.nil? || !variable_type.unwrap.kind.input?
|
30
30
|
# Pass -- it will get handled by a validator
|
31
31
|
else
|
32
32
|
variable_name = ast_variable.name
|
@@ -80,12 +80,12 @@ module GraphQL
|
|
80
80
|
else
|
81
81
|
val
|
82
82
|
end
|
83
|
-
end
|
83
|
+
end
|
84
84
|
|
85
85
|
def add_max_errors_reached_message
|
86
86
|
message = "Too many errors processing variables, max validation error limit reached. Execution aborted"
|
87
87
|
validation_result = GraphQL::Query::InputValidationResult.from_problem(message)
|
88
|
-
errors << GraphQL::Query::VariableValidationError.new(nil, nil, nil, validation_result, msg: message)
|
88
|
+
errors << GraphQL::Query::VariableValidationError.new(nil, nil, nil, validation_result, msg: message)
|
89
89
|
end
|
90
90
|
end
|
91
91
|
end
|
data/lib/graphql/query.rb
CHANGED
@@ -99,7 +99,7 @@ module GraphQL
|
|
99
99
|
# Even if `variables: nil` is passed, use an empty hash for simpler logic
|
100
100
|
variables ||= {}
|
101
101
|
@schema = schema
|
102
|
-
@context = schema.context_class.new(query: self,
|
102
|
+
@context = schema.context_class.new(query: self, values: context)
|
103
103
|
@warden = warden
|
104
104
|
@subscription_topic = subscription_topic
|
105
105
|
@root_value = root_value
|
@@ -317,7 +317,7 @@ module GraphQL
|
|
317
317
|
end
|
318
318
|
|
319
319
|
def_delegators :validation_pipeline, :validation_errors,
|
320
|
-
:analyzers, :ast_analyzers, :max_depth, :max_complexity
|
320
|
+
:analyzers, :ast_analyzers, :max_depth, :max_complexity, :validate_timeout_remaining
|
321
321
|
|
322
322
|
attr_accessor :analysis_errors
|
323
323
|
def valid?
|
@@ -395,7 +395,7 @@ module GraphQL
|
|
395
395
|
parse_error = nil
|
396
396
|
@document ||= begin
|
397
397
|
if query_string
|
398
|
-
GraphQL.parse(query_string, trace: self.current_trace)
|
398
|
+
GraphQL.parse(query_string, trace: self.current_trace, max_tokens: @schema.max_query_string_tokens)
|
399
399
|
end
|
400
400
|
rescue GraphQL::ParseError => err
|
401
401
|
parse_error = err
|
@@ -206,8 +206,8 @@ module GraphQL
|
|
206
206
|
# Used by the runtime.
|
207
207
|
# @api private
|
208
208
|
def prepare_value(obj, value, context: nil)
|
209
|
-
if
|
210
|
-
value = value
|
209
|
+
if type.unwrap.kind.input_object?
|
210
|
+
value = recursively_prepare_input_object(value, type)
|
211
211
|
end
|
212
212
|
|
213
213
|
Schema::Validator.validate!(validators, obj, context, value)
|
@@ -290,6 +290,7 @@ module GraphQL
|
|
290
290
|
# TODO code smell to access such a deeply-nested constant in a distant module
|
291
291
|
argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
|
292
292
|
value: resolved_loaded_value,
|
293
|
+
original_value: resolved_coerced_value,
|
293
294
|
definition: self,
|
294
295
|
default_used: default_used,
|
295
296
|
)
|
@@ -372,6 +373,21 @@ module GraphQL
|
|
372
373
|
|
373
374
|
private
|
374
375
|
|
376
|
+
def recursively_prepare_input_object(value, type)
|
377
|
+
if type.non_null?
|
378
|
+
type = type.of_type
|
379
|
+
end
|
380
|
+
|
381
|
+
if type.list? && !value.nil?
|
382
|
+
inner_type = type.of_type
|
383
|
+
value.map { |v| recursively_prepare_input_object(v, inner_type) }
|
384
|
+
elsif value.is_a?(GraphQL::Schema::InputObject)
|
385
|
+
value.prepare
|
386
|
+
else
|
387
|
+
value
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
375
391
|
def validate_input_type(input_type)
|
376
392
|
if input_type.is_a?(String) || input_type.is_a?(GraphQL::Schema::LateBoundType)
|
377
393
|
# Do nothing; assume this will be validated later
|
@@ -1,18 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'graphql/schema/base_64_bp'
|
4
|
-
|
2
|
+
require "base64"
|
5
3
|
module GraphQL
|
6
4
|
class Schema
|
7
5
|
# @api private
|
8
6
|
module Base64Encoder
|
9
7
|
def self.encode(unencoded_text, nonce: false)
|
10
|
-
|
8
|
+
Base64.urlsafe_encode64(unencoded_text, padding: false)
|
11
9
|
end
|
12
10
|
|
13
11
|
def self.decode(encoded_text, nonce: false)
|
14
12
|
# urlsafe_decode64 is for forward compatibility
|
15
|
-
|
13
|
+
Base64.urlsafe_decode64(encoded_text)
|
16
14
|
rescue ArgumentError
|
17
15
|
raise GraphQL::ExecutionError, "Invalid input: #{encoded_text.inspect}"
|
18
16
|
end
|
@@ -7,10 +7,16 @@ module GraphQL
|
|
7
7
|
class << self
|
8
8
|
# @see {Schema.from_definition}
|
9
9
|
def from_definition(schema_superclass, definition_string, parser: GraphQL.default_parser, **kwargs)
|
10
|
+
if defined?(parser::SchemaParser)
|
11
|
+
parser = parser::SchemaParser
|
12
|
+
end
|
10
13
|
from_document(schema_superclass, parser.parse(definition_string), **kwargs)
|
11
14
|
end
|
12
15
|
|
13
16
|
def from_definition_path(schema_superclass, definition_path, parser: GraphQL.default_parser, **kwargs)
|
17
|
+
if defined?(parser::SchemaParser)
|
18
|
+
parser = parser::SchemaParser
|
19
|
+
end
|
14
20
|
from_document(schema_superclass, parser.parse_file(definition_path), **kwargs)
|
15
21
|
end
|
16
22
|
|
@@ -120,10 +126,12 @@ module GraphQL
|
|
120
126
|
|
121
127
|
builder = self
|
122
128
|
|
129
|
+
found_types = types.values
|
123
130
|
schema_class = Class.new(schema_superclass) do
|
124
131
|
begin
|
125
132
|
# Add these first so that there's some chance of resolving late-bound types
|
126
|
-
|
133
|
+
add_type_and_traverse(found_types, root: false)
|
134
|
+
orphan_types(found_types.select { |t| t.respond_to?(:kind) && t.kind.object? })
|
127
135
|
query query_root_type
|
128
136
|
mutation mutation_root_type
|
129
137
|
subscription subscription_root_type
|