graphql 1.10.10 → 1.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/object_generator.rb +50 -8
  3. data/lib/graphql.rb +3 -3
  4. data/lib/graphql/execution/interpreter.rb +1 -1
  5. data/lib/graphql/execution/interpreter/arguments.rb +1 -5
  6. data/lib/graphql/execution/interpreter/runtime.rb +20 -24
  7. data/lib/graphql/execution/multiplex.rb +1 -2
  8. data/lib/graphql/introspection/schema_type.rb +3 -3
  9. data/lib/graphql/invalid_null_error.rb +18 -0
  10. data/lib/graphql/pagination/connection.rb +13 -6
  11. data/lib/graphql/pagination/connections.rb +5 -4
  12. data/lib/graphql/query.rb +1 -2
  13. data/lib/graphql/schema.rb +26 -3
  14. data/lib/graphql/schema/argument.rb +6 -0
  15. data/lib/graphql/schema/build_from_definition.rb +7 -12
  16. data/lib/graphql/schema/enum.rb +9 -1
  17. data/lib/graphql/schema/field.rb +68 -80
  18. data/lib/graphql/schema/field/connection_extension.rb +2 -1
  19. data/lib/graphql/schema/input_object.rb +1 -3
  20. data/lib/graphql/schema/interface.rb +5 -0
  21. data/lib/graphql/schema/member.rb +1 -0
  22. data/lib/graphql/schema/member/has_arguments.rb +6 -0
  23. data/lib/graphql/schema/member/has_fields.rb +1 -1
  24. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  25. data/lib/graphql/schema/object.rb +7 -0
  26. data/lib/graphql/schema/resolver.rb +14 -0
  27. data/lib/graphql/schema/subscription.rb +1 -1
  28. data/lib/graphql/schema/union.rb +6 -0
  29. data/lib/graphql/schema/warden.rb +0 -1
  30. data/lib/graphql/subscriptions.rb +41 -8
  31. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +49 -5
  32. data/lib/graphql/subscriptions/broadcast_analyzer.rb +84 -0
  33. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  34. data/lib/graphql/subscriptions/event.rb +16 -1
  35. data/lib/graphql/subscriptions/subscription_root.rb +13 -3
  36. data/lib/graphql/tracing.rb +1 -27
  37. data/lib/graphql/tracing/new_relic_tracing.rb +1 -12
  38. data/lib/graphql/tracing/platform_tracing.rb +39 -15
  39. data/lib/graphql/tracing/scout_tracing.rb +11 -0
  40. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  41. data/lib/graphql/types/iso_8601_date.rb +1 -1
  42. data/lib/graphql/types/iso_8601_date_time.rb +17 -13
  43. data/lib/graphql/version.rb +1 -1
  44. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec55133305287a9626164b06b4d87a97ee4d2b0fb75fb6c09c8964f34122c90f
4
- data.tar.gz: 85dc4bae04dac609134141b4385414505bd6cf510259b1fadee7b50783eea0eb
3
+ metadata.gz: 2b152fd76301d994c8a685bce0539e409739c6e1e303c611c34a6424b2d2e68e
4
+ data.tar.gz: 84215fce3790161bebfa183e05b69abe6394396bec177ce69f98e5a53484d7b5
5
5
  SHA512:
6
- metadata.gz: 45c3d9d7db094887e1f3a95aa43ed58f58889e25039351c6767f0b3134cbb56ff479fcf8c5325b137a063e604b63e9a1edf942ad5c5b132989f706b473ad52d0
7
- data.tar.gz: 8736c31c021fbaf119b3d2ed07a3d3b38ad22afa60070e6e5df62e16d2d8fe65314942a555500312bb40f8c37c7fb59755bf28dce144e707f2408d378c8d6d65
6
+ metadata.gz: 83d130c2cd4c76f9869d60e4a32f80e2a20a3ff5c614e6852686c1d06d2da8b454065b5c382099877c2449e77f938f8ef5e2dfd69e22bf5a505e04a3edb8db2d
7
+ data.tar.gz: 06bf9038ca1f32d03fc4a0fc2ed6763420ff58d7c7b4aaad38a703b0eb090c3a35fb829c2a7e13420c1507ea8013e5d7084067c733de98b416698ea39ba46ba3
@@ -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
@@ -91,6 +91,9 @@ require "graphql/analysis"
91
91
  require "graphql/tracing"
92
92
  require "graphql/dig"
93
93
  require "graphql/execution"
94
+ require "graphql/runtime_type_error"
95
+ require "graphql/unresolved_type_error"
96
+ require "graphql/invalid_null_error"
94
97
  require "graphql/schema"
95
98
  require "graphql/query"
96
99
  require "graphql/directive"
@@ -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"
@@ -26,7 +26,7 @@ module GraphQL
26
26
  schema_class.query_execution_strategy(GraphQL::Execution::Interpreter)
27
27
  schema_class.mutation_execution_strategy(GraphQL::Execution::Interpreter)
28
28
  schema_class.subscription_execution_strategy(GraphQL::Execution::Interpreter)
29
-
29
+ schema_class.add_subscription_extension_if_necessary
30
30
  GraphQL::Schema::Object.include(HandlesRawValue)
31
31
  end
32
32
 
@@ -21,15 +21,11 @@ module GraphQL
21
21
  @argument_values = argument_values
22
22
  end
23
23
 
24
- # Yields `ArgumentValue` instances which contain detailed metadata about each argument.
25
- def each_value
26
- argument_values.each { |arg_v| yield(arg_v) }
27
- end
28
-
29
24
  # @return [Hash{Symbol => ArgumentValue}]
30
25
  attr_reader :argument_values
31
26
 
32
27
  def_delegators :@keyword_arguments, :key?, :[], :keys, :each, :values
28
+ def_delegators :@argument_values, :each_value
33
29
 
34
30
  def inspect
35
31
  "#<#{self.class} @keyword_arguments=#{keyword_arguments.inspect}>"
@@ -182,8 +182,6 @@ module GraphQL
182
182
 
183
183
  kwarg_arguments = resolved_arguments.keyword_arguments
184
184
 
185
- # It might turn out that making arguments for every field is slow.
186
- # If we have to cache them, we'll need a more subtle approach here.
187
185
  field_defn.extras.each do |extra|
188
186
  case extra
189
187
  when :ast_node
@@ -256,7 +254,8 @@ module GraphQL
256
254
  def continue_value(path, value, field, is_non_null, ast_node)
257
255
  if value.nil?
258
256
  if is_non_null
259
- err = GraphQL::InvalidNullError.new(field.owner, field, value)
257
+ parent_type = field.owner_type
258
+ err = parent_type::InvalidNullError.new(parent_type, field, value)
260
259
  write_invalid_null_in_response(path, err)
261
260
  else
262
261
  write_in_response(path, nil)
@@ -306,18 +305,21 @@ module GraphQL
306
305
  write_in_response(path, r)
307
306
  r
308
307
  when "UNION", "INTERFACE"
309
- resolved_type_or_lazy = resolve_type(type, value, path)
308
+ resolved_type_or_lazy, resolved_value = resolve_type(type, value, path)
309
+ resolved_value ||= value
310
+
310
311
  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|
311
312
  possible_types = query.possible_types(type)
312
313
 
313
314
  if !possible_types.include?(resolved_type)
314
- parent_type = field.owner
315
- type_error = GraphQL::UnresolvedTypeError.new(value, field, parent_type, resolved_type, possible_types)
315
+ parent_type = field.owner_type
316
+ err_class = type::UnresolvedTypeError
317
+ type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
316
318
  schema.type_error(type_error, context)
317
319
  write_in_response(path, nil)
318
320
  nil
319
321
  else
320
- continue_field(path, value, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
322
+ continue_field(path, resolved_value, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
321
323
  end
322
324
  end
323
325
  when "OBJECT"
@@ -459,7 +461,13 @@ module GraphQL
459
461
  end
460
462
 
461
463
  def arguments(graphql_object, arg_owner, ast_node)
462
- query.arguments_for(ast_node, arg_owner, parent_object: graphql_object)
464
+ # Don't cache arguments if field extras are requested since extras mutate the argument data structure
465
+ if arg_owner.arguments_statically_coercible? && (!arg_owner.is_a?(GraphQL::Schema::Field) || arg_owner.extras.empty?)
466
+ query.arguments_for(ast_node, arg_owner)
467
+ else
468
+ # The arguments must be prepared in the context of the given object
469
+ query.arguments_for(ast_node, arg_owner, parent_object: graphql_object)
470
+ end
463
471
  end
464
472
 
465
473
  def write_invalid_null_in_response(path, invalid_null_error)
@@ -499,23 +507,11 @@ module GraphQL
499
507
  # at previous parts of the response.
500
508
  # This hash matches the response
501
509
  def type_at(path)
502
- t = @types_at_paths
503
- path.each do |part|
504
- t = t[part] || (raise("Invariant: #{part.inspect} not found in #{t}"))
505
- end
506
- t = t[:__type]
507
- t
510
+ @types_at_paths.fetch(path)
508
511
  end
509
512
 
510
513
  def set_type_at_path(path, type)
511
- types = @types_at_paths
512
- path.each do |part|
513
- types = types[part] ||= {}
514
- end
515
- # Use this magic key so that the hash contains:
516
- # - string keys for nested fields
517
- # - :__type for the object type of a selection
518
- types[:__type] ||= type
514
+ @types_at_paths[path] = type
519
515
  nil
520
516
  end
521
517
 
@@ -545,7 +541,7 @@ module GraphQL
545
541
 
546
542
  def resolve_type(type, value, path)
547
543
  trace_payload = { context: context, type: type, object: value, path: path }
548
- resolved_type = query.trace("resolve_type", trace_payload) do
544
+ resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
549
545
  query.resolve_type(type, value)
550
546
  end
551
547
 
@@ -556,7 +552,7 @@ module GraphQL
556
552
  end
557
553
  end
558
554
  else
559
- resolved_type
555
+ [resolved_type, resolved_value]
560
556
  end
561
557
  end
562
558
 
@@ -34,8 +34,7 @@ module GraphQL
34
34
  @schema = schema
35
35
  @queries = queries
36
36
  @context = context
37
- # TODO remove support for global tracers
38
- @tracers = schema.tracers + GraphQL::Tracing.tracers + (context[:tracers] || [])
37
+ @tracers = schema.tracers + (context[:tracers] || [])
39
38
  # Support `context: {backtrace: true}`
40
39
  if context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
41
40
  @tracers << GraphQL::Backtrace::Tracer
@@ -9,9 +9,9 @@ module GraphQL
9
9
  "query, mutation, and subscription operations."
10
10
 
11
11
  field :types, [GraphQL::Schema::LateBoundType.new("__Type")], "A list of all types supported by this server.", null: false
12
- field :queryType, GraphQL::Schema::LateBoundType.new("__Type"), "The type that query operations will be rooted at.", null: false
13
- field :mutationType, GraphQL::Schema::LateBoundType.new("__Type"), "If this server supports mutation, the type that mutation operations will be rooted at.", null: true
14
- field :subscriptionType, GraphQL::Schema::LateBoundType.new("__Type"), "If this server support subscription, the type that subscription operations will be rooted at.", null: true
12
+ field :query_type, GraphQL::Schema::LateBoundType.new("__Type"), "The type that query operations will be rooted at.", null: false
13
+ field :mutation_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server supports mutation, the type that mutation operations will be rooted at.", null: true
14
+ field :subscription_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server support subscription, the type that subscription operations will be rooted at.", null: true
15
15
  field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false
16
16
 
17
17
  def types
@@ -28,5 +28,23 @@ module GraphQL
28
28
  def parent_error?
29
29
  false
30
30
  end
31
+
32
+ class << self
33
+ attr_accessor :parent_class
34
+
35
+ def subclass_for(parent_class)
36
+ subclass = Class.new(self)
37
+ subclass.parent_class = parent_class
38
+ subclass
39
+ end
40
+
41
+ def inspect
42
+ if name.nil? && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation)
43
+ "#{mutation.inspect}::#{parent_class.graphql_name}::InvalidNullError"
44
+ else
45
+ super
46
+ end
47
+ end
48
+ end
31
49
  end
32
50
  end
@@ -26,6 +26,9 @@ module GraphQL
26
26
  # @return [GraphQL::Query::Context]
27
27
  attr_accessor :context
28
28
 
29
+ # @return [Object] the object this collection belongs to
30
+ attr_accessor :parent
31
+
29
32
  # Raw access to client-provided values. (`max_page_size` not applied to first or last.)
30
33
  attr_accessor :before_value, :after_value, :first_value, :last_value
31
34
 
@@ -49,13 +52,15 @@ module GraphQL
49
52
 
50
53
  # @param items [Object] some unpaginated collection item, like an `Array` or `ActiveRecord::Relation`
51
54
  # @param context [Query::Context]
55
+ # @param parent [Object] The object this collection belongs to
52
56
  # @param first [Integer, nil] The limit parameter from the client, if it provided one
53
57
  # @param after [String, nil] A cursor for pagination, if the client provided one
54
58
  # @param last [Integer, nil] Limit parameter from the client, if provided
55
59
  # @param before [String, nil] A cursor for pagination, if the client provided one.
56
60
  # @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given.
57
- def initialize(items, context: nil, first: nil, after: nil, max_page_size: :not_given, last: nil, before: nil)
61
+ def initialize(items, parent: nil, context: nil, first: nil, after: nil, max_page_size: :not_given, last: nil, before: nil)
58
62
  @items = items
63
+ @parent = parent
59
64
  @context = context
60
65
  @first_value = first
61
66
  @after_value = after
@@ -185,17 +190,19 @@ module GraphQL
185
190
  # A wrapper around paginated items. It includes a {cursor} for pagination
186
191
  # and could be extended with custom relationship-level data.
187
192
  class Edge
188
- def initialize(item, connection)
193
+ attr_reader :node
194
+
195
+ def initialize(node, connection)
189
196
  @connection = connection
190
- @item = item
197
+ @node = node
191
198
  end
192
199
 
193
- def node
194
- @item
200
+ def parent
201
+ @connection.parent
195
202
  end
196
203
 
197
204
  def cursor
198
- @connection.cursor_for(@item)
205
+ @cursor ||= @connection.cursor_for(@node)
199
206
  end
200
207
  end
201
208
  end
@@ -65,21 +65,22 @@ module GraphQL
65
65
 
66
66
  # Used by the runtime to wrap values in connection wrappers.
67
67
  # @api Private
68
- def wrap(field, object, arguments, context, wrappers: all_wrappers)
68
+ def wrap(field, parent, items, arguments, context, wrappers: all_wrappers)
69
69
  impl = nil
70
70
 
71
- object.class.ancestors.each { |cls|
71
+ items.class.ancestors.each { |cls|
72
72
  impl = wrappers[cls]
73
73
  break if impl
74
74
  }
75
75
 
76
76
  if impl.nil?
77
- raise ImplementationMissingError, "Couldn't find a connection wrapper for #{object.class} during #{field.path} (#{object.inspect})"
77
+ raise ImplementationMissingError, "Couldn't find a connection wrapper for #{items.class} during #{field.path} (#{items.inspect})"
78
78
  end
79
79
 
80
80
  impl.new(
81
- object,
81
+ items,
82
82
  context: context,
83
+ parent: parent,
83
84
  max_page_size: field.max_page_size || context.schema.default_max_page_size,
84
85
  first: arguments[:first],
85
86
  after: arguments[:after],
@@ -96,8 +96,7 @@ module GraphQL
96
96
  @fragments = nil
97
97
  @operations = nil
98
98
  @validate = validate
99
- # TODO: remove support for global tracers
100
- @tracers = schema.tracers + GraphQL::Tracing.tracers + (context ? context.fetch(:tracers, []) : [])
99
+ @tracers = schema.tracers + (context ? context.fetch(:tracers, []) : [])
101
100
  # Support `ctx[:backtrace] = true` for wrapping backtraces
102
101
  if context && context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
103
102
  @tracers << GraphQL::Backtrace::Tracer
@@ -1075,6 +1075,7 @@ module GraphQL
1075
1075
  raise GraphQL::Error, "Second definition of `subscription(...)` (#{new_subscription_object.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
1076
1076
  else
1077
1077
  @subscription_object = new_subscription_object
1078
+ add_subscription_extension_if_necessary
1078
1079
  add_type_and_traverse(new_subscription_object, root: true)
1079
1080
  nil
1080
1081
  end
@@ -1389,7 +1390,7 @@ module GraphQL
1389
1390
  # rubocop:disable Lint/DuplicateMethods
1390
1391
  module ResolveTypeWithType
1391
1392
  def resolve_type(type, obj, ctx)
1392
- first_resolved_type = if type.is_a?(Module) && type.respond_to?(:resolve_type)
1393
+ first_resolved_type, resolved_value = if type.is_a?(Module) && type.respond_to?(:resolve_type)
1393
1394
  type.resolve_type(obj, ctx)
1394
1395
  else
1395
1396
  super
@@ -1397,7 +1398,11 @@ module GraphQL
1397
1398
 
1398
1399
  after_lazy(first_resolved_type) do |resolved_type|
1399
1400
  if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind)) || resolved_type.is_a?(GraphQL::BaseType)
1400
- resolved_type
1401
+ if resolved_value
1402
+ [resolved_type, resolved_value]
1403
+ else
1404
+ resolved_type
1405
+ end
1401
1406
  else
1402
1407
  raise ".resolve_type should return a type definition, but got #{resolved_type.inspect} (#{resolved_type.class}) from `resolve_type(#{type}, #{obj}, #{ctx})`"
1403
1408
  end
@@ -1506,7 +1511,11 @@ module GraphQL
1506
1511
 
1507
1512
  # @return [GraphQL::Execution::Errors, Class<GraphQL::Execution::Errors::NullErrorHandler>]
1508
1513
  def error_handler
1509
- @error_handler ||= GraphQL::Execution::Errors::NullErrorHandler
1514
+ if defined?(@error_handler)
1515
+ @error_handler
1516
+ else
1517
+ find_inherited_value(:error_handler, GraphQL::Execution::Errors::NullErrorHandler)
1518
+ end
1510
1519
  end
1511
1520
 
1512
1521
  def lazy_resolve(lazy_class, value_method)
@@ -1640,6 +1649,20 @@ module GraphQL
1640
1649
  end
1641
1650
  end
1642
1651
 
1652
+ # @api private
1653
+ def add_subscription_extension_if_necessary
1654
+ if interpreter? && !defined?(@subscription_extension_added) && subscription && self.subscriptions
1655
+ @subscription_extension_added = true
1656
+ if subscription.singleton_class.ancestors.include?(Subscriptions::SubscriptionRoot)
1657
+ warn("`extend Subscriptions::SubscriptionRoot` is no longer required; you may remove it from #{self}'s `subscription` root type (#{subscription}).")
1658
+ else
1659
+ subscription.fields.each do |name, field|
1660
+ field.extension(Subscriptions::DefaultSubscriptionResolveExtension)
1661
+ end
1662
+ end
1663
+ end
1664
+ end
1665
+
1643
1666
  private
1644
1667
 
1645
1668
  def lazy_methods
@@ -154,6 +154,12 @@ module GraphQL
154
154
  raise ArgumentError, "Couldn't build type for Argument #{@owner.name}.#{name}: #{err.class.name}: #{err.message}", err.backtrace
155
155
  end
156
156
 
157
+ def statically_coercible?
158
+ return @statically_coercible if defined?(@statically_coercible)
159
+
160
+ @statically_coercible = !@prepare.is_a?(String) && !@prepare.is_a?(Symbol)
161
+ end
162
+
157
163
  # Apply the {prepare} configuration to `value`, using methods from `obj`.
158
164
  # Used by the runtime.
159
165
  # @api private