graphql 1.11.6 → 1.11.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/object_generator.rb +2 -0
  3. data/lib/graphql.rb +1 -0
  4. data/lib/graphql/execution/interpreter/arguments.rb +21 -6
  5. data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
  6. data/lib/graphql/execution/interpreter/runtime.rb +53 -39
  7. data/lib/graphql/integer_decoding_error.rb +17 -0
  8. data/lib/graphql/invalid_null_error.rb +1 -1
  9. data/lib/graphql/query.rb +4 -1
  10. data/lib/graphql/query/context.rb +4 -1
  11. data/lib/graphql/query/validation_pipeline.rb +1 -1
  12. data/lib/graphql/relay/array_connection.rb +2 -2
  13. data/lib/graphql/schema.rb +16 -3
  14. data/lib/graphql/schema/default_type_error.rb +2 -0
  15. data/lib/graphql/schema/field.rb +20 -11
  16. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  17. data/lib/graphql/schema/member/has_arguments.rb +51 -52
  18. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  19. data/lib/graphql/static_validation.rb +1 -0
  20. data/lib/graphql/static_validation/all_rules.rb +1 -0
  21. data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
  22. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  23. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  24. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  25. data/lib/graphql/static_validation/validator.rb +28 -9
  26. data/lib/graphql/tracing/platform_tracing.rb +1 -1
  27. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  28. data/lib/graphql/types/int.rb +9 -2
  29. data/lib/graphql/types/relay/base_connection.rb +2 -1
  30. data/lib/graphql/types/relay/base_edge.rb +2 -1
  31. data/lib/graphql/types/string.rb +7 -1
  32. data/lib/graphql/version.rb +1 -1
  33. data/readme.md +1 -1
  34. metadata +10 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b6e1d6fb5063c843becb61784cb776964a380af5a2fc5a98054d2554ddf4caa
4
- data.tar.gz: c41027cdf69f8c07bed157075c70e95936f3d26922f0b1d38f7759352a35ab43
3
+ metadata.gz: af3eaf2a85e27ab7a366e27a98d42e4372b93c7590c99fb6fb12fccb48ef37f9
4
+ data.tar.gz: 62be23c2f74539be298db79081b148db5d004325ef6ae6f1b007711659110a01
5
5
  SHA512:
6
- metadata.gz: 3dc4e13b30d51c32775785b9ed4a11940bc2cb541866877e6eef48bc621956700ddf057c597d2bc5a9fa09ca863e01ba84295e414e488e22ee03b94c28bc4003
7
- data.tar.gz: 70f5ba8c04e13b83af431db1e8edea297a3c43acc6f6356668094349b577fe7de4e8cdb029aaf8ce70cc243664915ac7dac38612badd0acb720ce57464769cec
6
+ metadata.gz: f12858fd3c28da572082443e022ed2a5281bda7eda01a35b4c6bee65273b5744ae43904c5bf6202a5d461d67de6ee1ec9d1210a121181d5876a3aee3f37b4f1e
7
+ data.tar.gz: ed4f7ba1b52e9526d4f4a630f2ca06234816bd6be7522e29a98015a8bfa94a8bccdf98a03139ef9f45b266a8f62cb9d922c93ddebf797125d91d04a2203a4aec
@@ -40,6 +40,8 @@ module Graphql
40
40
  case type_expression
41
41
  when "Text"
42
42
  ["String", null]
43
+ when "Decimal"
44
+ ["Float", null]
43
45
  when "DateTime", "Datetime"
44
46
  ["GraphQL::Types::ISO8601DateTime", null]
45
47
  when "Date"
@@ -129,6 +129,7 @@ require "graphql/introspection"
129
129
  require "graphql/analysis_error"
130
130
  require "graphql/coercion_error"
131
131
  require "graphql/invalid_name_error"
132
+ require "graphql/integer_decoding_error"
132
133
  require "graphql/integer_encoding_error"
133
134
  require "graphql/string_encoding_error"
134
135
  require "graphql/internal_representation"
@@ -14,18 +14,33 @@ module GraphQL
14
14
  # This hash is the one used at runtime.
15
15
  #
16
16
  # @return [Hash<Symbol, Object>]
17
- attr_reader :keyword_arguments
17
+ def keyword_arguments
18
+ @keyword_arguments ||= begin
19
+ kwargs = {}
20
+ argument_values.each do |name, arg_val|
21
+ kwargs[name] = arg_val.value
22
+ end
23
+ kwargs
24
+ end
25
+ end
18
26
 
19
- def initialize(keyword_arguments:, argument_values:)
20
- @keyword_arguments = keyword_arguments
27
+ # @param argument_values [nil, Hash{Symbol => ArgumentValue}]
28
+ def initialize(argument_values:)
21
29
  @argument_values = argument_values
30
+ @empty = argument_values.nil? || argument_values.empty?
22
31
  end
23
32
 
24
33
  # @return [Hash{Symbol => ArgumentValue}]
25
- attr_reader :argument_values
34
+ def argument_values
35
+ @argument_values ||= {}
36
+ end
37
+
38
+ def empty?
39
+ @empty
40
+ end
26
41
 
27
- def_delegators :@keyword_arguments, :key?, :[], :fetch, :keys, :each, :values
28
- def_delegators :@argument_values, :each_value
42
+ def_delegators :keyword_arguments, :key?, :[], :fetch, :keys, :each, :values
43
+ def_delegators :argument_values, :each_value
29
44
 
30
45
  def inspect
31
46
  "#<#{self.class} @keyword_arguments=#{keyword_arguments.inspect}>"
@@ -29,11 +29,16 @@ module GraphQL
29
29
 
30
30
  private
31
31
 
32
+ NO_ARGUMENTS = {}.freeze
33
+
32
34
  NO_VALUE_GIVEN = Object.new
33
35
 
34
36
  def prepare_args_hash(ast_arg_or_hash_or_value)
35
37
  case ast_arg_or_hash_or_value
36
38
  when Hash
39
+ if ast_arg_or_hash_or_value.empty?
40
+ return NO_ARGUMENTS
41
+ end
37
42
  args_hash = {}
38
43
  ast_arg_or_hash_or_value.each do |k, v|
39
44
  args_hash[k] = prepare_args_hash(v)
@@ -42,6 +47,9 @@ module GraphQL
42
47
  when Array
43
48
  ast_arg_or_hash_or_value.map { |v| prepare_args_hash(v) }
44
49
  when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
50
+ if ast_arg_or_hash_or_value.arguments.empty?
51
+ return NO_ARGUMENTS
52
+ end
45
53
  args_hash = {}
46
54
  ast_arg_or_hash_or_value.arguments.each do |arg|
47
55
  v = prepare_args_hash(arg.value)
@@ -47,8 +47,7 @@ module GraphQL
47
47
  root_op_type = root_operation.operation_type || "query"
48
48
  root_type = schema.root_type_for_operation(root_op_type)
49
49
  path = []
50
- set_interpreter_context(:current_object, query.root_value)
51
- set_interpreter_context(:current_path, path)
50
+ set_all_interpreter_context(query.root_value, nil, nil, path)
52
51
  object_proxy = authorized_new(root_type, query.root_value, context, path)
53
52
  object_proxy = schema.sync_lazy(object_proxy)
54
53
  if object_proxy.nil?
@@ -118,9 +117,10 @@ module GraphQL
118
117
  end
119
118
  end
120
119
 
120
+ NO_ARGS = {}.freeze
121
+
121
122
  def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
122
- set_interpreter_context(:current_object, owner_object)
123
- set_interpreter_context(:current_path, path)
123
+ set_all_interpreter_context(owner_object, nil, nil, path)
124
124
  selections_by_name = {}
125
125
  gather_selections(owner_object, owner_type, selections, selections_by_name)
126
126
  selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
@@ -159,8 +159,7 @@ module GraphQL
159
159
  # to propagate `null`
160
160
  set_type_at_path(next_path, return_type)
161
161
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
162
- set_interpreter_context(:current_path, next_path)
163
- set_interpreter_context(:current_field, field_defn)
162
+ set_all_interpreter_context(nil, field_defn, nil, next_path)
164
163
 
165
164
  context.scoped_context = scoped_context
166
165
  object = owner_object
@@ -171,44 +170,50 @@ module GraphQL
171
170
 
172
171
  begin
173
172
  kwarg_arguments = arguments(object, field_defn, ast_node)
174
- rescue GraphQL::ExecutionError => e
173
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => e
175
174
  continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
176
175
  next
177
176
  end
178
177
 
179
178
  after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
180
- if resolved_arguments.is_a? GraphQL::ExecutionError
179
+ case resolved_arguments
180
+ when GraphQL::ExecutionError, GraphQL::UnauthorizedError
181
181
  continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
182
182
  next
183
183
  end
184
184
 
185
- kwarg_arguments = resolved_arguments.keyword_arguments
186
-
187
- field_defn.extras.each do |extra|
188
- case extra
189
- when :ast_node
190
- kwarg_arguments[:ast_node] = ast_node
191
- when :execution_errors
192
- kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
193
- when :path
194
- kwarg_arguments[:path] = next_path
195
- when :lookahead
196
- if !field_ast_nodes
197
- field_ast_nodes = [ast_node]
185
+ if resolved_arguments.empty? && field_defn.extras.empty?
186
+ # We can avoid allocating the `{ Symbol => Object }` hash in this case
187
+ kwarg_arguments = NO_ARGS
188
+ else
189
+ kwarg_arguments = resolved_arguments.keyword_arguments
190
+
191
+ field_defn.extras.each do |extra|
192
+ case extra
193
+ when :ast_node
194
+ kwarg_arguments[:ast_node] = ast_node
195
+ when :execution_errors
196
+ kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
197
+ when :path
198
+ kwarg_arguments[:path] = next_path
199
+ when :lookahead
200
+ if !field_ast_nodes
201
+ field_ast_nodes = [ast_node]
202
+ end
203
+ kwarg_arguments[:lookahead] = Execution::Lookahead.new(
204
+ query: query,
205
+ ast_nodes: field_ast_nodes,
206
+ field: field_defn,
207
+ )
208
+ when :argument_details
209
+ kwarg_arguments[:argument_details] = resolved_arguments
210
+ else
211
+ kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
198
212
  end
199
- kwarg_arguments[:lookahead] = Execution::Lookahead.new(
200
- query: query,
201
- ast_nodes: field_ast_nodes,
202
- field: field_defn,
203
- )
204
- when :argument_details
205
- kwarg_arguments[:argument_details] = resolved_arguments
206
- else
207
- kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
208
213
  end
209
214
  end
210
215
 
211
- set_interpreter_context(:current_arguments, kwarg_arguments)
216
+ set_all_interpreter_context(nil, nil, kwarg_arguments, nil)
212
217
 
213
218
  # Optimize for the case that field is selected only once
214
219
  if field_ast_nodes.nil? || field_ast_nodes.size == 1
@@ -414,6 +419,21 @@ module GraphQL
414
419
  true
415
420
  end
416
421
 
422
+ def set_all_interpreter_context(object, field, arguments, path)
423
+ if object
424
+ @context[:current_object] = @interpreter_context[:current_object] = object
425
+ end
426
+ if field
427
+ @context[:current_field] = @interpreter_context[:current_field] = field
428
+ end
429
+ if arguments
430
+ @context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
431
+ end
432
+ if path
433
+ @context[:current_path] = @interpreter_context[:current_path] = path
434
+ end
435
+ end
436
+
417
437
  # @param obj [Object] Some user-returned value that may want to be batched
418
438
  # @param path [Array<String>]
419
439
  # @param field [GraphQL::Schema::Field]
@@ -421,16 +441,10 @@ module GraphQL
421
441
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
422
442
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
423
443
  def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block)
424
- set_interpreter_context(:current_object, owner_object)
425
- set_interpreter_context(:current_arguments, arguments)
426
- set_interpreter_context(:current_path, path)
427
- set_interpreter_context(:current_field, field)
444
+ set_all_interpreter_context(owner_object, field, arguments, path)
428
445
  if schema.lazy?(lazy_obj)
429
446
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
430
- set_interpreter_context(:current_path, path)
431
- set_interpreter_context(:current_field, field)
432
- set_interpreter_context(:current_object, owner_object)
433
- set_interpreter_context(:current_arguments, arguments)
447
+ set_all_interpreter_context(owner_object, field, arguments, path)
434
448
  context.scoped_context = scoped_context
435
449
  # Wrap the execution of _this_ method with tracing,
436
450
  # but don't wrap the continuation below
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ # This error is raised when `Types::Int` is given an input value outside of 32-bit integer range.
4
+ #
5
+ # For really big integer values, consider `GraphQL::Types::BigInt`
6
+ #
7
+ # @see GraphQL::Types::Int which raises this error
8
+ class IntegerDecodingError < GraphQL::RuntimeTypeError
9
+ # The value which couldn't be decoded
10
+ attr_reader :integer_value
11
+
12
+ def initialize(value)
13
+ @integer_value = value
14
+ super("Integer out of bounds: #{value}. \nConsider using GraphQL::Types::BigInt instead.")
15
+ end
16
+ end
17
+ end
@@ -39,7 +39,7 @@ module GraphQL
39
39
  end
40
40
 
41
41
  def inspect
42
- if name.nil? && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation)
42
+ if (name.nil? || parent_class.name.nil?) && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation)
43
43
  "#{mutation.inspect}::#{parent_class.graphql_name}::InvalidNullError"
44
44
  else
45
45
  super
@@ -88,6 +88,7 @@ module GraphQL
88
88
  schema = schema.graphql_definition
89
89
  end
90
90
  @schema = schema
91
+ @interpreter = @schema.interpreter?
91
92
  @filter = schema.default_filter.merge(except: except, only: only)
92
93
  @context = schema.context_class.new(query: self, object: root_value, values: context)
93
94
  @warden = warden
@@ -148,7 +149,9 @@ module GraphQL
148
149
  @query_string ||= (document ? document.to_query_string : nil)
149
150
  end
150
151
 
151
- def_delegators :@schema, :interpreter?
152
+ def interpreter?
153
+ @interpreter
154
+ end
152
155
 
153
156
  def subscription_update?
154
157
  @subscription_topic && subscription?
@@ -167,7 +167,10 @@ module GraphQL
167
167
  # @api private
168
168
  attr_accessor :scoped_context
169
169
 
170
- def_delegators :@provided_values, :[]=
170
+ def []=(key, value)
171
+ @provided_values[key] = value
172
+ end
173
+
171
174
  def_delegators :@query, :trace, :interpreter?
172
175
 
173
176
  # @!method []=(key, value)
@@ -72,7 +72,7 @@ module GraphQL
72
72
  elsif @operation_name_error
73
73
  @validation_errors << @operation_name_error
74
74
  else
75
- validation_result = @schema.static_validator.validate(@query, validate: @validate)
75
+ validation_result = @schema.static_validator.validate(@query, validate: @validate, timeout: @schema.validate_timeout)
76
76
  @validation_errors.concat(validation_result[:errors])
77
77
  @internal_representation = validation_result[:irep]
78
78
 
@@ -31,8 +31,6 @@ module GraphQL
31
31
  end
32
32
  end
33
33
 
34
- private
35
-
36
34
  def first
37
35
  @first ||= begin
38
36
  capped = limit_pagination_argument(arguments[:first], max_page_size)
@@ -47,6 +45,8 @@ module GraphQL
47
45
  @last ||= limit_pagination_argument(arguments[:last], max_page_size)
48
46
  end
49
47
 
48
+ private
49
+
50
50
  # apply first / last limit results
51
51
  def paged_nodes
52
52
  @paged_nodes ||= begin
@@ -157,7 +157,7 @@ module GraphQL
157
157
 
158
158
  accepts_definitions \
159
159
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
160
- :max_depth, :max_complexity, :default_max_page_size,
160
+ :validate_timeout, :max_depth, :max_complexity, :default_max_page_size,
161
161
  :orphan_types, :resolve_type, :type_error, :parse_error,
162
162
  :error_bubbling,
163
163
  :raise_definition_error,
@@ -196,7 +196,7 @@ module GraphQL
196
196
  attr_accessor \
197
197
  :query, :mutation, :subscription,
198
198
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
199
- :max_depth, :max_complexity, :default_max_page_size,
199
+ :validate_timeout, :max_depth, :max_complexity, :default_max_page_size,
200
200
  :orphan_types, :directives,
201
201
  :query_analyzers, :multiplex_analyzers, :instrumenters, :lazy_methods,
202
202
  :cursor_encoder,
@@ -366,7 +366,7 @@ module GraphQL
366
366
  validator_opts = { schema: self }
367
367
  rules && (validator_opts[:rules] = rules)
368
368
  validator = GraphQL::StaticValidation::Validator.new(**validator_opts)
369
- res = validator.validate(query)
369
+ res = validator.validate(query, timeout: validate_timeout)
370
370
  res[:errors]
371
371
  end
372
372
 
@@ -950,6 +950,7 @@ module GraphQL
950
950
  schema_defn.query = query && query.graphql_definition
951
951
  schema_defn.mutation = mutation && mutation.graphql_definition
952
952
  schema_defn.subscription = subscription && subscription.graphql_definition
953
+ schema_defn.validate_timeout = validate_timeout
953
954
  schema_defn.max_complexity = max_complexity
954
955
  schema_defn.error_bubbling = error_bubbling
955
956
  schema_defn.max_depth = max_depth
@@ -1272,6 +1273,18 @@ module GraphQL
1272
1273
  end
1273
1274
  end
1274
1275
 
1276
+ attr_writer :validate_timeout
1277
+
1278
+ def validate_timeout(new_validate_timeout = nil)
1279
+ if new_validate_timeout
1280
+ @validate_timeout = new_validate_timeout
1281
+ elsif defined?(@validate_timeout)
1282
+ @validate_timeout
1283
+ else
1284
+ find_inherited_value(:validate_timeout)
1285
+ end
1286
+ end
1287
+
1275
1288
  attr_writer :max_complexity
1276
1289
 
1277
1290
  def max_complexity(max_complexity = nil)
@@ -8,6 +8,8 @@ module GraphQL
8
8
  ctx.errors << type_error
9
9
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
10
10
  raise type_error
11
+ when GraphQL::IntegerDecodingError
12
+ nil
11
13
  end
12
14
  end
13
15
  end
@@ -725,33 +725,42 @@ module GraphQL
725
725
  if @extensions.empty?
726
726
  yield(obj, args)
727
727
  else
728
- extended_obj = obj
729
- extended_args = args
730
-
731
- memos = []
732
- value = run_extensions_before_resolve(memos, obj, args, ctx) do |obj, args|
733
- extended_obj = obj
734
- extended_args = args
728
+ # This is a hack to get the _last_ value for extended obj and args,
729
+ # in case one of the extensions doesn't `yield`.
730
+ # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
731
+ extended = { args: args, obj: obj, memos: nil }
732
+ value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args|
735
733
  yield(obj, args)
736
734
  end
737
735
 
736
+ extended_obj = extended[:obj]
737
+ extended_args = extended[:args]
738
+ memos = extended[:memos] || EMPTY_HASH
739
+
738
740
  ctx.schema.after_lazy(value) do |resolved_value|
739
- @extensions.each_with_index do |ext, idx|
741
+ idx = 0
742
+ @extensions.each do |ext|
740
743
  memo = memos[idx]
741
744
  # TODO after_lazy?
742
745
  resolved_value = ext.after_resolve(object: extended_obj, arguments: extended_args, context: ctx, value: resolved_value, memo: memo)
746
+ idx += 1
743
747
  end
744
748
  resolved_value
745
749
  end
746
750
  end
747
751
  end
748
752
 
749
- def run_extensions_before_resolve(memos, obj, args, ctx, idx: 0)
753
+ def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
750
754
  extension = @extensions[idx]
751
755
  if extension
752
756
  extension.resolve(object: obj, arguments: args, context: ctx) do |extended_obj, extended_args, memo|
753
- memos << memo
754
- run_extensions_before_resolve(memos, extended_obj, extended_args, ctx, idx: idx + 1) { |o, a| yield(o, a) }
757
+ if memo
758
+ memos = extended[:memos] ||= {}
759
+ memos[idx] = memo
760
+ end
761
+ extended[:obj] = extended_obj
762
+ extended[:args] = extended_args
763
+ run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) }
755
764
  end
756
765
  else
757
766
  yield(obj, args)
@@ -4,7 +4,7 @@ module GraphQL
4
4
  class Schema
5
5
  class Field
6
6
  class ScopeExtension < GraphQL::Schema::FieldExtension
7
- def after_resolve(value:, context:, **rest)
7
+ def after_resolve(object:, arguments:, context:, value:, memo:)
8
8
  if value.nil?
9
9
  value
10
10
  else
@@ -85,70 +85,69 @@ module GraphQL
85
85
  # @param context [GraphQL::Query::Context]
86
86
  # @return [Hash<Symbol, Object>, Execution::Lazy<Hash>]
87
87
  def coerce_arguments(parent_object, values, context)
88
- argument_values = {}
89
- kwarg_arguments = {}
90
88
  # Cache this hash to avoid re-merging it
91
89
  arg_defns = self.arguments
92
90
 
93
- maybe_lazies = []
94
- arg_lazies = arg_defns.map do |arg_name, arg_defn|
95
- arg_key = arg_defn.keyword
96
- has_value = false
97
- default_used = false
98
- if values.key?(arg_name)
99
- has_value = true
100
- value = values[arg_name]
101
- elsif values.key?(arg_key)
102
- has_value = true
103
- value = values[arg_key]
104
- elsif arg_defn.default_value?
105
- has_value = true
106
- value = arg_defn.default_value
107
- default_used = true
108
- end
109
-
110
- if has_value
111
- loads = arg_defn.loads
112
- loaded_value = nil
113
- if loads && !arg_defn.from_resolver?
114
- loaded_value = if arg_defn.type.list?
115
- loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, context) }
116
- context.schema.after_any_lazies(loaded_values) { |result| result }
117
- else
118
- load_application_object(arg_defn, loads, value, context)
119
- end
91
+ if arg_defns.empty?
92
+ GraphQL::Execution::Interpreter::Arguments.new(argument_values: nil)
93
+ else
94
+ argument_values = {}
95
+ arg_lazies = arg_defns.map do |arg_name, arg_defn|
96
+ arg_key = arg_defn.keyword
97
+ has_value = false
98
+ default_used = false
99
+ if values.key?(arg_name)
100
+ has_value = true
101
+ value = values[arg_name]
102
+ elsif values.key?(arg_key)
103
+ has_value = true
104
+ value = values[arg_key]
105
+ elsif arg_defn.default_value?
106
+ has_value = true
107
+ value = arg_defn.default_value
108
+ default_used = true
120
109
  end
121
110
 
122
- coerced_value = if loaded_value
123
- loaded_value
124
- else
125
- context.schema.error_handler.with_error_handling(context) do
126
- arg_defn.type.coerce_input(value, context)
111
+ if has_value
112
+ loads = arg_defn.loads
113
+ loaded_value = nil
114
+ if loads && !arg_defn.from_resolver?
115
+ loaded_value = if arg_defn.type.list?
116
+ loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, context) }
117
+ context.schema.after_any_lazies(loaded_values) { |result| result }
118
+ else
119
+ load_application_object(arg_defn, loads, value, context)
120
+ end
127
121
  end
128
- end
129
122
 
130
- context.schema.after_lazy(coerced_value) do |coerced_value|
131
- prepared_value = context.schema.error_handler.with_error_handling(context) do
132
- arg_defn.prepare_value(parent_object, coerced_value, context: context)
123
+ coerced_value = if loaded_value
124
+ loaded_value
125
+ else
126
+ context.schema.error_handler.with_error_handling(context) do
127
+ arg_defn.type.coerce_input(value, context)
128
+ end
133
129
  end
134
130
 
135
- kwarg_arguments[arg_key] = prepared_value
136
- # TODO code smell to access such a deeply-nested constant in a distant module
137
- argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
138
- value: prepared_value,
139
- definition: arg_defn,
140
- default_used: default_used,
141
- )
131
+ context.schema.after_lazy(coerced_value) do |coerced_value|
132
+ prepared_value = context.schema.error_handler.with_error_handling(context) do
133
+ arg_defn.prepare_value(parent_object, coerced_value, context: context)
134
+ end
135
+
136
+ # TODO code smell to access such a deeply-nested constant in a distant module
137
+ argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
138
+ value: prepared_value,
139
+ definition: arg_defn,
140
+ default_used: default_used,
141
+ )
142
+ end
142
143
  end
143
144
  end
144
- end
145
145
 
146
- maybe_lazies.concat(arg_lazies)
147
- context.schema.after_any_lazies(maybe_lazies) do
148
- GraphQL::Execution::Interpreter::Arguments.new(
149
- keyword_arguments: kwarg_arguments,
150
- argument_values: argument_values,
151
- )
146
+ context.schema.after_any_lazies(arg_lazies) do
147
+ GraphQL::Execution::Interpreter::Arguments.new(
148
+ argument_values: argument_values,
149
+ )
150
+ end
152
151
  end
153
152
  end
154
153
 
@@ -105,7 +105,7 @@ module GraphQL
105
105
  sig = super
106
106
  # Arguments were added at the root, but they should be nested
107
107
  sig[:arguments].clear
108
- sig[:arguments][:input] = { type: input_type, required: true }
108
+ sig[:arguments][:input] = { type: input_type, required: true, description: "Parameters for #{graphql_name}" }
109
109
  sig
110
110
  end
111
111
 
@@ -4,6 +4,7 @@ require "graphql/static_validation/definition_dependencies"
4
4
  require "graphql/static_validation/type_stack"
5
5
  require "graphql/static_validation/validator"
6
6
  require "graphql/static_validation/validation_context"
7
+ require "graphql/static_validation/validation_timeout_error"
7
8
  require "graphql/static_validation/literal_validator"
8
9
  require "graphql/static_validation/base_visitor"
9
10
  require "graphql/static_validation/no_validate_visitor"
@@ -34,6 +34,7 @@ module GraphQL
34
34
  GraphQL::StaticValidation::VariableUsagesAreAllowed,
35
35
  GraphQL::StaticValidation::MutationRootExists,
36
36
  GraphQL::StaticValidation::SubscriptionRootExists,
37
+ GraphQL::StaticValidation::InputObjectNamesAreUnique,
37
38
  ]
38
39
  end
39
40
  end
@@ -202,15 +202,16 @@ module GraphQL
202
202
  )
203
203
  end
204
204
 
205
- args = possible_arguments(node1, node2)
206
- if args.size > 1
207
- msg = "Field '#{response_key}' has an argument conflict: #{args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ")}?"
205
+ if !same_arguments?(node1, node2)
206
+ args = [serialize_field_args(node1), serialize_field_args(node2)]
207
+ conflicts = args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ")
208
+ msg = "Field '#{response_key}' has an argument conflict: #{conflicts}?"
208
209
  context.errors << GraphQL::StaticValidation::FieldsWillMergeError.new(
209
210
  msg,
210
211
  nodes: [node1, node2],
211
212
  path: [],
212
213
  field_name: response_key,
213
- conflicts: args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ")
214
+ conflicts: conflicts
214
215
  )
215
216
  end
216
217
  end
@@ -326,20 +327,19 @@ module GraphQL
326
327
  [fields, fragment_spreads]
327
328
  end
328
329
 
329
- def possible_arguments(field1, field2)
330
+ def same_arguments?(field1, field2)
330
331
  # Check for incompatible / non-identical arguments on this node:
331
- [field1, field2].map do |n|
332
- if n.arguments.any?
333
- serialized_args = {}
334
- n.arguments.each do |a|
335
- arg_value = a.value
336
- serialized_args[a.name] = serialize_arg(arg_value)
337
- end
338
- serialized_args
339
- else
340
- NO_ARGS
341
- end
342
- end.uniq
332
+ arguments1 = field1.arguments
333
+ arguments2 = field2.arguments
334
+
335
+ return false if arguments1.length != arguments2.length
336
+
337
+ arguments1.all? do |argument1|
338
+ argument2 = arguments2.find { |argument| argument.name == argument1.name }
339
+ return false if argument2.nil?
340
+
341
+ serialize_arg(argument1.value) == serialize_arg(argument2.value)
342
+ end
343
343
  end
344
344
 
345
345
  def serialize_arg(arg_value)
@@ -353,6 +353,14 @@ module GraphQL
353
353
  end
354
354
  end
355
355
 
356
+ def serialize_field_args(field)
357
+ serialized_args = {}
358
+ field.arguments.each do |argument|
359
+ serialized_args[argument.name] = serialize_arg(argument.value)
360
+ end
361
+ serialized_args
362
+ end
363
+
356
364
  def compared_fragments_key(frag1, frag2, exclusive)
357
365
  # Cache key to not compare two fragments more than once.
358
366
  # The key includes both fragment names sorted (this way we
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ module InputObjectNamesAreUnique
5
+ def on_input_object(node, parent)
6
+ validate_input_fields(node)
7
+ super
8
+ end
9
+
10
+ private
11
+
12
+ def validate_input_fields(node)
13
+ input_field_defns = node.arguments
14
+ input_fields_by_name = Hash.new { |h, k| h[k] = [] }
15
+ input_field_defns.each { |a| input_fields_by_name[a.name] << a }
16
+
17
+ input_fields_by_name.each do |name, defns|
18
+ if defns.size > 1
19
+ error = GraphQL::StaticValidation::InputObjectNamesAreUniqueError.new(
20
+ "There can be only one input field named \"#{name}\"",
21
+ nodes: defns,
22
+ name: name
23
+ )
24
+ add_error(error)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class InputObjectNamesAreUniqueError < StaticValidation::Error
5
+ attr_reader :name
6
+
7
+ def initialize(message, path: nil, nodes: [], name:)
8
+ super(message, path: path, nodes: nodes)
9
+ @name = name
10
+ end
11
+
12
+ # A hash representation of this Message
13
+ def to_h
14
+ extensions = {
15
+ "code" => code,
16
+ "name" => name
17
+ }
18
+
19
+ super.merge({
20
+ "extensions" => extensions
21
+ })
22
+ end
23
+
24
+ def code
25
+ "inputFieldNotUnique"
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class ValidationTimeoutError < StaticValidation::Error
5
+ def initialize(message, path: nil, nodes: [])
6
+ super(message, path: path, nodes: nodes)
7
+ end
8
+
9
+ # A hash representation of this Message
10
+ def to_h
11
+ extensions = {
12
+ "code" => code
13
+ }
14
+
15
+ super.merge({
16
+ "extensions" => extensions
17
+ })
18
+ end
19
+
20
+ def code
21
+ "validationTimeout"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -20,8 +20,10 @@ module GraphQL
20
20
 
21
21
  # Validate `query` against the schema. Returns an array of message hashes.
22
22
  # @param query [GraphQL::Query]
23
+ # @param validate [Boolean]
24
+ # @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds.
23
25
  # @return [Array<Hash>]
24
- def validate(query, validate: true)
26
+ def validate(query, validate: true, timeout: nil)
25
27
  query.trace("validate", { validate: validate, query: query }) do
26
28
  can_skip_rewrite = query.context.interpreter? && query.schema.using_ast_analysis? && query.schema.is_a?(Class)
27
29
  errors = if validate == false && can_skip_rewrite
@@ -32,21 +34,29 @@ module GraphQL
32
34
 
33
35
  context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class)
34
36
 
35
- # Attach legacy-style rules.
36
- # Only loop through rules if it has legacy-style rules
37
- unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
38
- legacy_rules.each do |rule_class_or_module|
39
- if rule_class_or_module.method_defined?(:validate)
40
- rule_class_or_module.new.validate(context)
37
+ begin
38
+ # CAUTION: Usage of the timeout module makes the assumption that validation rules are stateless Ruby code that requires no cleanup if process was interrupted. This means no blocking IO calls, native gems, locks, or `rescue` clauses that must be reached.
39
+ # A timeout value of 0 or nil will execute the block without any timeout.
40
+ Timeout::timeout(timeout) do
41
+ # Attach legacy-style rules.
42
+ # Only loop through rules if it has legacy-style rules
43
+ unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
44
+ legacy_rules.each do |rule_class_or_module|
45
+ if rule_class_or_module.method_defined?(:validate)
46
+ rule_class_or_module.new.validate(context)
47
+ end
48
+ end
41
49
  end
50
+
51
+ context.visitor.visit
42
52
  end
53
+ rescue Timeout::Error
54
+ handle_timeout(query, context)
43
55
  end
44
56
 
45
- context.visitor.visit
46
57
  context.errors
47
58
  end
48
59
 
49
-
50
60
  irep = if errors.empty? && context
51
61
  # Only return this if there are no errors and validation was actually run
52
62
  context.visitor.rewrite_document
@@ -60,6 +70,15 @@ module GraphQL
60
70
  }
61
71
  end
62
72
  end
73
+
74
+ # Invoked when static validation times out.
75
+ # @param query [GraphQL::Query]
76
+ # @param context [GraphQL::StaticValidation::ValidationContext]
77
+ def handle_timeout(query, context)
78
+ context.errors << GraphQL::StaticValidation::ValidationTimeoutError.new(
79
+ "Timeout on validation of query"
80
+ )
81
+ end
63
82
  end
64
83
  end
65
84
  end
@@ -95,7 +95,7 @@ module GraphQL
95
95
  end
96
96
 
97
97
  def self.use(schema_defn, options = {})
98
- tracer = self.new(options)
98
+ tracer = self.new(**options)
99
99
  schema_defn.instrument(:field, tracer)
100
100
  schema_defn.tracer(tracer)
101
101
  end
@@ -16,7 +16,10 @@ module GraphQL
16
16
  end
17
17
 
18
18
  def collect(object)
19
- labels = { key: object['key'], platform_key: object['platform_key'] }
19
+ default_labels = { key: object['key'], platform_key: object['platform_key'] }
20
+ custom = object['custom_labels']
21
+ labels = custom.nil? ? default_labels : default_labels.merge(custom)
22
+
20
23
  @graphql_gauge.observe object['duration'], labels
21
24
  end
22
25
 
@@ -9,8 +9,15 @@ module GraphQL
9
9
  MIN = -(2**31)
10
10
  MAX = (2**31) - 1
11
11
 
12
- def self.coerce_input(value, _ctx)
13
- value.is_a?(Integer) ? value : nil
12
+ def self.coerce_input(value, ctx)
13
+ return if !value.is_a?(Integer)
14
+
15
+ if value >= MIN && value <= MAX
16
+ value
17
+ else
18
+ err = GraphQL::IntegerDecodingError.new(value)
19
+ ctx.schema.type_error(err, ctx)
20
+ end
14
21
  end
15
22
 
16
23
  def self.coerce_result(value, ctx)
@@ -94,7 +94,8 @@ module GraphQL
94
94
  type = nullable ? [@node_type, null: true] : [@node_type]
95
95
  field :nodes, type,
96
96
  null: nullable,
97
- description: "A list of nodes."
97
+ description: "A list of nodes.",
98
+ connection: false
98
99
  end
99
100
  end
100
101
 
@@ -33,7 +33,8 @@ module GraphQL
33
33
  if node_type
34
34
  @node_type = node_type
35
35
  # Add a default `node` field
36
- field :node, node_type, null: null, description: "The item at the end of the edge."
36
+ field :node, node_type, null: null, description: "The item at the end of the edge.",
37
+ connection: false
37
38
  end
38
39
  @node_type
39
40
  end
@@ -7,7 +7,13 @@ module GraphQL
7
7
 
8
8
  def self.coerce_result(value, ctx)
9
9
  str = value.to_s
10
- str.encoding == Encoding::UTF_8 ? str : str.encode(Encoding::UTF_8)
10
+ if str.encoding == Encoding::UTF_8
11
+ str
12
+ elsif str.frozen?
13
+ str.encode(Encoding::UTF_8)
14
+ else
15
+ str.encode!(Encoding::UTF_8)
16
+ end
11
17
  rescue EncodingError
12
18
  err = GraphQL::StringEncodingError.new(str)
13
19
  ctx.schema.type_error(err, ctx)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.11.6"
3
+ VERSION = "1.11.7"
4
4
  end
data/readme.md CHANGED
@@ -9,7 +9,7 @@
9
9
  A Ruby implementation of [GraphQL](https://graphql.org/).
10
10
 
11
11
  - [Website](https://graphql-ruby.org/)
12
- - [API Documentation](https://www.rubydoc.info/gems/graphql)
12
+ - [API Documentation](https://www.rubydoc.info/github/rmosolgo/graphql-ruby)
13
13
  - [Newsletter](https://tinyletter.com/graphql-ruby)
14
14
 
15
15
  ## Installation
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: 1.11.6
4
+ version: 1.11.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-29 00:00:00.000000000 Z
11
+ date: 2021-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -445,6 +445,7 @@ files:
445
445
  - lib/graphql/id_type.rb
446
446
  - lib/graphql/input_object_type.rb
447
447
  - lib/graphql/int_type.rb
448
+ - lib/graphql/integer_decoding_error.rb
448
449
  - lib/graphql/integer_encoding_error.rb
449
450
  - lib/graphql/interface_type.rb
450
451
  - lib/graphql/internal_representation.rb
@@ -650,6 +651,8 @@ files:
650
651
  - lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb
651
652
  - lib/graphql/static_validation/rules/fragments_are_used.rb
652
653
  - lib/graphql/static_validation/rules/fragments_are_used_error.rb
654
+ - lib/graphql/static_validation/rules/input_object_names_are_unique.rb
655
+ - lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb
653
656
  - lib/graphql/static_validation/rules/mutation_root_exists.rb
654
657
  - lib/graphql/static_validation/rules/mutation_root_exists_error.rb
655
658
  - lib/graphql/static_validation/rules/no_definitions_are_present.rb
@@ -676,6 +679,7 @@ files:
676
679
  - lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb
677
680
  - lib/graphql/static_validation/type_stack.rb
678
681
  - lib/graphql/static_validation/validation_context.rb
682
+ - lib/graphql/static_validation/validation_timeout_error.rb
679
683
  - lib/graphql/static_validation/validator.rb
680
684
  - lib/graphql/string_encoding_error.rb
681
685
  - lib/graphql/string_type.rb
@@ -737,7 +741,7 @@ metadata:
737
741
  source_code_uri: https://github.com/rmosolgo/graphql-ruby
738
742
  bug_tracker_uri: https://github.com/rmosolgo/graphql-ruby/issues
739
743
  mailing_list_uri: https://tinyletter.com/graphql-ruby
740
- post_install_message:
744
+ post_install_message:
741
745
  rdoc_options: []
742
746
  require_paths:
743
747
  - lib
@@ -752,8 +756,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
752
756
  - !ruby/object:Gem::Version
753
757
  version: '0'
754
758
  requirements: []
755
- rubygems_version: 3.0.3
756
- signing_key:
759
+ rubygems_version: 3.2.3
760
+ signing_key:
757
761
  specification_version: 4
758
762
  summary: A GraphQL language and runtime for Ruby
759
763
  test_files: []