graphql 1.10.10 → 1.11.1

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 (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