graphql 2.6.14 → 2.6.15

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.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/templates/graphql_controller.erb +7 -1
  3. data/lib/graphql/analysis/max_query_complexity.rb +2 -1
  4. data/lib/graphql/analysis/max_query_depth.rb +2 -1
  5. data/lib/graphql/analysis/query_complexity.rb +2 -1
  6. data/lib/graphql/analysis/query_depth.rb +1 -1
  7. data/lib/graphql/analysis/visitor.rb +14 -2
  8. data/lib/graphql/analysis.rb +4 -5
  9. data/lib/graphql/analysis_error.rb +1 -1
  10. data/lib/graphql/coercion_error.rb +1 -1
  11. data/lib/graphql/current.rb +5 -1
  12. data/lib/graphql/dashboard.rb +1 -1
  13. data/lib/graphql/date_encoding_error.rb +3 -1
  14. data/lib/graphql/duration_encoding_error.rb +3 -1
  15. data/lib/graphql/execution/directive_checks.rb +2 -1
  16. data/lib/graphql/execution/errors.rb +18 -7
  17. data/lib/graphql/execution/field_resolve_step.rb +3 -3
  18. data/lib/graphql/execution/input_values.rb +160 -120
  19. data/lib/graphql/execution/interpreter/runtime.rb +9 -5
  20. data/lib/graphql/execution/lookahead.rb +19 -8
  21. data/lib/graphql/execution/next.rb +1 -0
  22. data/lib/graphql/execution/runner.rb +19 -12
  23. data/lib/graphql/execution/selections_step.rb +1 -1
  24. data/lib/graphql/execution/variable_values.rb +248 -0
  25. data/lib/graphql/float_decoding_error.rb +3 -1
  26. data/lib/graphql/integer_decoding_error.rb +3 -1
  27. data/lib/graphql/introspection/base_object.rb +1 -1
  28. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  29. data/lib/graphql/introspection/directive_type.rb +3 -1
  30. data/lib/graphql/introspection/dynamic_fields.rb +3 -1
  31. data/lib/graphql/introspection/enum_value_type.rb +3 -1
  32. data/lib/graphql/introspection/field_type.rb +5 -1
  33. data/lib/graphql/introspection/type_kind_enum.rb +1 -1
  34. data/lib/graphql/introspection.rb +2 -1
  35. data/lib/graphql/language/comment.rb +2 -1
  36. data/lib/graphql/language/generation.rb +2 -1
  37. data/lib/graphql/language/lexer.rb +29 -21
  38. data/lib/graphql/language/parser.rb +10 -7
  39. data/lib/graphql/language.rb +1 -1
  40. data/lib/graphql/name_validator.rb +2 -1
  41. data/lib/graphql/pagination.rb +1 -1
  42. data/lib/graphql/query/fingerprint.rb +2 -1
  43. data/lib/graphql/query/partial.rb +4 -0
  44. data/lib/graphql/query.rb +8 -12
  45. data/lib/graphql/railtie.rb +1 -1
  46. data/lib/graphql/relay.rb +1 -1
  47. data/lib/graphql/rubocop.rb +1 -1
  48. data/lib/graphql/runtime_error.rb +1 -1
  49. data/lib/graphql/runtime_type_error.rb +1 -1
  50. data/lib/graphql/schema/always_visible.rb +3 -1
  51. data/lib/graphql/schema/base_64_encoder.rb +3 -1
  52. data/lib/graphql/schema/built_in_types.rb +2 -2
  53. data/lib/graphql/schema/directive/deprecated.rb +1 -1
  54. data/lib/graphql/schema/directive/include.rb +2 -1
  55. data/lib/graphql/schema/directive/skip.rb +2 -1
  56. data/lib/graphql/schema/directive/specified_by.rb +1 -1
  57. data/lib/graphql/schema/field/scope_extension.rb +2 -1
  58. data/lib/graphql/schema/field.rb +3 -0
  59. data/lib/graphql/schema/find_inherited_value.rb +5 -1
  60. data/lib/graphql/schema/member/graphql_type_names.rb +1 -1
  61. data/lib/graphql/schema/member/has_directives.rb +20 -22
  62. data/lib/graphql/schema/member/has_path.rb +2 -1
  63. data/lib/graphql/schema/member/has_unresolved_type_error.rb +2 -1
  64. data/lib/graphql/schema/member.rb +3 -2
  65. data/lib/graphql/schema/mutation.rb +6 -1
  66. data/lib/graphql/schema/object.rb +1 -1
  67. data/lib/graphql/schema/resolver.rb +2 -2
  68. data/lib/graphql/schema/type_expression.rb +3 -1
  69. data/lib/graphql/schema/validator/all_validator.rb +6 -3
  70. data/lib/graphql/static_validation/all_rules.rb +1 -1
  71. data/lib/graphql/static_validation/base_visitor.rb +125 -53
  72. data/lib/graphql/static_validation/definition_dependencies.rb +14 -2
  73. data/lib/graphql/static_validation/interpreter_visitor.rb +1 -1
  74. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +4 -1
  75. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  76. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +8 -4
  77. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -1
  78. data/lib/graphql/static_validation/rules/fragments_are_named.rb +2 -1
  79. data/lib/graphql/static_validation/rules/fragments_are_named_error.rb +4 -1
  80. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +6 -3
  81. data/lib/graphql/static_validation/rules/fragments_are_used.rb +2 -1
  82. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +3 -1
  83. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +3 -2
  84. data/lib/graphql/static_validation/rules/mutation_root_exists_error.rb +4 -1
  85. data/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb +4 -1
  86. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +4 -1
  87. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +9 -2
  88. data/lib/graphql/static_validation/rules/query_root_exists.rb +3 -2
  89. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +4 -1
  90. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error.rb +10 -4
  91. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +8 -6
  92. data/lib/graphql/static_validation/rules/subscription_root_exists_error.rb +4 -1
  93. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +5 -4
  94. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +2 -1
  95. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +2 -1
  96. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  97. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  98. data/lib/graphql/static_validation/validation_context.rb +49 -14
  99. data/lib/graphql/static_validation/validation_timeout_error.rb +4 -1
  100. data/lib/graphql/static_validation.rb +1 -2
  101. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +14 -13
  102. data/lib/graphql/testing.rb +1 -1
  103. data/lib/graphql/tracing/legacy_trace.rb +1 -1
  104. data/lib/graphql/tracing/null_trace.rb +1 -1
  105. data/lib/graphql/types/big_int.rb +4 -1
  106. data/lib/graphql/types/boolean.rb +3 -1
  107. data/lib/graphql/types/id.rb +3 -1
  108. data/lib/graphql/types/iso_8601_date.rb +1 -0
  109. data/lib/graphql/types/iso_8601_date_time.rb +6 -2
  110. data/lib/graphql/types/iso_8601_duration.rb +3 -0
  111. data/lib/graphql/types/relay/base_connection.rb +1 -1
  112. data/lib/graphql/types/relay/base_edge.rb +1 -1
  113. data/lib/graphql/types/relay/node.rb +1 -1
  114. data/lib/graphql/types/relay/page_info.rb +1 -1
  115. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -1
  116. data/lib/graphql/types/relay.rb +1 -1
  117. data/lib/graphql/types/string.rb +1 -0
  118. data/lib/graphql/types.rb +1 -1
  119. data/lib/graphql/version.rb +2 -2
  120. metadata +3 -4
  121. data/lib/graphql/query/variables.rb +0 -99
  122. data/lib/graphql/static_validation/literal_validator.rb +0 -157
@@ -0,0 +1,248 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+ module GraphQL
4
+ module Execution
5
+ # Validates and coerces a query's provided variables in a single pass.
6
+ #
7
+ # Reading methods (`#[]`, `#fetch`, `#to_h`, ...) return the values _as provided_ by the client
8
+ # (or the default value from the query string), so that existing callers keep working.
9
+ # Coerced values are available from `#coerced_value` for the runtime.
10
+ class VariableValues
11
+ extend Forwardable
12
+
13
+ # **Returns**
14
+ #
15
+ # - `Array<GraphQL::Query::VariableValidationError>` — Any errors encountered when coercing the provided variables
16
+ attr_reader :errors
17
+
18
+ attr_reader :context
19
+
20
+ def initialize(query, ast_variables, provided_variables)
21
+ @query = query
22
+ @context = query.context
23
+ @types = query.types
24
+ @schema = query.schema
25
+ @provided_variables = deep_stringify(provided_variables)
26
+ @errors = []
27
+ @storage = {}
28
+ @coerced_values = {}
29
+ build_values(ast_variables)
30
+ end
31
+
32
+ def_delegators :@storage, :length, :key?, :[], :fetch, :to_h, :each
33
+
34
+ # **Returns**
35
+ #
36
+ # - `Object` — The coerced value for the variable named `name`, or `nil` if it wasn't given
37
+ def coerced_value(name) # :nodoc:
38
+ @coerced_values[name]
39
+ end
40
+
41
+ private
42
+
43
+ UNEXPECTED_NULL_MESSAGE = "Expected value to not be null"
44
+ INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object."
45
+
46
+ def build_values(ast_variables)
47
+ max_errors = @schema.validate_max_errors
48
+ ast_variables.each do |ast_variable|
49
+ if max_errors && max_errors <= @errors.size
50
+ add_max_errors_reached_message
51
+ break
52
+ end
53
+ variable_type = GraphQL::Schema::TypeExpression.build_type(@types, ast_variable.type)
54
+ if variable_type.nil? || !variable_type.unwrap.kind.input?
55
+ # Pass -- it will get handled by a validator
56
+ next
57
+ end
58
+
59
+ variable_name = ast_variable.name
60
+ default_value = ast_variable.default_value
61
+ value_was_provided = @provided_variables.key?(variable_name)
62
+
63
+ if value_was_provided || default_value.nil?
64
+ provided_value = @provided_variables[variable_name]
65
+ validation_result = GraphQL::Query::InputValidationResult.new
66
+ remaining_errors = max_errors ? max_errors - @errors.size : nil
67
+ path = []
68
+ coerced_value = begin
69
+ coerce_variable_value(provided_value, variable_type, path, validation_result, remaining_errors)
70
+ rescue GraphQL::ExecutionError => ex
71
+ # TODO: This should really include the path to the problematic node in the variable value
72
+ validation_result = GraphQL::Query::InputValidationResult.from_problem(ex.message)
73
+ nil
74
+ end
75
+ if !validation_result.valid?
76
+ @errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result)
77
+ elsif value_was_provided
78
+ @storage[variable_name] = provided_value
79
+ @coerced_values[variable_name] = coerced_value
80
+ end
81
+ elsif default_value.is_a?(Language::Nodes::NullValue)
82
+ @storage[variable_name] = nil
83
+ @coerced_values[variable_name] = nil
84
+ else
85
+ @storage[variable_name] = default_value
86
+ @coerced_values[variable_name] = @query.input_values.value_from_ast(default_value, variable_type)
87
+ end
88
+ end
89
+ end
90
+
91
+ def coerce_variable_value(value, type, path, result, max_errors)
92
+ if type.non_null?
93
+ if value.nil?
94
+ result.add_problem(UNEXPECTED_NULL_MESSAGE, path.dup)
95
+ return nil
96
+ end
97
+ type = type.of_type
98
+ end
99
+
100
+ if value.nil?
101
+ nil
102
+ elsif type.list?
103
+ coerce_list_value(value, type.of_type, path, result, max_errors)
104
+ elsif type.kind.input_object?
105
+ coerce_input_object_value(value, type, path, result)
106
+ elsif type.kind.enum?
107
+ coerce_enum_value(value, type, path, result)
108
+ elsif type.kind.scalar?
109
+ coerce_scalar_value(value, type, path, result)
110
+ else
111
+ raise GraphQL::Error, "Unexpected input type: #{type.graphql_name}."
112
+ end
113
+ end
114
+
115
+ def coerce_list_value(value, item_type, path, result, max_errors)
116
+ items = value.is_a?(Array) ? value : [value]
117
+ coerced_items = Array.new(items.size)
118
+ invalid_items = 0
119
+ items.each_with_index do |item, idx|
120
+ item_result = GraphQL::Query::InputValidationResult.new
121
+ path.push(idx)
122
+ coerced_items[idx] = coerce_variable_value(item, item_type, path, item_result, nil)
123
+ path.pop
124
+ if !item_result.valid?
125
+ if max_errors
126
+ if invalid_items == max_errors
127
+ result.add_problem("Too many errors processing list variable, max validation error limit reached. Execution aborted", [*path, nil])
128
+ break
129
+ end
130
+ invalid_items += 1
131
+ end
132
+ item_result.problems.each do |problem|
133
+ result.add_problem(problem["explanation"], problem["path"], message: problem["message"], extensions: problem["extensions"])
134
+ end
135
+ end
136
+ end
137
+ coerced_items.freeze
138
+ end
139
+
140
+ def coerce_input_object_value(value, type, path, result)
141
+ if value.is_a?(Array) || !(value.respond_to?(:to_h) || value.respond_to?(:to_unsafe_h))
142
+ result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(value) }, path.dup)
143
+ return nil
144
+ end
145
+
146
+ coerced_obj = {}
147
+ value.each do |argument_name, argument_value|
148
+ path.push(argument_name)
149
+ argument = @types.argument(type, argument_name)
150
+ if argument.nil?
151
+ result.add_problem("Field is not defined on #{type.graphql_name}", path.dup)
152
+ else
153
+ if argument_value.nil? && argument.replace_null_with_default?
154
+ coerced_obj[argument.keyword] = argument.default_value
155
+ else
156
+ coerced_obj[argument.keyword] = coerce_variable_value(argument_value, argument.type, path, result, nil)
157
+ end
158
+ end
159
+ path.pop
160
+ end
161
+
162
+ @types.arguments(type).each do |argument|
163
+ if !value.key?(argument.graphql_name)
164
+ if argument.default_value?
165
+ coerced_obj[argument.keyword] = argument.default_value
166
+ elsif argument.type.non_null?
167
+ path.push(argument.graphql_name)
168
+ result.add_problem(UNEXPECTED_NULL_MESSAGE, path.dup)
169
+ path.pop
170
+ end
171
+ end
172
+ end
173
+
174
+ if type.one_of?
175
+ if value.size == 1
176
+ value.each do |name, v|
177
+ if v.nil?
178
+ result.add_problem("'#{type.graphql_name}' requires exactly one argument, but '#{name}' was `null`.", path.dup)
179
+ end
180
+ end
181
+ else
182
+ result.add_problem("'#{type.graphql_name}' requires exactly one argument, but #{value.size} were provided.", path.dup)
183
+ end
184
+ end
185
+
186
+ coerced_obj
187
+ end
188
+
189
+ def coerce_enum_value(value, type, path, result)
190
+ allowed_values = @types.enum_values(type)
191
+ matching_value = allowed_values.find { |v| v.graphql_name == value }
192
+ if matching_value.nil?
193
+ result.add_problem("Expected #{GraphQL::Language.serialize(value)} to be one of: #{allowed_values.map(&:graphql_name).join(', ')}", path.dup)
194
+ nil
195
+ else
196
+ begin
197
+ type.coerce_input(value, @context)
198
+ rescue GraphQL::UnauthorizedEnumValueError => enum_err
199
+ @schema.unauthorized_object(enum_err)
200
+ end
201
+ end
202
+ rescue GraphQL::ExecutionError => err
203
+ result.add_problem(err.message, path.dup)
204
+ nil
205
+ end
206
+
207
+ def coerce_scalar_value(value, type, path, result)
208
+ coerced_value = begin
209
+ type.coerce_input(value, @context)
210
+ rescue GraphQL::CoercionError => err
211
+ result.add_problem(err.message, path.dup, message: err.message, extensions: err.extensions)
212
+ return nil
213
+ rescue GraphQL::ExecutionError => err
214
+ result.add_problem(err.message, path.dup)
215
+ return nil
216
+ rescue StandardError => err
217
+ @query.handle_or_reraise(err)
218
+ end
219
+
220
+ if coerced_value.nil?
221
+ result.add_problem("Could not coerce value #{GraphQL::Language.serialize(value)} to #{type.graphql_name}", path.dup)
222
+ end
223
+ coerced_value
224
+ end
225
+
226
+ def add_max_errors_reached_message
227
+ message = "Too many errors processing variables, max validation error limit reached. Execution aborted"
228
+ validation_result = GraphQL::Query::InputValidationResult.from_problem(message)
229
+ @errors << GraphQL::Query::VariableValidationError.new(nil, nil, nil, validation_result, msg: message)
230
+ end
231
+
232
+ def deep_stringify(val)
233
+ case val
234
+ when Array
235
+ val.map { |v| deep_stringify(v) }
236
+ when Hash
237
+ new_val = {}
238
+ val.each do |k, v|
239
+ new_val[k.to_s] = deep_stringify(v)
240
+ end
241
+ new_val
242
+ else
243
+ val
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  # This error is raised when `Types::Float` is given a non-finite input value.
5
5
  class FloatDecodingError < GraphQL::RuntimeTypeError
6
6
  # The value which couldn't be decoded
7
+ #: Float
7
8
  attr_reader :float_value
8
9
 
10
+ #: (Float value) -> void
9
11
  def initialize(value)
10
12
  @float_value = value
11
13
  super("Float is not finite: #{value.inspect}.")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  # This error is raised when `Types::Int` is given an input value outside of 32-bit integer range.
5
5
  #
@@ -8,8 +8,10 @@ module GraphQL
8
8
  # See [GraphQL::Types::Int](rdoc-ref:GraphQL::Types::Int) which raises this error
9
9
  class IntegerDecodingError < GraphQL::RuntimeTypeError
10
10
  # The value which couldn't be decoded
11
+ #: Integer
11
12
  attr_reader :integer_value
12
13
 
14
+ #: (Integer value) -> void
13
15
  def initialize(value)
14
16
  @integer_value = value
15
17
  super("Integer out of bounds: #{value}. \nConsider using GraphQL::Types::BigInt instead.")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  module Introspection
5
5
  class BaseObject < GraphQL::Schema::Object
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  module Introspection
5
5
  class DirectiveLocationEnum < GraphQL::Schema::Enum
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  module Introspection
5
5
  class DirectiveType < Introspection::BaseObject
@@ -22,12 +22,14 @@ module GraphQL
22
22
 
23
23
  field :is_repeatable, Boolean, method: :repeatable?
24
24
 
25
+ #: (singleton(GraphQL::Schema::Directive) object, GraphQL::Query::Context context, include_deprecated: bool) -> Array[GraphQL::Schema::Argument]
25
26
  def self.resolve_args(object, context, include_deprecated:)
26
27
  args = context.types.arguments(object)
27
28
  args = args.reject(&:deprecation_reason) unless include_deprecated
28
29
  args
29
30
  end
30
31
 
32
+ #: (include_deprecated: bool) -> Array[GraphQL::Schema::Argument]
31
33
  def args(include_deprecated:)
32
34
  self.class.resolve_args(object, context, include_deprecated: include_deprecated)
33
35
  end
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  module Introspection
5
5
  class DynamicFields < Introspection::BaseObject
6
6
  field :__typename, String, "The name of this type", null: false, dynamic_introspection: true, resolve_each: true
7
7
 
8
+ #: -> ::String
8
9
  def __typename
9
10
  self.class.__typename(object, context)
10
11
  end
11
12
 
13
+ #: (untyped object, GraphQL::Query::Context context) -> ::String
12
14
  def self.__typename(object, context)
13
15
  object.class.graphql_name
14
16
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  module Introspection
5
5
  class EnumValueType < Introspection::BaseObject
@@ -12,10 +12,12 @@ module GraphQL
12
12
  field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated
13
13
  field :deprecation_reason, String
14
14
 
15
+ #: (GraphQL::Schema::EnumValue object, GraphQL::Query::Context context) -> bool
15
16
  def self.resolve_is_deprecated(object, context)
16
17
  !!object.deprecation_reason
17
18
  end
18
19
 
20
+ #: -> bool
19
21
  def is_deprecated
20
22
  self.class.resolve_is_deprecated(object, context)
21
23
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  module Introspection
5
5
  class FieldType < Introspection::BaseObject
@@ -15,20 +15,24 @@ module GraphQL
15
15
  field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated
16
16
  field :deprecation_reason, String
17
17
 
18
+ #: (GraphQL::Schema::Field object, GraphQL::Query::Context _context) -> bool
18
19
  def self.resolve_is_deprecated(object, _context)
19
20
  !!object.deprecation_reason
20
21
  end
21
22
 
23
+ #: -> bool
22
24
  def is_deprecated
23
25
  self.class.resolve_is_deprecated(object, context)
24
26
  end
25
27
 
28
+ #: (GraphQL::Schema::Field object, GraphQL::Query::Context context, include_deprecated: bool) -> Array[GraphQL::Schema::Argument]
26
29
  def self.resolve_args(object, context, include_deprecated:)
27
30
  args = context.types.arguments(object)
28
31
  args = args.reject(&:deprecation_reason) unless include_deprecated
29
32
  args
30
33
  end
31
34
 
35
+ #: (include_deprecated: bool) -> Array[GraphQL::Schema::Argument]
32
36
  def args(include_deprecated:)
33
37
  self.class.resolve_args(object, context, include_deprecated: include_deprecated)
34
38
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  module Introspection
5
5
  class TypeKindEnum < GraphQL::Schema::Enum
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  module Introspection
5
+ #: (?include_deprecated_args: bool, ?include_schema_description: bool, ?include_is_repeatable: bool, ?include_specified_by_url: bool, ?include_is_one_of: bool) -> String
5
6
  def self.query(include_deprecated_args: false, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
6
7
  # The introspection query to end all introspection queries, copied from
7
8
  # https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  module Language
5
5
  module Comment
6
+ #: (String str, ?indent: String) -> String
6
7
  def self.print(str, indent: '')
7
8
  lines = str.split("\n").map do |line|
8
9
  comment_str = "".dup
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  module Language
5
5
  # Exposes [.generate](rdoc-ref:.generate), which turns AST nodes back into query strings.
@@ -30,6 +30,7 @@ module GraphQL
30
30
  #
31
31
  # :call-seq:
32
32
  # generate(GraphQL::Language::Nodes::AbstractNode node, String indent:, GraphQL::Language::Printer printer:) -> String
33
+ #: (GraphQL::Language::Nodes::AbstractNode node, ?indent: String, ?printer: GraphQL::Language::Printer) -> String
33
34
  def generate(node, indent: "", printer: GraphQL::Language::Printer.new)
34
35
  printer.print(node, indent: indent)
35
36
  end
@@ -32,17 +32,16 @@ module GraphQL
32
32
  attr_reader :pos, :tokens_count
33
33
 
34
34
  def advance
35
- loop do
36
- @scanner.skip(IGNORE_REGEXP)
37
- if @scanner.skip(COMMENT_REGEXP)
38
- @tokens_count += 1
39
- if @tokens_count > @max_tokens
40
- @pos = @scanner.pos
41
- raise_parse_error("This query is too large to execute.")
42
- end
43
- next
35
+ @scanner.skip(IGNORE_REGEXP)
36
+ # A failed skip clears matched_size, which debug_token_value relies on for UNKNOWN_CHAR.
37
+ while @scanner.skip("#")
38
+ @scanner.skip(COMMENT_BODY_REGEXP)
39
+ @tokens_count += 1
40
+ if @tokens_count > @max_tokens
41
+ @pos = @scanner.pos
42
+ raise_parse_error("This query is too large to execute.")
44
43
  end
45
- break
44
+ @scanner.skip(IGNORE_REGEXP)
46
45
  end
47
46
 
48
47
  if @scanner.eos?
@@ -50,10 +49,10 @@ module GraphQL
50
49
  return false
51
50
  end
52
51
  @tokens_count += 1
52
+ @pos = @scanner.pos
53
53
  if @tokens_count > @max_tokens
54
54
  raise_parse_error("This query is too large to execute.")
55
55
  end
56
- @pos = @scanner.pos
57
56
  next_byte = @string.getbyte(@pos)
58
57
  next_byte_is_for = FIRST_BYTES[next_byte]
59
58
  case next_byte_is_for
@@ -82,11 +81,12 @@ module GraphQL
82
81
  @scanner.skip(IDENTIFIER_REGEXP)
83
82
  :IDENTIFIER
84
83
  when ByteFor::NUMBER
85
- if len = @scanner.skip(NUMERIC_REGEXP)
86
-
87
- if GraphQL.reject_numbers_followed_by_names
88
- new_pos = @scanner.pos
89
- peek_byte = @string.getbyte(new_pos)
84
+ if @scanner.skip(NUMERIC_REGEXP)
85
+ peek_byte = @string.getbyte(@scanner.pos)
86
+ if peek_byte == 46 || (peek_byte && peek_byte >= 48 && peek_byte <= 57)
87
+ raise_parse_error("Invalid number: unexpected character #{peek_byte.chr.inspect}")
88
+ end
89
+ if peek_byte && GraphQL.reject_numbers_followed_by_names
90
90
  next_first_byte = FIRST_BYTES[peek_byte]
91
91
  if next_first_byte == ByteFor::NAME || next_first_byte == ByteFor::IDENTIFIER
92
92
  number_part = token_value
@@ -152,7 +152,9 @@ module GraphQL
152
152
  "\\t" => "\t",
153
153
  }
154
154
  UTF_8 = /\\u(?:([\da-f]{4})|\{([\da-f]+)\})(?:\\u([\da-f]{4}))?/i
155
- VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o
155
+ # Surrogate pairs are combined by `UTF_8` during replacement; validating each escape alone keeps this unambiguous, so it can't backtrack exponentially.
156
+ VALID_UTF_8 = /\\u(?:[\da-f]{4}|\{[\da-f]+\})/i
157
+ VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{VALID_UTF_8})*\z/o
156
158
  ESCAPED = /(?:#{ESCAPES}|#{UTF_8})/o
157
159
 
158
160
  def string_value
@@ -183,13 +185,20 @@ module GraphQL
183
185
  end
184
186
 
185
187
  def line_number
186
- @scanner.string.byteslice(0, @pos).b.count("\n".b) + 1
188
+ line_prefix = @scanner.string.byteslice(0, @pos).b
189
+ line_number = line_prefix.count("\r") + line_prefix.count("\n") + 1
190
+ offset = 0 #: Integer?
191
+ while (offset = line_prefix.index("\r\n", offset))
192
+ line_number -= 1
193
+ offset += 2
194
+ end
195
+ line_number
187
196
  end
188
197
 
189
198
  def column_number
190
199
  line_prefix = @scanner.string.byteslice(0, @pos)
191
200
  line_prefix = line_prefix.b unless line_prefix.valid_encoding?
192
- newline_index = line_prefix.rindex("\n")
201
+ newline_index = line_prefix.rindex(/[\r\n]/)
193
202
  newline_index ? line_prefix.length - newline_index : line_prefix.length + 1
194
203
  end
195
204
 
@@ -198,12 +207,11 @@ module GraphQL
198
207
  end
199
208
 
200
209
  IGNORE_REGEXP = /[, \r\n\t\uFEFF]+/
201
- COMMENT_REGEXP = /\#[^\n]*/
210
+ COMMENT_BODY_REGEXP = /[^\n\r]*/
202
211
  IDENTIFIER_REGEXP = /[_A-Za-z][_0-9A-Za-z]*/
203
212
  INT_REGEXP = /-?(?:[0]|[1-9][0-9]*)/
204
213
  FLOAT_DECIMAL_REGEXP = /[.][0-9]+/
205
214
  FLOAT_EXP_REGEXP = /[eE][+-]?[0-9]+/
206
- # TODO: FLOAT_EXP_REGEXP should not be allowed to follow INT_REGEXP, integers are not allowed to have exponent parts.
207
215
  NUMERIC_REGEXP = /#{INT_REGEXP}(#{FLOAT_DECIMAL_REGEXP}#{FLOAT_EXP_REGEXP}|#{FLOAT_DECIMAL_REGEXP}|#{FLOAT_EXP_REGEXP})?/
208
216
 
209
217
  KEYWORDS = [
@@ -47,7 +47,7 @@ module GraphQL
47
47
  document
48
48
  end
49
49
  rescue SystemStackError
50
- raise GraphQL::ParseError.new("This query is too large to execute.", nil, nil, @query_str, filename: @filename)
50
+ raise GraphQL::ParseError.new("This query is too large to execute.", nil, nil, @graphql_str, filename: @filename)
51
51
  end
52
52
  end
53
53
 
@@ -89,8 +89,10 @@ module GraphQL
89
89
  la = []
90
90
  idx = 0 #: Integer?
91
91
  while idx
92
- idx = graphql_str.index("\n", idx)
92
+ idx = graphql_str.index(/[\r\n]/, idx)
93
93
  if idx
94
+ # Record the final byte so CRLF counts as one line break.
95
+ idx += 1 if graphql_str.getbyte(idx) == 13 && graphql_str.getbyte(idx + 1) == 10
94
96
  la << idx
95
97
  idx += 1
96
98
  end
@@ -172,7 +174,7 @@ module GraphQL
172
174
  var_type = self.type || raise_parse_error("Missing type definition for variable: $#{var_name}")
173
175
  default_value = if at?(:EQUALS)
174
176
  advance_token
175
- value
177
+ value(const: true)
176
178
  end
177
179
 
178
180
  directives = parse_directives #: untyped
@@ -491,7 +493,7 @@ module GraphQL
491
493
  type = self.type
492
494
  default_value = if at?(:EQUALS)
493
495
  advance_token
494
- value
496
+ value(const: true)
495
497
  else
496
498
  nil
497
499
  end
@@ -715,7 +717,7 @@ module GraphQL
715
717
  token_value
716
718
  end
717
719
 
718
- def value
720
+ def value(const: false)
719
721
  case token_name
720
722
  when :INT
721
723
  expect_token_value(:INT).to_i
@@ -738,7 +740,7 @@ module GraphQL
738
740
  advance_token
739
741
  list = []
740
742
  while !at?(:RBRACKET)
741
- list << value
743
+ list << value(const: const)
742
744
  end
743
745
  expect_token(:RBRACKET)
744
746
  list
@@ -750,11 +752,12 @@ module GraphQL
750
752
  loc = pos #: untyped
751
753
  n = parse_name
752
754
  expect_token(:COLON)
753
- args << Argument.new(pos: loc, name: n, value: value, filename: @filename, source: self)
755
+ args << Argument.new(pos: loc, name: n, value: value(const: const), filename: @filename, source: self)
754
756
  end
755
757
  expect_token(:RCURLY)
756
758
  InputObject.new(pos: start, arguments: args, filename: @filename, source: self)
757
759
  when :VAR_SIGN
760
+ raise_parse_error("Unexpected variable in constant value") if const
758
761
  loc = pos
759
762
  advance_token
760
763
  VariableIdentifier.new(pos: loc, name: parse_name, filename: @filename, source: self)
@@ -59,7 +59,7 @@ module GraphQL
59
59
  if scanner.skip('"""')
60
60
  inside_triple_quoted_string = !inside_triple_quoted_string
61
61
  new_query_str && (new_query_str << scanner.matched)
62
- elsif scanner.skip(/(?:\\"|[^"\n\r])+/m)
62
+ elsif scanner.skip(inside_single_quoted_string ? /(?>\\[\\"]|[^"\n\r])+/ : /(?:\\"|[^"\n\r])+/m)
63
63
  new_query_str && (new_query_str << scanner.matched)
64
64
  elsif scanner.skip('"')
65
65
  new_query_str && (new_query_str << '"')
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  module GraphQL
4
4
  class NameValidator
5
5
  VALID_NAME_REGEX = /^[_a-zA-Z][_a-zA-Z0-9]*$/
6
6
 
7
+ #: (String | Symbol name) -> void
7
8
  def self.validate!(name)
8
9
  name = name.is_a?(String) ? name : name.to_s
9
10
  raise GraphQL::InvalidNameError.new(name, VALID_NAME_REGEX) unless name.match?(VALID_NAME_REGEX)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
  require "graphql/pagination/array_connection"
4
4
  require "graphql/pagination/active_record_relation_connection"
5
5
  require "graphql/pagination/connections"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: true
2
+ # typed: strict
3
3
 
4
4
  require 'digest/sha2'
5
5
 
@@ -20,6 +20,7 @@ module GraphQL
20
20
  #
21
21
  # :call-seq:
22
22
  # generate(input_str) -> String
23
+ #: (String input_str) -> String
23
24
  def self.generate(input_str)
24
25
  # Implemented to be:
25
26
  # - Short (and uniform) length
@@ -102,6 +102,10 @@ module GraphQL
102
102
  @query.variables
103
103
  end
104
104
 
105
+ def input_values
106
+ @query.input_values
107
+ end
108
+
105
109
  def fragments
106
110
  @query.fragments
107
111
  end