graphql 1.10.14 → 1.11.4

Sign up to get free protection for your applications and to get access to all the features.
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