graphql 1.10.12 → 1.11.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/templates/graphql_controller.erb +11 -9
  3. data/lib/graphql.rb +3 -3
  4. data/lib/graphql/directive.rb +4 -0
  5. data/lib/graphql/execution/interpreter.rb +1 -1
  6. data/lib/graphql/execution/interpreter/runtime.rb +6 -4
  7. data/lib/graphql/execution/multiplex.rb +1 -2
  8. data/lib/graphql/field.rb +4 -0
  9. data/lib/graphql/input_object_type.rb +4 -0
  10. data/lib/graphql/introspection/schema_type.rb +3 -3
  11. data/lib/graphql/invalid_null_error.rb +18 -0
  12. data/lib/graphql/language/nodes.rb +1 -0
  13. data/lib/graphql/language/visitor.rb +2 -2
  14. data/lib/graphql/pagination/connection.rb +18 -13
  15. data/lib/graphql/pagination/connections.rb +17 -4
  16. data/lib/graphql/query.rb +1 -2
  17. data/lib/graphql/schema.rb +22 -16
  18. data/lib/graphql/schema/argument.rb +5 -0
  19. data/lib/graphql/schema/build_from_definition.rb +7 -12
  20. data/lib/graphql/schema/enum_value.rb +1 -0
  21. data/lib/graphql/schema/field.rb +63 -77
  22. data/lib/graphql/schema/field/connection_extension.rb +5 -1
  23. data/lib/graphql/schema/input_object.rb +16 -15
  24. data/lib/graphql/schema/loader.rb +19 -1
  25. data/lib/graphql/schema/member/has_arguments.rb +16 -0
  26. data/lib/graphql/schema/object.rb +1 -1
  27. data/lib/graphql/schema/resolver.rb +14 -0
  28. data/lib/graphql/schema/subscription.rb +1 -1
  29. data/lib/graphql/schema/union.rb +29 -0
  30. data/lib/graphql/schema/warden.rb +6 -1
  31. data/lib/graphql/static_validation/literal_validator.rb +7 -7
  32. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  33. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
  34. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -2
  35. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +4 -2
  36. data/lib/graphql/subscriptions.rb +41 -8
  37. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +66 -11
  38. data/lib/graphql/subscriptions/broadcast_analyzer.rb +84 -0
  39. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  40. data/lib/graphql/subscriptions/event.rb +16 -1
  41. data/lib/graphql/subscriptions/serialize.rb +22 -4
  42. data/lib/graphql/subscriptions/subscription_root.rb +3 -1
  43. data/lib/graphql/tracing.rb +1 -27
  44. data/lib/graphql/tracing/platform_tracing.rb +25 -15
  45. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  46. data/lib/graphql/version.rb +1 -1
  47. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 840c0d49f547bfb3a44878e0631df87f3b287179a077806169480a8d2820b30b
4
- data.tar.gz: 498c331da5d7f3818054def18b1a964f1ff97699b79b7be1635eefbad0dcc946
3
+ metadata.gz: bafdf456b47766daa370e9f8559e2f525b37d24b6f5a253742eba71759428f29
4
+ data.tar.gz: f2dc55cb9c2777104f23358a27e01328ed4b4df5334ce237e9af1cae731c3ab7
5
5
  SHA512:
6
- metadata.gz: 0cc7bdbeff9a1c70f28c50ffa93740020173a969d34e9aa8872073f721555ebe1c67fa74e13d012d6c547bc4aa6515ab7dafe173dd75bfdea9fe0c4a2b256ac8
7
- data.tar.gz: a466d22dd0e14f95b7273faedc68d3ff214e0f72411e491f454d037b1b74034a67b1eb6334cd567334cf5856c1b91e65a38024955426079c317c43eee47557c7
6
+ metadata.gz: f7120485f538e60577a55b9674cb21e89785d10becfb5545e8412c284547b4c066c3c7abff15b53edca990cb49fd659eeb0df16e84f6bfb511aca89dd41d2834
7
+ data.tar.gz: a48a2c59655e72c44d711f69cd94c7649a0ada18e1dba50cabadaa8a2748b12021ed7f03a6accca78bfba4aab93de2d37cf0b5e119f3f08a12a7d9784667af15
@@ -5,7 +5,7 @@ class GraphqlController < ApplicationController
5
5
  # protect_from_forgery with: :null_session
6
6
 
7
7
  def execute
8
- variables = ensure_hash(params[:variables])
8
+ variables = prepare_variables(params[:variables])
9
9
  query = params[:query]
10
10
  operation_name = params[:operationName]
11
11
  context = {
@@ -21,21 +21,23 @@ class GraphqlController < ApplicationController
21
21
 
22
22
  private
23
23
 
24
- # Handle form data, JSON body, or a blank value
25
- def ensure_hash(ambiguous_param)
26
- case ambiguous_param
24
+ # Handle variables in form data, JSON body, or a blank value
25
+ def prepare_variables(variables_param)
26
+ case variables_param
27
27
  when String
28
- if ambiguous_param.present?
29
- ensure_hash(JSON.parse(ambiguous_param))
28
+ if variables_param.present?
29
+ JSON.parse(variables_param) || {}
30
30
  else
31
31
  {}
32
32
  end
33
- when Hash, ActionController::Parameters
34
- ambiguous_param
33
+ when Hash
34
+ variables_param
35
+ when ActionController::Parameters
36
+ variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables.
35
37
  when nil
36
38
  {}
37
39
  else
38
- raise ArgumentError, "Unexpected parameter: #{ambiguous_param}"
40
+ raise ArgumentError, "Unexpected parameter: #{variables_param}"
39
41
  end
40
42
  end
41
43
 
@@ -91,13 +91,13 @@ 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"
97
100
  require "graphql/execution"
98
- require "graphql/runtime_type_error"
99
- require "graphql/unresolved_type_error"
100
- require "graphql/invalid_null_error"
101
101
  require "graphql/types"
102
102
  require "graphql/relay"
103
103
  require "graphql/boolean_type"
@@ -99,6 +99,10 @@ module GraphQL
99
99
  def type_class
100
100
  metadata[:type_class]
101
101
  end
102
+
103
+ def get_argument(argument_name)
104
+ arguments[argument_name]
105
+ end
102
106
  end
103
107
  end
104
108
 
@@ -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
 
@@ -254,7 +254,8 @@ module GraphQL
254
254
  def continue_value(path, value, field, is_non_null, ast_node)
255
255
  if value.nil?
256
256
  if is_non_null
257
- err = field.owner::InvalidNullError.new(field.owner, field, value)
257
+ parent_type = field.owner_type
258
+ err = parent_type::InvalidNullError.new(parent_type, field, value)
258
259
  write_invalid_null_in_response(path, err)
259
260
  else
260
261
  write_in_response(path, nil)
@@ -311,7 +312,7 @@ module GraphQL
311
312
  possible_types = query.possible_types(type)
312
313
 
313
314
  if !possible_types.include?(resolved_type)
314
- parent_type = field.owner
315
+ parent_type = field.owner_type
315
316
  err_class = type::UnresolvedTypeError
316
317
  type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
317
318
  schema.type_error(type_error, context)
@@ -460,8 +461,9 @@ module GraphQL
460
461
  end
461
462
 
462
463
  def arguments(graphql_object, arg_owner, ast_node)
463
- # Don't cache arguments if field extras are requested since extras mutate the argument data structure
464
- if arg_owner.arguments_statically_coercible? && (!arg_owner.is_a?(GraphQL::Schema::Field) || arg_owner.extras.empty?)
464
+ # Don't cache arguments if field extras or extensions are requested since they can mutate the argument data structure
465
+ if arg_owner.arguments_statically_coercible? &&
466
+ (!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?))
465
467
  query.arguments_for(ast_node, arg_owner)
466
468
  else
467
469
  # The arguments must be prepared in the context of the given object
@@ -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
@@ -207,6 +207,10 @@ module GraphQL
207
207
  metadata[:type_class]
208
208
  end
209
209
 
210
+ def get_argument(argument_name)
211
+ arguments[argument_name]
212
+ end
213
+
210
214
  private
211
215
 
212
216
  def build_default_resolver
@@ -58,6 +58,10 @@ module GraphQL
58
58
  result
59
59
  end
60
60
 
61
+ def get_argument(argument_name)
62
+ arguments[argument_name]
63
+ end
64
+
61
65
  private
62
66
 
63
67
  def coerce_non_null_input(value, ctx)
@@ -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
@@ -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
@@ -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,20 @@ 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
+
1651
1666
  private
1652
1667
 
1653
1668
  def lazy_methods
@@ -1768,16 +1783,7 @@ module GraphQL
1768
1783
  if owner.kind.union?
1769
1784
  # It's a union with possible_types
1770
1785
  # 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
- }
1786
+ owner.assign_type_membership_object_type(type)
1781
1787
  own_possible_types[owner.graphql_name] = owner.possible_types
1782
1788
  elsif type.kind.interface? && owner.kind.object?
1783
1789
  new_interfaces = []
@@ -61,6 +61,11 @@ module GraphQL
61
61
  @from_resolver = from_resolver
62
62
  @method_access = method_access
63
63
 
64
+ if !@null && default_value?
65
+ raise ArgumentError, "Argument '#{@name}' has conflicting params, " \
66
+ "either use `required: false` or remove `default_value:`."
67
+ end
68
+
64
69
  if definition_block
65
70
  if definition_block.arity == 1
66
71
  instance_exec(self, &definition_block)