graphql 1.10.14 → 1.11.4

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +8 -0
  3. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  4. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  5. data/lib/generators/graphql/templates/base_field.erb +2 -0
  6. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  7. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  8. data/lib/generators/graphql/templates/base_mutation.erb +2 -0
  9. data/lib/generators/graphql/templates/base_object.erb +2 -0
  10. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  11. data/lib/generators/graphql/templates/base_union.erb +2 -0
  12. data/lib/generators/graphql/templates/enum.erb +2 -0
  13. data/lib/generators/graphql/templates/graphql_controller.erb +13 -9
  14. data/lib/generators/graphql/templates/interface.erb +2 -0
  15. data/lib/generators/graphql/templates/loader.erb +2 -0
  16. data/lib/generators/graphql/templates/mutation.erb +2 -0
  17. data/lib/generators/graphql/templates/mutation_type.erb +2 -0
  18. data/lib/generators/graphql/templates/object.erb +2 -0
  19. data/lib/generators/graphql/templates/query_type.erb +2 -0
  20. data/lib/generators/graphql/templates/scalar.erb +2 -0
  21. data/lib/generators/graphql/templates/schema.erb +2 -0
  22. data/lib/generators/graphql/templates/union.erb +2 -0
  23. data/lib/graphql.rb +3 -3
  24. data/lib/graphql/execution/interpreter.rb +1 -1
  25. data/lib/graphql/execution/interpreter/runtime.rb +26 -26
  26. data/lib/graphql/execution/multiplex.rb +1 -2
  27. data/lib/graphql/introspection/schema_type.rb +3 -3
  28. data/lib/graphql/invalid_null_error.rb +18 -0
  29. data/lib/graphql/language/nodes.rb +1 -0
  30. data/lib/graphql/language/visitor.rb +2 -2
  31. data/lib/graphql/pagination/connection.rb +18 -13
  32. data/lib/graphql/pagination/connections.rb +17 -4
  33. data/lib/graphql/query.rb +1 -2
  34. data/lib/graphql/query/context.rb +20 -1
  35. data/lib/graphql/query/fingerprint.rb +2 -0
  36. data/lib/graphql/query/validation_pipeline.rb +3 -0
  37. data/lib/graphql/schema.rb +26 -16
  38. data/lib/graphql/schema/build_from_definition.rb +7 -12
  39. data/lib/graphql/schema/build_from_definition/resolve_map.rb +3 -1
  40. data/lib/graphql/schema/enum_value.rb +1 -0
  41. data/lib/graphql/schema/field.rb +63 -77
  42. data/lib/graphql/schema/field/connection_extension.rb +42 -32
  43. data/lib/graphql/schema/loader.rb +19 -1
  44. data/lib/graphql/schema/member/has_fields.rb +15 -5
  45. data/lib/graphql/schema/mutation.rb +4 -0
  46. data/lib/graphql/schema/object.rb +1 -1
  47. data/lib/graphql/schema/resolver.rb +20 -0
  48. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -1
  49. data/lib/graphql/schema/subscription.rb +3 -13
  50. data/lib/graphql/schema/union.rb +29 -0
  51. data/lib/graphql/schema/warden.rb +2 -4
  52. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +4 -2
  53. data/lib/graphql/subscriptions.rb +69 -24
  54. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +66 -11
  55. data/lib/graphql/subscriptions/broadcast_analyzer.rb +84 -0
  56. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  57. data/lib/graphql/subscriptions/event.rb +16 -1
  58. data/lib/graphql/subscriptions/serialize.rb +22 -4
  59. data/lib/graphql/subscriptions/subscription_root.rb +3 -1
  60. data/lib/graphql/tracing.rb +1 -27
  61. data/lib/graphql/tracing/appoptics_tracing.rb +10 -2
  62. data/lib/graphql/tracing/platform_tracing.rb +25 -15
  63. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  64. data/lib/graphql/types/iso_8601_date_time.rb +2 -1
  65. data/lib/graphql/types/relay/base_connection.rb +6 -5
  66. data/lib/graphql/version.rb +1 -1
  67. metadata +5 -2
@@ -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
@@ -524,6 +524,7 @@ module GraphQL
524
524
 
525
525
  # Usage of a variable in a query. Name does _not_ include `$`.
526
526
  class VariableIdentifier < NameOnlyNode
527
+ self.children_method_name = :value
527
528
  end
528
529
 
529
530
  class SchemaDefinition < AbstractNode
@@ -89,7 +89,7 @@ module GraphQL
89
89
  # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
90
90
  # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
91
91
  def on_abstract_node(node, parent)
92
- if node == DELETE_NODE
92
+ if node.equal?(DELETE_NODE)
93
93
  # This might be passed to `super(DELETE_NODE, ...)`
94
94
  # by a user hook, don't want to keep visiting in that case.
95
95
  nil
@@ -179,7 +179,7 @@ module GraphQL
179
179
  # The user-provided hook returned a new node.
180
180
  new_parent = new_parent && new_parent.replace_child(node, new_node)
181
181
  return new_node, new_parent
182
- elsif new_node == DELETE_NODE
182
+ elsif new_node.equal?(DELETE_NODE)
183
183
  # The user-provided hook requested to remove this node
184
184
  new_parent = new_parent && new_parent.delete_child(node)
185
185
  return nil, new_parent
@@ -15,17 +15,15 @@ module GraphQL
15
15
  class PaginationImplementationMissingError < GraphQL::Error
16
16
  end
17
17
 
18
- # @return [Class] The class to use for wrapping items as `edges { ... }`. Defaults to `Connection::Edge`
19
- def self.edge_class
20
- self::Edge
21
- end
22
-
23
18
  # @return [Object] A list object, from the application. This is the unpaginated value passed into the connection.
24
19
  attr_reader :items
25
20
 
26
21
  # @return [GraphQL::Query::Context]
27
22
  attr_accessor :context
28
23
 
24
+ # @return [Object] the object this collection belongs to
25
+ attr_accessor :parent
26
+
29
27
  # Raw access to client-provided values. (`max_page_size` not applied to first or last.)
30
28
  attr_accessor :before_value, :after_value, :first_value, :last_value
31
29
 
@@ -49,19 +47,21 @@ module GraphQL
49
47
 
50
48
  # @param items [Object] some unpaginated collection item, like an `Array` or `ActiveRecord::Relation`
51
49
  # @param context [Query::Context]
50
+ # @param parent [Object] The object this collection belongs to
52
51
  # @param first [Integer, nil] The limit parameter from the client, if it provided one
53
52
  # @param after [String, nil] A cursor for pagination, if the client provided one
54
53
  # @param last [Integer, nil] Limit parameter from the client, if provided
55
54
  # @param before [String, nil] A cursor for pagination, if the client provided one.
56
55
  # @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)
56
+ def initialize(items, parent: nil, context: nil, first: nil, after: nil, max_page_size: :not_given, last: nil, before: nil, edge_class: nil)
58
57
  @items = items
58
+ @parent = parent
59
59
  @context = context
60
60
  @first_value = first
61
61
  @after_value = after
62
62
  @last_value = last
63
63
  @before_value = before
64
-
64
+ @edge_class = edge_class || self.class::Edge
65
65
  # This is only true if the object was _initialized_ with an override
66
66
  # or if one is assigned later.
67
67
  @has_max_page_size_override = max_page_size != :not_given
@@ -112,9 +112,12 @@ module GraphQL
112
112
 
113
113
  # @return [Array<Edge>] {nodes}, but wrapped with Edge instances
114
114
  def edges
115
- @edges ||= nodes.map { |n| self.class.edge_class.new(n, self) }
115
+ @edges ||= nodes.map { |n| @edge_class.new(n, self) }
116
116
  end
117
117
 
118
+ # @return [Class] A wrapper class for edges of this connection
119
+ attr_accessor :edge_class
120
+
118
121
  # @return [Array<Object>] A slice of {items}, constrained by {@first_value}/{@after_value}/{@last_value}/{@before_value}
119
122
  def nodes
120
123
  raise PaginationImplementationMissingError, "Implement #{self.class}#nodes to paginate `@items`"
@@ -185,17 +188,19 @@ module GraphQL
185
188
  # A wrapper around paginated items. It includes a {cursor} for pagination
186
189
  # and could be extended with custom relationship-level data.
187
190
  class Edge
188
- def initialize(item, connection)
191
+ attr_reader :node
192
+
193
+ def initialize(node, connection)
189
194
  @connection = connection
190
- @item = item
195
+ @node = node
191
196
  end
192
197
 
193
- def node
194
- @item
198
+ def parent
199
+ @connection.parent
195
200
  end
196
201
 
197
202
  def cursor
198
- @connection.cursor_for(@item)
203
+ @cursor ||= @connection.cursor_for(@node)
199
204
  end
200
205
  end
201
206
  end
@@ -65,29 +65,42 @@ 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],
86
87
  last: arguments[:last],
87
88
  before: arguments[:before],
89
+ edge_class: edge_class_for_field(field),
88
90
  )
89
91
  end
90
92
 
93
+ # use an override if there is one
94
+ # @api private
95
+ def edge_class_for_field(field)
96
+ conn_type = field.type.unwrap
97
+ conn_type_edge_type = conn_type.respond_to?(:edge_class) && conn_type.edge_class
98
+ if conn_type_edge_type && conn_type_edge_type != Relay::Edge
99
+ conn_type_edge_type
100
+ else
101
+ nil
102
+ end
103
+ end
91
104
  protected
92
105
 
93
106
  attr_reader :wrappers
@@ -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
@@ -168,7 +168,6 @@ module GraphQL
168
168
  attr_accessor :scoped_context
169
169
 
170
170
  def_delegators :@provided_values, :[]=
171
- def_delegators :to_h, :fetch, :dig
172
171
  def_delegators :@query, :trace, :interpreter?
173
172
 
174
173
  # @!method []=(key, value)
@@ -180,6 +179,26 @@ module GraphQL
180
179
  @provided_values[key]
181
180
  end
182
181
 
182
+ UNSPECIFIED_FETCH_DEFAULT = Object.new
183
+
184
+ def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT)
185
+ if @scoped_context.key?(key)
186
+ @scoped_context[key]
187
+ elsif @provided_values.key?(key)
188
+ @provided_values[key]
189
+ elsif default != UNSPECIFIED_FETCH_DEFAULT
190
+ default
191
+ elsif block_given?
192
+ yield(self, key)
193
+ else
194
+ raise KeyError.new(key: key)
195
+ end
196
+ end
197
+
198
+ def dig(key, *other_keys)
199
+ @scoped_context.key?(key) ? @scoped_context.dig(key, *other_keys) : @provided_values.dig(key, *other_keys)
200
+ end
201
+
183
202
  def to_h
184
203
  @provided_values.merge(@scoped_context)
185
204
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'digest/sha2'
4
+
3
5
  module GraphQL
4
6
  class Query
5
7
  # @api private
@@ -90,6 +90,9 @@ module GraphQL
90
90
  end
91
91
 
92
92
  @valid = @validation_errors.empty?
93
+ rescue SystemStackError => err
94
+ @valid = false
95
+ @schema.query_stack_error(@query, err)
93
96
  end
94
97
 
95
98
  # If there are max_* values, add them,
@@ -127,7 +127,7 @@ module GraphQL
127
127
  end
128
128
  end
129
129
 
130
- # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered wtih {#lazy_resolve}.
130
+ # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {#lazy_resolve}.
131
131
  def lazy_method_name(obj)
132
132
  lazy_methods.get(obj)
133
133
  end
@@ -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
@@ -1533,9 +1534,9 @@ module GraphQL
1533
1534
 
1534
1535
  # Add several directives at once
1535
1536
  # @param new_directives [Class]
1536
- def directives(new_directives = nil)
1537
- if new_directives
1538
- new_directives.each { |d| directive(d) }
1537
+ def directives(*new_directives)
1538
+ if new_directives.any?
1539
+ new_directives.flatten.each { |d| directive(d) }
1539
1540
  end
1540
1541
 
1541
1542
  find_inherited_value(:directives, default_directives).merge(own_directives)
@@ -1549,11 +1550,11 @@ module GraphQL
1549
1550
  end
1550
1551
 
1551
1552
  def default_directives
1552
- {
1553
+ @default_directives ||= {
1553
1554
  "include" => GraphQL::Schema::Directive::Include,
1554
1555
  "skip" => GraphQL::Schema::Directive::Skip,
1555
1556
  "deprecated" => GraphQL::Schema::Directive::Deprecated,
1556
- }
1557
+ }.freeze
1557
1558
  end
1558
1559
 
1559
1560
  def tracer(new_tracer)
@@ -1648,6 +1649,24 @@ module GraphQL
1648
1649
  end
1649
1650
  end
1650
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
+
1666
+ def query_stack_error(query, err)
1667
+ query.analysis_errors.push(GraphQL::AnalysisError.new("This query is too large to execute."))
1668
+ end
1669
+
1651
1670
  private
1652
1671
 
1653
1672
  def lazy_methods
@@ -1768,16 +1787,7 @@ module GraphQL
1768
1787
  if owner.kind.union?
1769
1788
  # It's a union with possible_types
1770
1789
  # Replace the item by class name
1771
- owner.type_memberships.each { |tm|
1772
- possible_type = tm.object_type
1773
- if possible_type.is_a?(String) && (possible_type == type.name)
1774
- # This is a match of Ruby class names, not graphql names,
1775
- # since strings are used to refer to constants.
1776
- tm.object_type = type
1777
- elsif possible_type.is_a?(LateBoundType) && possible_type.graphql_name == type.graphql_name
1778
- tm.object_type = type
1779
- end
1780
- }
1790
+ owner.assign_type_membership_object_type(type)
1781
1791
  own_possible_types[owner.graphql_name] = owner.possible_types
1782
1792
  elsif type.kind.interface? && owner.kind.object?
1783
1793
  new_interfaces = []
@@ -43,9 +43,7 @@ module GraphQL
43
43
  when GraphQL::Language::Nodes::EnumTypeDefinition
44
44
  types[definition.name] = build_enum_type(definition, type_resolver)
45
45
  when GraphQL::Language::Nodes::ObjectTypeDefinition
46
- is_subscription_root = (definition.name == "Subscription" && (schema_definition.nil? || schema_definition.subscription.nil?)) || (schema_definition && (definition.name == schema_definition.subscription))
47
- should_extend_subscription_root = is_subscription_root && interpreter
48
- types[definition.name] = build_object_type(definition, type_resolver, default_resolve: default_resolve, extend_subscription_root: should_extend_subscription_root)
46
+ types[definition.name] = build_object_type(definition, type_resolver, default_resolve: default_resolve)
49
47
  when GraphQL::Language::Nodes::InterfaceTypeDefinition
50
48
  types[definition.name] = build_interface_type(definition, type_resolver)
51
49
  when GraphQL::Language::Nodes::UnionTypeDefinition
@@ -204,7 +202,7 @@ module GraphQL
204
202
  end
205
203
  end
206
204
 
207
- def build_object_type(object_type_definition, type_resolver, default_resolve:, extend_subscription_root:)
205
+ def build_object_type(object_type_definition, type_resolver, default_resolve:)
208
206
  builder = self
209
207
  type_def = nil
210
208
  typed_resolve_fn = ->(field, obj, args, ctx) { default_resolve.call(type_def, field, obj, args, ctx) }
@@ -214,10 +212,6 @@ module GraphQL
214
212
  graphql_name(object_type_definition.name)
215
213
  description(object_type_definition.description)
216
214
  ast_node(object_type_definition)
217
- if extend_subscription_root
218
- # This has to come before `field ...` configurations since it modifies them
219
- extend Subscriptions::SubscriptionRoot
220
- end
221
215
 
222
216
  object_type_definition.interfaces.each do |interface_name|
223
217
  interface_defn = type_resolver.call(interface_name)
@@ -303,7 +297,7 @@ module GraphQL
303
297
 
304
298
  field_definitions.map do |field_definition|
305
299
  type_name = resolve_type_name(field_definition.type)
306
-
300
+ resolve_method_name = "resolve_field_#{field_definition.name}"
307
301
  owner.field(
308
302
  field_definition.name,
309
303
  description: field_definition.description,
@@ -315,14 +309,15 @@ module GraphQL
315
309
  ast_node: field_definition,
316
310
  method_conflict_warning: false,
317
311
  camelize: false,
312
+ resolver_method: resolve_method_name,
318
313
  ) do
319
314
  builder.build_arguments(self, field_definition.arguments, type_resolver)
320
315
 
321
316
  # Don't do this for interfaces
322
317
  if default_resolve
323
- # TODO fragile hack. formalize this API?
324
- define_singleton_method :resolve_field_method do |obj, args, ctx|
325
- default_resolve.call(self, obj.object, args, ctx)
318
+ owner.send(:define_method, resolve_method_name) do |**args|
319
+ field_instance = self.class.get_field(field_definition.name)
320
+ default_resolve.call(field_instance, object, args, context)
326
321
  end
327
322
  end
328
323
  end
@@ -45,8 +45,10 @@ module GraphQL
45
45
  @resolve_hash[type_name_s][field_name.to_s] = resolve_fn
46
46
  end
47
47
  when Proc
48
- # for example, __resolve_type
48
+ # for example, "resolve_type"
49
49
  @resolve_hash[type_name_s] = fields
50
+ else
51
+ raise ArgumentError, "Unexpected resolve hash value for #{type_name.inspect}: #{fields.inspect} (#{fields.class})"
50
52
  end
51
53
  end
52
54
 
@@ -41,6 +41,7 @@ module GraphQL
41
41
 
42
42
  def initialize(graphql_name, desc = nil, owner:, ast_node: nil, description: nil, value: nil, deprecation_reason: nil, &block)
43
43
  @graphql_name = graphql_name.to_s
44
+ GraphQL::NameValidator.validate!(@graphql_name)
44
45
  @description = desc || description
45
46
  @value = value.nil? ? @graphql_name : value
46
47
  @deprecation_reason = deprecation_reason
@@ -36,9 +36,18 @@ module GraphQL
36
36
  # @return [Symbol] The method on the type to look up
37
37
  attr_reader :resolver_method
38
38
 
39
- # @return [Class] The type that this field belongs to
39
+ # @return [Class] The thing this field was defined on (type, mutation, resolver)
40
40
  attr_accessor :owner
41
41
 
42
+ # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type)
43
+ def owner_type
44
+ @owner_type ||= if owner < GraphQL::Schema::Mutation
45
+ owner.payload_type
46
+ else
47
+ owner
48
+ end
49
+ end
50
+
42
51
  # @return [Symbol] the original name of the field, passed in by the user
43
52
  attr_reader :original_name
44
53
 
@@ -191,9 +200,10 @@ module GraphQL
191
200
  # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads
192
201
  # @param extensions [Array<Class, Hash<Class => Object>>] Named extensions to apply to this field (see also {#extension})
193
202
  # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
203
+ # @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts
194
204
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
195
205
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
196
- def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, arguments: EMPTY_HASH, &definition_block)
206
+ def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, &definition_block)
197
207
  if name.nil?
198
208
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
199
209
  end
@@ -237,8 +247,8 @@ module GraphQL
237
247
  end
238
248
 
239
249
  # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
240
- method_name = method || hash_key || @underscored_name
241
- resolver_method ||= @underscored_name.to_sym
250
+ method_name = method || hash_key || name_s
251
+ resolver_method ||= name_s.to_sym
242
252
 
243
253
  @method_str = method_name.to_s
244
254
  @method_sym = method_name.to_sym
@@ -251,6 +261,7 @@ module GraphQL
251
261
  @max_page_size = max_page_size == :not_given ? nil : max_page_size
252
262
  @introspection = introspection
253
263
  @extras = extras
264
+ @broadcastable = broadcastable
254
265
  @resolver_class = resolver_class
255
266
  @scope = scope
256
267
  @trace = trace
@@ -295,6 +306,13 @@ module GraphQL
295
306
  end
296
307
  end
297
308
 
309
+ # If true, subscription updates with this field can be shared between viewers
310
+ # @return [Boolean, nil]
311
+ # @see GraphQL::Subscriptions::BroadcastAnalyzer
312
+ def broadcastable?
313
+ @broadcastable
314
+ end
315
+
298
316
  # @param text [String]
299
317
  # @return [String]
300
318
  def description(text = nil)
@@ -534,7 +552,7 @@ module GraphQL
534
552
  @resolve_proc.call(extended_obj, args, ctx)
535
553
  end
536
554
  else
537
- public_send_field(after_obj, ruby_args, ctx)
555
+ public_send_field(after_obj, ruby_args, query_ctx)
538
556
  end
539
557
  else
540
558
  err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self)
@@ -558,30 +576,7 @@ module GraphQL
558
576
  application_object = object.object
559
577
  ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
560
578
  if is_authorized
561
- # Apply field extensions
562
- with_extensions(object, args, ctx) do |extended_obj, extended_args|
563
- field_receiver = if @resolver_class
564
- resolver_obj = if extended_obj.is_a?(GraphQL::Schema::Object)
565
- extended_obj.object
566
- else
567
- extended_obj
568
- end
569
- @resolver_class.new(object: resolver_obj, context: ctx, field: self)
570
- else
571
- extended_obj
572
- end
573
-
574
- if field_receiver.respond_to?(@resolver_method)
575
- # Call the method with kwargs, if there are any
576
- if extended_args.any?
577
- field_receiver.public_send(@resolver_method, **extended_args)
578
- else
579
- field_receiver.public_send(@resolver_method)
580
- end
581
- else
582
- resolve_field_method(field_receiver, extended_args, ctx)
583
- end
584
- end
579
+ public_send_field(object, args, ctx)
585
580
  else
586
581
  err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
587
582
  ctx.schema.unauthorized_field(err)
@@ -597,43 +592,6 @@ module GraphQL
597
592
  err
598
593
  end
599
594
 
600
- # Find a way to resolve this field, checking:
601
- #
602
- # - Hash keys, if the wrapped object is a hash;
603
- # - A method on the wrapped object;
604
- # - Or, raise not implemented.
605
- #
606
- # This can be overridden by defining a method on the object type.
607
- # @param obj [GraphQL::Schema::Object]
608
- # @param ruby_kwargs [Hash<Symbol => Object>]
609
- # @param ctx [GraphQL::Query::Context]
610
- def resolve_field_method(obj, ruby_kwargs, ctx)
611
- if obj.object.is_a?(Hash)
612
- inner_object = obj.object
613
- if inner_object.key?(@method_sym)
614
- inner_object[@method_sym]
615
- else
616
- inner_object[@method_str]
617
- end
618
- elsif obj.object.respond_to?(@method_sym)
619
- if ruby_kwargs.any?
620
- obj.object.public_send(@method_sym, **ruby_kwargs)
621
- else
622
- obj.object.public_send(@method_sym)
623
- end
624
- else
625
- raise <<-ERR
626
- Failed to implement #{@owner.graphql_name}.#{@name}, tried:
627
-
628
- - `#{obj.class}##{@resolver_method}`, which did not exist
629
- - `#{obj.object.class}##{@method_sym}`, which did not exist
630
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
631
-
632
- To implement this field, define one of the methods above (and check for typos)
633
- ERR
634
- end
635
- end
636
-
637
595
  # @param ctx [GraphQL::Query::Context::FieldResolutionContext]
638
596
  def fetch_extra(extra_name, ctx)
639
597
  if extra_name != :path && extra_name != :ast_node && respond_to?(extra_name)
@@ -704,24 +662,52 @@ module GraphQL
704
662
  end
705
663
  end
706
664
 
707
- def public_send_field(obj, ruby_kwargs, field_ctx)
708
- query_ctx = field_ctx.query.context
709
- with_extensions(obj, ruby_kwargs, query_ctx) do |extended_obj, extended_args|
665
+ def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
666
+ with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
710
667
  if @resolver_class
711
- if extended_obj.is_a?(GraphQL::Schema::Object)
712
- extended_obj = extended_obj.object
668
+ if obj.is_a?(GraphQL::Schema::Object)
669
+ obj = obj.object
713
670
  end
714
- extended_obj = @resolver_class.new(object: extended_obj, context: query_ctx, field: self)
671
+ obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
715
672
  end
716
673
 
717
- if extended_obj.respond_to?(@resolver_method)
718
- if extended_args.any?
719
- extended_obj.public_send(@resolver_method, **extended_args)
674
+ # Find a way to resolve this field, checking:
675
+ #
676
+ # - A method on the type instance;
677
+ # - Hash keys, if the wrapped object is a hash;
678
+ # - A method on the wrapped object;
679
+ # - Or, raise not implemented.
680
+ #
681
+ if obj.respond_to?(@resolver_method)
682
+ # Call the method with kwargs, if there are any
683
+ if ruby_kwargs.any?
684
+ obj.public_send(@resolver_method, **ruby_kwargs)
685
+ else
686
+ obj.public_send(@resolver_method)
687
+ end
688
+ elsif obj.object.is_a?(Hash)
689
+ inner_object = obj.object
690
+ if inner_object.key?(@method_sym)
691
+ inner_object[@method_sym]
720
692
  else
721
- extended_obj.public_send(@resolver_method)
693
+ inner_object[@method_str]
694
+ end
695
+ elsif obj.object.respond_to?(@method_sym)
696
+ if ruby_kwargs.any?
697
+ obj.object.public_send(@method_sym, **ruby_kwargs)
698
+ else
699
+ obj.object.public_send(@method_sym)
722
700
  end
723
701
  else
724
- resolve_field_method(extended_obj, extended_args, query_ctx)
702
+ raise <<-ERR
703
+ Failed to implement #{@owner.graphql_name}.#{@name}, tried:
704
+
705
+ - `#{obj.class}##{@resolver_method}`, which did not exist
706
+ - `#{obj.object.class}##{@method_sym}`, which did not exist
707
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
708
+
709
+ To implement this field, define one of the methods above (and check for typos)
710
+ ERR
725
711
  end
726
712
  end
727
713
  end