graphql 2.2.14 → 2.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f35b5aadaa5d2666f3f41f3347a0dec2a3799e747e0f70c4075ab9dd21e6c965
4
- data.tar.gz: b5f5b6ed45a287fdb8a7e29b0e45b13fc3093c9040a85a5c139db3c9df1ca41e
3
+ metadata.gz: 566d6d5c49b331b3f38e2f4d338c635bae02a86b14a004ff55f1eccccb973181
4
+ data.tar.gz: 0a8a048f8644e933252ca30459386e2049ac8c3093731709d344bf898f884731
5
5
  SHA512:
6
- metadata.gz: 3bb546944fd6a331ffaeba21130ce6253d372300b3ff3b7252d7d8da934a5ae7b4f95503128855e19fa5f860ef4e9868103f4212e068c556f0f9908ed051f0dc
7
- data.tar.gz: 0d2f779dbfd416d92ec46774178f7556dfdac7e5c3e70b54a816de32af5f849493da8771e679e7555209e9ea2ab62702e574bc1a9416a328e9afbc74ac8a4306
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
- next if argument.value.nil?
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.value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
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, argument.value)
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.value.each do |value|
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
- argument.value.each do |value|
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, object: nil, schema: schema, values: context)
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}|[^"\\]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/
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!(ESCAPES, ESCAPES_REPLACE)
305
- raw_string.gsub!(UTF_8) do |_matched_str|
306
- codepoint_1 = ($1 || $2).to_i(16)
307
- codepoint_2 = $3
308
-
309
- if codepoint_2
310
- codepoint_2 = codepoint_2.to_i(16)
311
- if (codepoint_1 >= 0xD800 && codepoint_1 <= 0xDBFF) && # leading surrogate
312
- (codepoint_2 >= 0xDC00 && codepoint_2 <= 0xDFFF) # trailing surrogate
313
- # A surrogate pair
314
- combined = ((codepoint_1 - 0xD800) * 0x400) + (codepoint_2 - 0xDC00) + 0x10000
315
- [combined].pack('U'.freeze)
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
- # Two separate code points
318
- [codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze)
334
+ [codepoint_1].pack('U'.freeze)
319
335
  end
320
336
  else
321
- [codepoint_1].pack('U'.freeze)
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
@@ -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:, object:)
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, object: root_value, values: context)
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
- orphan_types types.values
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
- input_obj_instance = self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil)
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
- orphan_types(types.values)
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
- if loaded_args.any?
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)
@@ -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
- schema_defn.tracer(tracer)
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, &block)
28
- instrument_execution("#{platform_key}", "#{trace_method}", &block)
27
+ def #{trace_method}(**data)
28
+ instrument_execution("#{platform_key}", "#{trace_method}") { super }
29
29
  end
30
30
  RUBY
31
31
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.2.14"
3
+ VERSION = "2.3.1"
4
4
  end
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.2.14
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-03-18 00:00:00.000000000 Z
11
+ date: 2024-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64