graphql 1.10.6 → 1.10.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/object_generator.rb +50 -8
  3. data/lib/graphql.rb +4 -4
  4. data/lib/graphql/analysis/ast/query_complexity.rb +1 -1
  5. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
  6. data/lib/graphql/execution/instrumentation.rb +1 -1
  7. data/lib/graphql/execution/interpreter.rb +2 -0
  8. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  9. data/lib/graphql/execution/interpreter/arguments.rb +36 -0
  10. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -7
  11. data/lib/graphql/execution/interpreter/runtime.rb +47 -38
  12. data/lib/graphql/execution/lookahead.rb +3 -1
  13. data/lib/graphql/internal_representation/scope.rb +2 -2
  14. data/lib/graphql/internal_representation/visit.rb +2 -2
  15. data/lib/graphql/language/document_from_schema_definition.rb +42 -23
  16. data/lib/graphql/object_type.rb +1 -1
  17. data/lib/graphql/relay/base_connection.rb +0 -2
  18. data/lib/graphql/schema.rb +28 -13
  19. data/lib/graphql/schema/argument.rb +6 -0
  20. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  21. data/lib/graphql/schema/enum.rb +9 -1
  22. data/lib/graphql/schema/field.rb +30 -21
  23. data/lib/graphql/schema/input_object.rb +9 -6
  24. data/lib/graphql/schema/interface.rb +5 -0
  25. data/lib/graphql/schema/list.rb +7 -1
  26. data/lib/graphql/schema/loader.rb +110 -103
  27. data/lib/graphql/schema/member.rb +1 -0
  28. data/lib/graphql/schema/member/has_arguments.rb +33 -13
  29. data/lib/graphql/schema/member/has_fields.rb +1 -1
  30. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  31. data/lib/graphql/schema/non_null.rb +5 -0
  32. data/lib/graphql/schema/object.rb +10 -3
  33. data/lib/graphql/schema/printer.rb +0 -14
  34. data/lib/graphql/schema/resolver.rb +1 -1
  35. data/lib/graphql/schema/union.rb +6 -0
  36. data/lib/graphql/schema/warden.rb +7 -1
  37. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  38. data/lib/graphql/subscriptions/subscription_root.rb +10 -2
  39. data/lib/graphql/tracing.rb +5 -4
  40. data/lib/graphql/tracing/new_relic_tracing.rb +1 -12
  41. data/lib/graphql/tracing/platform_tracing.rb +14 -0
  42. data/lib/graphql/tracing/scout_tracing.rb +11 -0
  43. data/lib/graphql/types/iso_8601_date.rb +3 -3
  44. data/lib/graphql/types/iso_8601_date_time.rb +18 -8
  45. data/lib/graphql/version.rb +1 -1
  46. metadata +6 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b006fe34754dbc2e3ee7ef459e05bdf325755538e013efd96c9eef3990af9a7
4
- data.tar.gz: b655b349e59ea3a34d6ed71fb3870af1cc91afc75bf7147cb0d9b436b78a97c8
3
+ metadata.gz: 647d1d9e043e1e409e56f3d38f7139f2462f2590d5f32b93627dffbb46b0dcdf
4
+ data.tar.gz: 1cd86cbb6e6452f5300eadaf51a8e6404a6a5a5cdfcffc883ba76190d4960c75
5
5
  SHA512:
6
- metadata.gz: 9a07c35f18dfdb53924fc9ff8466c3255c7235f855d5ea7380738e4cde90c47c6cd12aaa505a27ee1c3ec3bee84a8a82ed9ec113cbbd362ef152a8b1ec6cdf38
7
- data.tar.gz: 23e61762fd23870cb651334aa84a9e83bb9dda486b114ea8bb2b937eeb77705bac200255b321983caf4f81d6efd0b231a992b618563b86d6446b68585ec429bc
6
+ metadata.gz: 99484e3df661ee34b882bf8e999cbb134574f24d04eb4ed591ecad8f4098ef44b91ba72ae7e856115297cadce8ac13b605df30de5c9b5fd41ba8fbfd5581ac1f
7
+ data.tar.gz: dfc446058e5abbc0575bbc5ad9e07ec7b2f626ff8b0da73f8bb358efb31c6e35776a9950f07b01dc79032aa8178cf0b9d082b454dab4ce3ecb1d06c051aaecf5
@@ -15,20 +15,62 @@ module Graphql
15
15
  desc "Create a GraphQL::ObjectType with the given name and fields"
16
16
  source_root File.expand_path('../templates', __FILE__)
17
17
 
18
- argument :fields,
19
- type: :array,
20
- default: [],
21
- banner: "name:type name:type ...",
22
- desc: "Fields for this object (type may be expressed as Ruby or GraphQL)"
18
+ argument :custom_fields,
19
+ type: :array,
20
+ default: [],
21
+ banner: "name:type name:type ...",
22
+ desc: "Fields for this object (type may be expressed as Ruby or GraphQL)"
23
23
 
24
24
  class_option :node,
25
- type: :boolean,
26
- default: false,
27
- desc: "Include the Relay Node interface"
25
+ type: :boolean,
26
+ default: false,
27
+ desc: "Include the Relay Node interface"
28
28
 
29
29
  def create_type_file
30
30
  template "object.erb", "#{options[:directory]}/types/#{type_file_name}.rb"
31
31
  end
32
+
33
+ def fields
34
+ columns = []
35
+ columns += klass.columns.map { |c| generate_column_string(c) } if class_exists?
36
+ columns + custom_fields
37
+ end
38
+
39
+ def self.normalize_type_expression(type_expression, mode:, null: true)
40
+ case type_expression
41
+ when "Text"
42
+ ["String", null]
43
+ when "DateTime", "Datetime"
44
+ ["GraphQL::Types::ISO8601DateTime", null]
45
+ when "Date"
46
+ ["GraphQL::Types::ISO8601Date", null]
47
+ else
48
+ super
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def generate_column_string(column)
55
+ name = column.name
56
+ required = column.null ? "" : "!"
57
+ type = column_type_string(column)
58
+ "#{name}:#{required}#{type}"
59
+ end
60
+
61
+ def column_type_string(column)
62
+ column.name == "id" ? "ID" : column.type.to_s.camelize
63
+ end
64
+
65
+ def class_exists?
66
+ klass.is_a?(Class) && klass.ancestors.include?(ActiveRecord::Base)
67
+ rescue NameError
68
+ return false
69
+ end
70
+
71
+ def klass
72
+ @klass ||= Module.const_get(type_name.camelize)
73
+ end
32
74
  end
33
75
  end
34
76
  end
@@ -89,12 +89,15 @@ require "graphql/name_validator"
89
89
  require "graphql/language"
90
90
  require "graphql/analysis"
91
91
  require "graphql/tracing"
92
- require "graphql/execution"
93
92
  require "graphql/dig"
93
+ require "graphql/execution"
94
94
  require "graphql/schema"
95
95
  require "graphql/query"
96
96
  require "graphql/directive"
97
97
  require "graphql/execution"
98
+ require "graphql/runtime_type_error"
99
+ require "graphql/unresolved_type_error"
100
+ require "graphql/invalid_null_error"
98
101
  require "graphql/types"
99
102
  require "graphql/relay"
100
103
  require "graphql/boolean_type"
@@ -109,10 +112,7 @@ require "graphql/introspection"
109
112
 
110
113
  require "graphql/analysis_error"
111
114
  require "graphql/coercion_error"
112
- require "graphql/runtime_type_error"
113
- require "graphql/invalid_null_error"
114
115
  require "graphql/invalid_name_error"
115
- require "graphql/unresolved_type_error"
116
116
  require "graphql/integer_encoding_error"
117
117
  require "graphql/string_encoding_error"
118
118
  require "graphql/internal_representation"
@@ -54,7 +54,7 @@ module GraphQL
54
54
  case defined_complexity
55
55
  when Proc
56
56
  arguments = @query.arguments_for(@node, @field_definition)
57
- defined_complexity.call(@query.context, arguments, child_complexity)
57
+ defined_complexity.call(@query.context, arguments.keyword_arguments, child_complexity)
58
58
  when Numeric
59
59
  defined_complexity + child_complexity
60
60
  else
@@ -43,8 +43,8 @@ module GraphQL
43
43
  @storage = storage
44
44
  end
45
45
 
46
- def each
47
- @storage.each { |i| yield(i) }
46
+ def each(&block)
47
+ @storage.each(&block)
48
48
  end
49
49
  end
50
50
 
@@ -77,7 +77,7 @@ module GraphQL
77
77
  end
78
78
 
79
79
  def call_after_hooks(instrumenters, object, after_hook_name, ex)
80
- instrumenters.reverse.each do |instrumenter|
80
+ instrumenters.reverse_each do |instrumenter|
81
81
  begin
82
82
  instrumenter.public_send(after_hook_name, object)
83
83
  rescue => e
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/execution/interpreter/argument_value"
3
+ require "graphql/execution/interpreter/arguments"
2
4
  require "graphql/execution/interpreter/arguments_cache"
3
5
  require "graphql/execution/interpreter/execution_errors"
4
6
  require "graphql/execution/interpreter/hash_response"
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Execution
5
+ class Interpreter
6
+ # A container for metadata regarding arguments present in a GraphQL query.
7
+ # @see Interpreter::Arguments#argument_values for a hash of these objects.
8
+ class ArgumentValue
9
+ def initialize(definition:, value:, default_used:)
10
+ @definition = definition
11
+ @value = value
12
+ @default_used = default_used
13
+ end
14
+
15
+ # @return [Object] The Ruby-ready value for this Argument
16
+ attr_reader :value
17
+
18
+ # @return [GraphQL::Schema::Argument] The definition instance for this argument
19
+ attr_reader :definition
20
+
21
+ # @return [Boolean] `true` if the schema-defined `default_value:` was applied in this case. (No client-provided value was present.)
22
+ def default_used?
23
+ @default_used
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Execution
5
+ class Interpreter
6
+ # A wrapper for argument hashes in GraphQL queries.
7
+ #
8
+ # @see GraphQL::Query#arguments_for to get access to these objects.
9
+ class Arguments
10
+ extend Forwardable
11
+ include GraphQL::Dig
12
+
13
+ # The Ruby-style arguments hash, ready for a resolver.
14
+ # This hash is the one used at runtime.
15
+ #
16
+ # @return [Hash<Symbol, Object>]
17
+ attr_reader :keyword_arguments
18
+
19
+ def initialize(keyword_arguments:, argument_values:)
20
+ @keyword_arguments = keyword_arguments
21
+ @argument_values = argument_values
22
+ end
23
+
24
+ # @return [Hash{Symbol => ArgumentValue}]
25
+ attr_reader :argument_values
26
+
27
+ def_delegators :@keyword_arguments, :key?, :[], :keys, :each, :values
28
+ def_delegators :@argument_values, :each_value
29
+
30
+ def inspect
31
+ "#<#{self.class} @keyword_arguments=#{keyword_arguments.inspect}>"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -14,13 +14,9 @@ module GraphQL
14
14
  # Then call into the schema to coerce those incoming values
15
15
  args = arg_owner.coerce_arguments(parent_object, args_hash, query.context)
16
16
 
17
- h3[parent_object] = if args.is_a?(GraphQL::Execution::Lazy)
18
- args.then { |resolved_args|
19
- # when this promise is resolved, update the cache with the resolved value
20
- h3[parent_object] = resolved_args
21
- }
22
- else
23
- args
17
+ h3[parent_object] = @query.schema.after_lazy(args) do |resolved_args|
18
+ # when this promise is resolved, update the cache with the resolved value
19
+ h3[parent_object] = resolved_args
24
20
  end
25
21
  end
26
22
  end
@@ -174,9 +174,14 @@ module GraphQL
174
174
  next
175
175
  end
176
176
 
177
- 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 |kwarg_arguments|
178
- # It might turn out that making arguments for every field is slow.
179
- # If we have to cache them, we'll need a more subtle approach here.
177
+ 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|
178
+ if resolved_arguments.is_a? GraphQL::ExecutionError
179
+ continue_value(next_path, resolved_arguments, field_defn, return_type.non_null?, ast_node)
180
+ next
181
+ end
182
+
183
+ kwarg_arguments = resolved_arguments.keyword_arguments
184
+
180
185
  field_defn.extras.each do |extra|
181
186
  case extra
182
187
  when :ast_node
@@ -194,6 +199,8 @@ module GraphQL
194
199
  ast_nodes: field_ast_nodes,
195
200
  field: field_defn,
196
201
  )
202
+ when :argument_details
203
+ kwarg_arguments[:argument_details] = resolved_arguments
197
204
  else
198
205
  kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
199
206
  end
@@ -247,7 +254,7 @@ module GraphQL
247
254
  def continue_value(path, value, field, is_non_null, ast_node)
248
255
  if value.nil?
249
256
  if is_non_null
250
- err = GraphQL::InvalidNullError.new(field.owner, field, value)
257
+ err = field.owner::InvalidNullError.new(field.owner, field, value)
251
258
  write_invalid_null_in_response(path, err)
252
259
  else
253
260
  write_in_response(path, nil)
@@ -297,18 +304,21 @@ module GraphQL
297
304
  write_in_response(path, r)
298
305
  r
299
306
  when "UNION", "INTERFACE"
300
- resolved_type_or_lazy = resolve_type(type, value, path)
307
+ resolved_type_or_lazy, resolved_value = resolve_type(type, value, path)
308
+ resolved_value ||= value
309
+
301
310
  after_lazy(resolved_type_or_lazy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
302
311
  possible_types = query.possible_types(type)
303
312
 
304
313
  if !possible_types.include?(resolved_type)
305
314
  parent_type = field.owner
306
- type_error = GraphQL::UnresolvedTypeError.new(value, field, parent_type, resolved_type, possible_types)
315
+ err_class = type::UnresolvedTypeError
316
+ type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
307
317
  schema.type_error(type_error, context)
308
318
  write_in_response(path, nil)
309
319
  nil
310
320
  else
311
- continue_field(path, value, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
321
+ continue_field(path, resolved_value, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
312
322
  end
313
323
  end
314
324
  when "OBJECT"
@@ -347,9 +357,15 @@ module GraphQL
347
357
  end
348
358
  end
349
359
  end
350
- rescue NoMethodError
351
- # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
352
- raise ListResultFailedError.new(value: value, field: field, path: path)
360
+ rescue NoMethodError => err
361
+ # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
362
+ if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
363
+ # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
364
+ raise ListResultFailedError.new(value: value, field: field, path: path)
365
+ else
366
+ # This was some other NoMethodError -- let it bubble to reveal the real error.
367
+ raise
368
+ end
353
369
  end
354
370
 
355
371
  response_list
@@ -363,11 +379,12 @@ module GraphQL
363
379
  end
364
380
  end
365
381
 
366
- def resolve_with_directives(object, ast_node)
367
- run_directive(object, ast_node, 0) { yield }
382
+ def resolve_with_directives(object, ast_node, &block)
383
+ return yield if ast_node.directives.empty?
384
+ run_directive(object, ast_node, 0, &block)
368
385
  end
369
386
 
370
- def run_directive(object, ast_node, idx)
387
+ def run_directive(object, ast_node, idx, &block)
371
388
  dir_node = ast_node.directives[idx]
372
389
  if !dir_node
373
390
  yield
@@ -376,9 +393,9 @@ module GraphQL
376
393
  if !dir_defn.is_a?(Class)
377
394
  dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
378
395
  end
379
- dir_args = arguments(nil, dir_defn, dir_node)
396
+ dir_args = arguments(nil, dir_defn, dir_node).keyword_arguments
380
397
  dir_defn.resolve(object, dir_args, context) do
381
- run_directive(object, ast_node, idx + 1) { yield }
398
+ run_directive(object, ast_node, idx + 1, &block)
382
399
  end
383
400
  end
384
401
  end
@@ -387,7 +404,7 @@ module GraphQL
387
404
  def directives_include?(node, graphql_object, parent_type)
388
405
  node.directives.each do |dir_node|
389
406
  dir_defn = schema.directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
390
- args = arguments(graphql_object, dir_defn, dir_node)
407
+ args = arguments(graphql_object, dir_defn, dir_node).keyword_arguments
391
408
  if !dir_defn.include?(graphql_object, args, context)
392
409
  return false
393
410
  end
@@ -401,7 +418,7 @@ module GraphQL
401
418
  # @param eager [Boolean] Set to `true` for mutation root fields only
402
419
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
403
420
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
404
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true)
421
+ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block)
405
422
  @interpreter_context[:current_object] = owner_object
406
423
  @interpreter_context[:current_arguments] = arguments
407
424
  @interpreter_context[:current_path] = path
@@ -426,11 +443,9 @@ module GraphQL
426
443
  end
427
444
  end
428
445
  rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
429
- yield(err)
430
- end
431
- after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager) do |really_inner_obj|
432
- yield(really_inner_obj)
446
+ err
433
447
  end
448
+ after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
434
449
  end
435
450
 
436
451
  if eager
@@ -445,7 +460,13 @@ module GraphQL
445
460
  end
446
461
 
447
462
  def arguments(graphql_object, arg_owner, ast_node)
448
- query.arguments_for(ast_node, arg_owner, parent_object: graphql_object)
463
+ # Don't cache arguments if field extras are requested since extras mutate the argument data structure
464
+ if arg_owner.arguments_statically_coercible? && (!arg_owner.is_a?(GraphQL::Schema::Field) || arg_owner.extras.empty?)
465
+ query.arguments_for(ast_node, arg_owner)
466
+ else
467
+ # The arguments must be prepared in the context of the given object
468
+ query.arguments_for(ast_node, arg_owner, parent_object: graphql_object)
469
+ end
449
470
  end
450
471
 
451
472
  def write_invalid_null_in_response(path, invalid_null_error)
@@ -485,23 +506,11 @@ module GraphQL
485
506
  # at previous parts of the response.
486
507
  # This hash matches the response
487
508
  def type_at(path)
488
- t = @types_at_paths
489
- path.each do |part|
490
- t = t[part] || (raise("Invariant: #{part.inspect} not found in #{t}"))
491
- end
492
- t = t[:__type]
493
- t
509
+ @types_at_paths.fetch(path)
494
510
  end
495
511
 
496
512
  def set_type_at_path(path, type)
497
- types = @types_at_paths
498
- path.each do |part|
499
- types = types[part] ||= {}
500
- end
501
- # Use this magic key so that the hash contains:
502
- # - string keys for nested fields
503
- # - :__type for the object type of a selection
504
- types[:__type] ||= type
513
+ @types_at_paths[path] = type
505
514
  nil
506
515
  end
507
516
 
@@ -531,7 +540,7 @@ module GraphQL
531
540
 
532
541
  def resolve_type(type, value, path)
533
542
  trace_payload = { context: context, type: type, object: value, path: path }
534
- resolved_type = query.trace("resolve_type", trace_payload) do
543
+ resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
535
544
  query.resolve_type(type, value)
536
545
  end
537
546
 
@@ -542,7 +551,7 @@ module GraphQL
542
551
  end
543
552
  end
544
553
  else
545
- resolved_type
554
+ [resolved_type, resolved_value]
546
555
  end
547
556
  end
548
557
 
@@ -55,7 +55,9 @@ module GraphQL
55
55
  @arguments
56
56
  else
57
57
  @arguments = if @field
58
- @query.arguments_for(@ast_nodes.first, @field)
58
+ @query.schema.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args|
59
+ args.is_a?(Execution::Interpreter::Arguments) ? args.keyword_arguments : args
60
+ end
59
61
  else
60
62
  nil
61
63
  end
@@ -66,11 +66,11 @@ module GraphQL
66
66
  # Call the block for each type in `self`.
67
67
  # This uses the simplest possible expression of `self`,
68
68
  # so if this scope is defined by an abstract type, it gets yielded.
69
- def each
69
+ def each(&block)
70
70
  if @abstract_type
71
71
  yield(@type)
72
72
  else
73
- @types.each { |t| yield(t) }
73
+ @types.each(&block)
74
74
  end
75
75
  end
76
76