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.

Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/templates/schema.erb +3 -0
  3. data/lib/graphql/analysis/ast/field_usage.rb +36 -9
  4. data/lib/graphql/analysis/ast/visitor.rb +8 -0
  5. data/lib/graphql/analysis/ast.rb +10 -1
  6. data/lib/graphql/backtrace/inspect_result.rb +0 -12
  7. data/lib/graphql/coercion_error.rb +1 -9
  8. data/lib/graphql/dataloader/request.rb +5 -0
  9. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  10. data/lib/graphql/execution/interpreter/runtime.rb +9 -0
  11. data/lib/graphql/execution/interpreter.rb +90 -150
  12. data/lib/graphql/introspection/entry_points.rb +9 -3
  13. data/lib/graphql/introspection/schema_type.rb +3 -1
  14. data/lib/graphql/language/document_from_schema_definition.rb +2 -3
  15. data/lib/graphql/language/lexer.rb +48 -30
  16. data/lib/graphql/language/nodes.rb +2 -1
  17. data/lib/graphql/language/parser.rb +25 -11
  18. data/lib/graphql/language/printer.rb +4 -0
  19. data/lib/graphql/language.rb +60 -0
  20. data/lib/graphql/pagination/array_connection.rb +6 -6
  21. data/lib/graphql/query/context.rb +30 -33
  22. data/lib/graphql/query/validation_pipeline.rb +2 -2
  23. data/lib/graphql/query/variables.rb +3 -3
  24. data/lib/graphql/query.rb +3 -3
  25. data/lib/graphql/schema/argument.rb +18 -2
  26. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  27. data/lib/graphql/schema/build_from_definition.rb +9 -1
  28. data/lib/graphql/schema/field.rb +33 -30
  29. data/lib/graphql/schema/input_object.rb +1 -2
  30. data/lib/graphql/schema/interface.rb +5 -1
  31. data/lib/graphql/schema/loader.rb +2 -1
  32. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  33. data/lib/graphql/schema/mutation.rb +7 -0
  34. data/lib/graphql/schema/resolver.rb +19 -10
  35. data/lib/graphql/schema/unique_within_type.rb +1 -1
  36. data/lib/graphql/schema.rb +119 -28
  37. data/lib/graphql/static_validation/literal_validator.rb +1 -2
  38. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  39. data/lib/graphql/static_validation/validator.rb +3 -0
  40. data/lib/graphql/subscriptions/serialize.rb +2 -0
  41. data/lib/graphql/subscriptions.rb +0 -3
  42. data/lib/graphql/testing/helpers.rb +32 -6
  43. data/lib/graphql/tracing/data_dog_trace.rb +21 -34
  44. data/lib/graphql/tracing/data_dog_tracing.rb +7 -21
  45. data/lib/graphql/tracing/legacy_hooks_trace.rb +74 -0
  46. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  47. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
  48. data/lib/graphql/tracing/prometheus_trace.rb +2 -2
  49. data/lib/graphql/tracing/sentry_trace.rb +112 -0
  50. data/lib/graphql/tracing.rb +3 -1
  51. data/lib/graphql/version.rb +1 -1
  52. data/lib/graphql.rb +10 -2
  53. metadata +38 -23
  54. data/lib/graphql/schema/base_64_bp.rb +0 -26
  55. 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}|[^"\\]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/
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!(ESCAPES, ESCAPES_REPLACE)
303
- raw_string.gsub!(UTF_8) do |_matched_str|
304
- codepoint_1 = ($1 || $2).to_i(16)
305
- codepoint_2 = $3
306
-
307
- if codepoint_2
308
- codepoint_2 = codepoint_2.to_i(16)
309
- if (codepoint_1 >= 0xD800 && codepoint_1 <= 0xDBFF) && # leading surrogate
310
- (codepoint_2 >= 0xDC00 && codepoint_2 <= 0xDFFF) # trailing surrogate
311
- # A surrogate pair
312
- combined = ((codepoint_1 - 0xD800) * 0x400) + (codepoint_2 - 0xDC00) + 0x10000
313
- [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
314
333
  else
315
- # Two separate code points
316
- [codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze)
334
+ [codepoint_1].pack('U'.freeze)
317
335
  end
318
336
  else
319
- [codepoint_1].pack('U'.freeze)
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 false
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
- defs << Nodes::VariableDefinition.new(pos: loc, name: var_name, type: var_type, default_value: default_value, filename: @filename, source_string: @graphql_str)
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
- if token_name && Lexer::Punctuation.const_defined?(token_name)
733
- Lexer::Punctuation.const_get(token_name)
734
- elsif token_name == :ELLIPSIS
735
- "..."
736
- else
737
- @lexer.token_value
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)
@@ -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)-1
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 first
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:, 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]
@@ -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, 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
@@ -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 value.is_a?(GraphQL::Schema::InputObject)
210
- value = value.prepare
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
- Base64Bp.urlsafe_encode64(unencoded_text, padding: false)
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
- Base64Bp.urlsafe_decode64(encoded_text)
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
- 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