graphql 2.2.14 → 2.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/graphql/templates/schema.erb +3 -0
- data/lib/graphql/analysis/ast/field_usage.rb +7 -5
- data/lib/graphql/analysis/ast/visitor.rb +8 -0
- data/lib/graphql/backtrace/inspect_result.rb +0 -12
- data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
- data/lib/graphql/language/document_from_schema_definition.rb +1 -1
- data/lib/graphql/language/lexer.rb +46 -30
- data/lib/graphql/language/parser.rb +14 -4
- data/lib/graphql/language.rb +60 -0
- data/lib/graphql/query/context.rb +30 -33
- data/lib/graphql/query.rb +2 -2
- data/lib/graphql/schema/argument.rb +1 -0
- data/lib/graphql/schema/build_from_definition.rb +9 -1
- data/lib/graphql/schema/input_object.rb +1 -2
- data/lib/graphql/schema/loader.rb +2 -1
- data/lib/graphql/schema/mutation.rb +7 -0
- data/lib/graphql/schema/resolver.rb +10 -5
- data/lib/graphql/schema.rb +27 -1
- data/lib/graphql/testing/helpers.rb +24 -2
- data/lib/graphql/tracing/platform_tracing.rb +1 -1
- data/lib/graphql/tracing/prometheus_trace.rb +2 -2
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +9 -2
- 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: 566d6d5c49b331b3f38e2f4d338c635bae02a86b14a004ff55f1eccccb973181
|
4
|
+
data.tar.gz: 0a8a048f8644e933252ca30459386e2049ac8c3093731709d344bf898f884731
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12136553e963ed98012887215d1f21efd31c2a3bacd40ada6836ca3884680cb4ac6a8c7a1fabaa7fc8699c0ba6db9f20418ae068a74694daef8db1d13e506933
|
7
|
+
data.tar.gz: 1faab9e89cf122660fb277df551ba026685cefeaa2fba9d83b590a5a18d4c4dc1202712ecf404c2998eb12c27a1219bd548944ef5f65daaad79371163ca66b43
|
@@ -26,6 +26,9 @@ class <%= schema_name %> < GraphQL::Schema
|
|
26
26
|
raise(GraphQL::RequiredImplementationMissingError)
|
27
27
|
end
|
28
28
|
|
29
|
+
# Limit the size of incoming queries:
|
30
|
+
max_query_string_tokens(5000)
|
31
|
+
|
29
32
|
# Stop validating when it encounters this many errors:
|
30
33
|
validate_max_errors(100)
|
31
34
|
end
|
@@ -41,7 +41,9 @@ module GraphQL
|
|
41
41
|
@used_deprecated_arguments << argument.definition.path
|
42
42
|
end
|
43
43
|
|
44
|
-
|
44
|
+
arg_val = argument.value
|
45
|
+
|
46
|
+
next if arg_val.nil?
|
45
47
|
|
46
48
|
argument_type = argument.definition.type
|
47
49
|
if argument_type.non_null?
|
@@ -49,18 +51,18 @@ module GraphQL
|
|
49
51
|
end
|
50
52
|
|
51
53
|
if argument_type.kind.input_object?
|
52
|
-
extract_deprecated_arguments(argument.
|
54
|
+
extract_deprecated_arguments(argument.original_value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
|
53
55
|
elsif argument_type.kind.enum?
|
54
|
-
extract_deprecated_enum_value(argument_type,
|
56
|
+
extract_deprecated_enum_value(argument_type, arg_val)
|
55
57
|
elsif argument_type.list?
|
56
58
|
inner_type = argument_type.unwrap
|
57
59
|
case inner_type.kind
|
58
60
|
when TypeKinds::INPUT_OBJECT
|
59
|
-
argument.
|
61
|
+
argument.original_value.each do |value|
|
60
62
|
extract_deprecated_arguments(value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
|
61
63
|
end
|
62
64
|
when TypeKinds::ENUM
|
63
|
-
|
65
|
+
arg_val.each do |value|
|
64
66
|
extract_deprecated_enum_value(inner_type, value)
|
65
67
|
end
|
66
68
|
else
|
@@ -118,8 +118,12 @@ module GraphQL
|
|
118
118
|
def on_inline_fragment(node, parent)
|
119
119
|
on_fragment_with_type(node) do
|
120
120
|
@path.push("...#{node.type ? " on #{node.type.name}" : ""}")
|
121
|
+
@skipping = @skip_stack.last || skip?(node)
|
122
|
+
@skip_stack << @skipping
|
123
|
+
|
121
124
|
call_on_enter_inline_fragment(node, parent)
|
122
125
|
super
|
126
|
+
@skipping = @skip_stack.pop
|
123
127
|
call_on_leave_inline_fragment(node, parent)
|
124
128
|
end
|
125
129
|
end
|
@@ -187,9 +191,13 @@ module GraphQL
|
|
187
191
|
|
188
192
|
def on_fragment_spread(node, parent)
|
189
193
|
@path.push("... #{node.name}")
|
194
|
+
@skipping = @skip_stack.last || skip?(node)
|
195
|
+
@skip_stack << @skipping
|
196
|
+
|
190
197
|
call_on_enter_fragment_spread(node, parent)
|
191
198
|
enter_fragment_spread_inline(node)
|
192
199
|
super
|
200
|
+
@skipping = @skip_stack.pop
|
193
201
|
leave_fragment_spread_inline(node)
|
194
202
|
call_on_leave_fragment_spread(node, parent)
|
195
203
|
@path.pop
|
@@ -16,12 +16,6 @@ module GraphQL
|
|
16
16
|
"[" +
|
17
17
|
obj.map { |v| inspect_truncated(v) }.join(", ") +
|
18
18
|
"]"
|
19
|
-
when Query::Context::SharedMethods
|
20
|
-
if obj.invalid_null?
|
21
|
-
"nil"
|
22
|
-
else
|
23
|
-
inspect_truncated(obj.value)
|
24
|
-
end
|
25
19
|
else
|
26
20
|
inspect_truncated(obj)
|
27
21
|
end
|
@@ -33,12 +27,6 @@ module GraphQL
|
|
33
27
|
"{...}"
|
34
28
|
when Array
|
35
29
|
"[...]"
|
36
|
-
when Query::Context::SharedMethods
|
37
|
-
if obj.invalid_null?
|
38
|
-
"nil"
|
39
|
-
else
|
40
|
-
inspect_truncated(obj.value)
|
41
|
-
end
|
42
30
|
when GraphQL::Execution::Lazy
|
43
31
|
"(unresolved)"
|
44
32
|
else
|
@@ -6,15 +6,19 @@ module GraphQL
|
|
6
6
|
# A container for metadata regarding arguments present in a GraphQL query.
|
7
7
|
# @see Interpreter::Arguments#argument_values for a hash of these objects.
|
8
8
|
class ArgumentValue
|
9
|
-
def initialize(definition:, value:, default_used:)
|
9
|
+
def initialize(definition:, value:, original_value:, default_used:)
|
10
10
|
@definition = definition
|
11
11
|
@value = value
|
12
|
+
@original_value = original_value
|
12
13
|
@default_used = default_used
|
13
14
|
end
|
14
15
|
|
15
16
|
# @return [Object] The Ruby-ready value for this Argument
|
16
17
|
attr_reader :value
|
17
18
|
|
19
|
+
# @return [Object] The value of this argument _before_ `prepare` is applied.
|
20
|
+
attr_reader :original_value
|
21
|
+
|
18
22
|
# @return [GraphQL::Schema::Argument] The definition instance for this argument
|
19
23
|
attr_reader :definition
|
20
24
|
|
@@ -24,7 +24,7 @@ module GraphQL
|
|
24
24
|
@include_built_in_directives = include_built_in_directives
|
25
25
|
@include_one_of = false
|
26
26
|
|
27
|
-
schema_context = schema.context_class.new(query: nil,
|
27
|
+
schema_context = schema.context_class.new(query: nil, schema: schema, values: context)
|
28
28
|
|
29
29
|
|
30
30
|
@warden = @schema.warden_class.new(
|
@@ -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
|
@@ -109,29 +126,27 @@ module GraphQL
|
|
109
126
|
}
|
110
127
|
UTF_8 = /\\u(?:([\dAa-f]{4})|\{([\da-f]{4,})\})(?:\\u([\dAa-f]{4}))?/i
|
111
128
|
VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o
|
129
|
+
ESCAPED = /(?:#{ESCAPES}|#{UTF_8})/o
|
112
130
|
|
113
131
|
def string_value
|
114
132
|
str = token_value
|
115
133
|
is_block = str.start_with?('"""')
|
116
134
|
if is_block
|
117
135
|
str.gsub!(/\A"""|"""\z/, '')
|
136
|
+
return Language::BlockString.trim_whitespace(str)
|
118
137
|
else
|
119
138
|
str.gsub!(/\A"|"\z/, '')
|
120
|
-
end
|
121
|
-
|
122
|
-
if is_block
|
123
|
-
str = Language::BlockString.trim_whitespace(str)
|
124
|
-
end
|
125
|
-
|
126
|
-
if !str.valid_encoding? || !str.match?(VALID_STRING)
|
127
|
-
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
128
|
-
else
|
129
|
-
Lexer.replace_escaped_characters_in_place(str)
|
130
139
|
|
131
|
-
if !str.valid_encoding?
|
140
|
+
if !str.valid_encoding? || !str.match?(VALID_STRING)
|
132
141
|
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
133
142
|
else
|
134
|
-
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
|
135
150
|
end
|
136
151
|
end
|
137
152
|
end
|
@@ -158,6 +173,7 @@ module GraphQL
|
|
158
173
|
INT_REGEXP = /-?(?:[0]|[1-9][0-9]*)/
|
159
174
|
FLOAT_DECIMAL_REGEXP = /[.][0-9]+/
|
160
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.
|
161
177
|
NUMERIC_REGEXP = /#{INT_REGEXP}(#{FLOAT_DECIMAL_REGEXP}#{FLOAT_EXP_REGEXP}|#{FLOAT_DECIMAL_REGEXP}|#{FLOAT_EXP_REGEXP})?/
|
162
178
|
|
163
179
|
KEYWORDS = [
|
@@ -252,11 +268,10 @@ module GraphQL
|
|
252
268
|
FOUR_DIGIT_UNICODE = /#{UNICODE_DIGIT}{4}/
|
253
269
|
N_DIGIT_UNICODE = %r{#{Punctuation::LCURLY}#{UNICODE_DIGIT}{4,}#{Punctuation::RCURLY}}x
|
254
270
|
UNICODE_ESCAPE = %r{\\u(?:#{FOUR_DIGIT_UNICODE}|#{N_DIGIT_UNICODE})}
|
255
|
-
# # https://graphql.github.io/graphql-spec/June2018/#sec-String-Value
|
256
271
|
STRING_ESCAPE = %r{[\\][\\/bfnrt]}
|
257
272
|
BLOCK_QUOTE = '"""'
|
258
273
|
ESCAPED_QUOTE = /\\"/;
|
259
|
-
STRING_CHAR = /#{ESCAPED_QUOTE}|[^"
|
274
|
+
STRING_CHAR = /#{ESCAPED_QUOTE}|[^"\\\n\r]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/
|
260
275
|
QUOTED_STRING_REGEXP = %r{#{QUOTE} (?:#{STRING_CHAR})* #{QUOTE}}x
|
261
276
|
BLOCK_STRING_REGEXP = %r{
|
262
277
|
#{BLOCK_QUOTE}
|
@@ -301,24 +316,25 @@ module GraphQL
|
|
301
316
|
# Replace any escaped unicode or whitespace with the _actual_ characters
|
302
317
|
# To avoid allocating more strings, this modifies the string passed into it
|
303
318
|
def self.replace_escaped_characters_in_place(raw_string)
|
304
|
-
raw_string.gsub!(
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
(
|
313
|
-
|
314
|
-
|
315
|
-
|
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
|
316
333
|
else
|
317
|
-
|
318
|
-
[codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze)
|
334
|
+
[codepoint_1].pack('U'.freeze)
|
319
335
|
end
|
320
336
|
else
|
321
|
-
[
|
337
|
+
ESCAPES_REPLACE[matched_str]
|
322
338
|
end
|
323
339
|
end
|
324
340
|
nil
|
@@ -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
|
@@ -732,6 +733,9 @@ module GraphQL
|
|
732
733
|
# Only use when we care about the expected token's value
|
733
734
|
def expect_token_value(tok)
|
734
735
|
token_value = @lexer.token_value
|
736
|
+
if @dedup_identifiers
|
737
|
+
token_value = -token_value
|
738
|
+
end
|
735
739
|
expect_token(tok)
|
736
740
|
token_value
|
737
741
|
end
|
@@ -741,6 +745,12 @@ module GraphQL
|
|
741
745
|
def debug_token_value
|
742
746
|
@lexer.debug_token_value(token_name)
|
743
747
|
end
|
748
|
+
class SchemaParser < Parser
|
749
|
+
def initialize(*args, **kwargs)
|
750
|
+
super
|
751
|
+
@dedup_identifiers = true
|
752
|
+
end
|
753
|
+
end
|
744
754
|
end
|
745
755
|
end
|
746
756
|
end
|
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
|
@@ -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]
|
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
|
@@ -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
|
@@ -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
|
)
|
@@ -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
|
@@ -215,8 +215,7 @@ module GraphQL
|
|
215
215
|
if resolved_arguments.is_a?(GraphQL::Error)
|
216
216
|
raise resolved_arguments
|
217
217
|
else
|
218
|
-
|
219
|
-
input_obj_instance.prepare
|
218
|
+
self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil)
|
220
219
|
end
|
221
220
|
end
|
222
221
|
end
|
@@ -32,7 +32,8 @@ module GraphQL
|
|
32
32
|
end
|
33
33
|
|
34
34
|
Class.new(GraphQL::Schema) do
|
35
|
-
|
35
|
+
add_type_and_traverse(types.values, root: false)
|
36
|
+
orphan_types(types.values.select { |t| t.kind.object? })
|
36
37
|
directives(directives)
|
37
38
|
description(schema["description"])
|
38
39
|
|
@@ -62,6 +62,13 @@ module GraphQL
|
|
62
62
|
extend GraphQL::Schema::Member::HasFields
|
63
63
|
extend GraphQL::Schema::Resolver::HasPayloadType
|
64
64
|
|
65
|
+
# @api private
|
66
|
+
def call_resolve(_args_hash)
|
67
|
+
# Clear any cached values from `loads` or authorization:
|
68
|
+
dataloader.clear_cache
|
69
|
+
super
|
70
|
+
end
|
71
|
+
|
65
72
|
class << self
|
66
73
|
def visible?(context)
|
67
74
|
true
|
@@ -103,11 +103,7 @@ module GraphQL
|
|
103
103
|
end
|
104
104
|
elsif authorized_val
|
105
105
|
# Finally, all the hooks have passed, so resolve it
|
106
|
-
|
107
|
-
public_send(self.class.resolve_method, **loaded_args)
|
108
|
-
else
|
109
|
-
public_send(self.class.resolve_method)
|
110
|
-
end
|
106
|
+
call_resolve(loaded_args)
|
111
107
|
else
|
112
108
|
raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
|
113
109
|
end
|
@@ -117,6 +113,15 @@ module GraphQL
|
|
117
113
|
end
|
118
114
|
end
|
119
115
|
|
116
|
+
# @api private {GraphQL::Schema::Mutation} uses this to clear the dataloader cache
|
117
|
+
def call_resolve(args_hash)
|
118
|
+
if args_hash.any?
|
119
|
+
public_send(self.class.resolve_method, **args_hash)
|
120
|
+
else
|
121
|
+
public_send(self.class.resolve_method)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
120
125
|
# Do the work. Everything happens here.
|
121
126
|
# @return [Object] An object corresponding to the return type
|
122
127
|
def resolve(**args)
|
data/lib/graphql/schema.rb
CHANGED
@@ -643,6 +643,17 @@ module GraphQL
|
|
643
643
|
end
|
644
644
|
end
|
645
645
|
|
646
|
+
# A limit on the number of tokens to accept on incoming query strings.
|
647
|
+
# Use this to prevent parsing maliciously-large query strings.
|
648
|
+
# @return [nil, Integer]
|
649
|
+
def max_query_string_tokens(new_max_tokens = NOT_CONFIGURED)
|
650
|
+
if NOT_CONFIGURED.equal?(new_max_tokens)
|
651
|
+
defined?(@max_query_string_tokens) ? @max_query_string_tokens : find_inherited_value(:max_query_string_tokens)
|
652
|
+
else
|
653
|
+
@max_query_string_tokens = new_max_tokens
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
646
657
|
def default_page_size(new_default_page_size = nil)
|
647
658
|
if new_default_page_size
|
648
659
|
@default_page_size = new_default_page_size
|
@@ -861,6 +872,17 @@ module GraphQL
|
|
861
872
|
def orphan_types(*new_orphan_types)
|
862
873
|
if new_orphan_types.any?
|
863
874
|
new_orphan_types = new_orphan_types.flatten
|
875
|
+
non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object }
|
876
|
+
if non_object_types.any?
|
877
|
+
raise ArgumentError, <<~ERR
|
878
|
+
Only object type classes should be added as `orphan_types(...)`.
|
879
|
+
|
880
|
+
- Remove these no-op types from `orphan_types`: #{non_object_types.map { |t| "#{t.inspect} (#{t.kind.name})"}.join(", ")}
|
881
|
+
- See https://graphql-ruby.org/type_definitions/interfaces.html#orphan-types
|
882
|
+
|
883
|
+
To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
|
884
|
+
ERR
|
885
|
+
end
|
864
886
|
add_type_and_traverse(new_orphan_types, root: false)
|
865
887
|
own_orphan_types.concat(new_orphan_types.flatten)
|
866
888
|
end
|
@@ -1129,7 +1151,11 @@ module GraphQL
|
|
1129
1151
|
}.freeze
|
1130
1152
|
end
|
1131
1153
|
|
1132
|
-
def tracer(new_tracer)
|
1154
|
+
def tracer(new_tracer, silence_deprecation_warning: false)
|
1155
|
+
if !silence_deprecation_warning
|
1156
|
+
warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
|
1157
|
+
warn " #{caller(1, 1).first}"
|
1158
|
+
end
|
1133
1159
|
default_trace = trace_class_for(:default, build: true)
|
1134
1160
|
if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers)
|
1135
1161
|
trace_with(GraphQL::Tracing::CallLegacyTracers)
|
@@ -39,9 +39,9 @@ module GraphQL
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
def run_graphql_field(schema, field_path, object, arguments: {}, context: {})
|
42
|
+
def run_graphql_field(schema, field_path, object, arguments: {}, context: {}, ast_node: nil, lookahead: nil)
|
43
43
|
type_name, *field_names = field_path.split(".")
|
44
|
-
dummy_query = GraphQL::Query.new(schema, context: context)
|
44
|
+
dummy_query = GraphQL::Query.new(schema, "{ __typename }", context: context)
|
45
45
|
query_context = dummy_query.context
|
46
46
|
object_type = dummy_query.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
|
47
47
|
if object_type
|
@@ -57,6 +57,28 @@ module GraphQL
|
|
57
57
|
dummy_query.context.dataloader.run_isolated {
|
58
58
|
field_args = visible_field.coerce_arguments(graphql_result, arguments, query_context)
|
59
59
|
field_args = schema.sync_lazy(field_args)
|
60
|
+
if visible_field.extras.any?
|
61
|
+
extra_args = {}
|
62
|
+
visible_field.extras.each do |extra|
|
63
|
+
extra_args[extra] = case extra
|
64
|
+
when :ast_node
|
65
|
+
ast_node ||= GraphQL::Language::Nodes::Field.new(name: visible_field.graphql_name)
|
66
|
+
when :lookahead
|
67
|
+
lookahead ||= begin
|
68
|
+
ast_node ||= GraphQL::Language::Nodes::Field.new(name: visible_field.graphql_name)
|
69
|
+
Execution::Lookahead.new(
|
70
|
+
query: dummy_query,
|
71
|
+
ast_nodes: [ast_node],
|
72
|
+
field: visible_field,
|
73
|
+
)
|
74
|
+
end
|
75
|
+
else
|
76
|
+
raise ArgumentError, "This extra isn't supported in `run_graphql_field` yet: `#{extra.inspect}`. Open an issue on GitHub to request it: https://github.com/rmosolgo/graphql-ruby/issues/new"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
field_args = field_args.merge_extras(extra_args)
|
81
|
+
end
|
60
82
|
graphql_result = visible_field.resolve(graphql_result, field_args.keyword_arguments, query_context)
|
61
83
|
graphql_result = schema.sync_lazy(graphql_result)
|
62
84
|
}
|
@@ -86,7 +86,7 @@ module GraphQL
|
|
86
86
|
else
|
87
87
|
warn("`use(#{self.name})` and `Tracing::PlatformTracing` are deprecated. Use a `trace_with(...)` module instead. More info: https://graphql-ruby.org/queries/tracing.html. Please open an issue on the GraphQL-Ruby repo if you want to discuss further!")
|
88
88
|
tracer = self.new(**options)
|
89
|
-
|
89
|
+
schema_defn.tracer(tracer, silence_deprecation_warning: true)
|
90
90
|
end
|
91
91
|
end
|
92
92
|
end
|
@@ -24,8 +24,8 @@ module GraphQL
|
|
24
24
|
'execute_query_lazy' => "graphql.execute",
|
25
25
|
}.each do |trace_method, platform_key|
|
26
26
|
module_eval <<-RUBY, __FILE__, __LINE__
|
27
|
-
def #{trace_method}(**data
|
28
|
-
instrument_execution("#{platform_key}", "#{trace_method}"
|
27
|
+
def #{trace_method}(**data)
|
28
|
+
instrument_execution("#{platform_key}", "#{trace_method}") { super }
|
29
29
|
end
|
30
30
|
RUBY
|
31
31
|
end
|
data/lib/graphql/version.rb
CHANGED
data/lib/graphql.rb
CHANGED
@@ -42,8 +42,8 @@ This is probably a bug in GraphQL-Ruby, please report this error on GitHub: http
|
|
42
42
|
# Turn a query string or schema definition into an AST
|
43
43
|
# @param graphql_string [String] a GraphQL query string or schema definition
|
44
44
|
# @return [GraphQL::Language::Nodes::Document]
|
45
|
-
def self.parse(graphql_string, trace: GraphQL::Tracing::NullTrace, filename: nil)
|
46
|
-
default_parser.parse(graphql_string, trace: trace, filename: filename)
|
45
|
+
def self.parse(graphql_string, trace: GraphQL::Tracing::NullTrace, filename: nil, max_tokens: nil)
|
46
|
+
default_parser.parse(graphql_string, trace: trace, filename: filename, max_tokens: max_tokens)
|
47
47
|
end
|
48
48
|
|
49
49
|
# Read the contents of `filename` and parse them as GraphQL
|
@@ -74,6 +74,13 @@ This is probably a bug in GraphQL-Ruby, please report this error on GitHub: http
|
|
74
74
|
EMPTY_HASH = {}.freeze
|
75
75
|
EMPTY_ARRAY = [].freeze
|
76
76
|
end
|
77
|
+
|
78
|
+
class << self
|
79
|
+
# If true, the parser should raise when an integer or float is followed immediately by an identifier (instead of a space or punctuation)
|
80
|
+
attr_accessor :reject_numbers_followed_by_names
|
81
|
+
end
|
82
|
+
|
83
|
+
self.reject_numbers_followed_by_names = false
|
77
84
|
end
|
78
85
|
|
79
86
|
# Order matters for these:
|
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: 2.
|
4
|
+
version: 2.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base64
|