graphql 1.12.5 → 1.12.10

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +1 -1
  3. data/lib/generators/graphql/templates/graphql_controller.erb +2 -2
  4. data/lib/graphql.rb +13 -11
  5. data/lib/graphql/dataloader.rb +36 -5
  6. data/lib/graphql/execution/errors.rb +109 -11
  7. data/lib/graphql/execution/interpreter/runtime.rb +32 -36
  8. data/lib/graphql/introspection.rb +1 -1
  9. data/lib/graphql/introspection/directive_type.rb +7 -3
  10. data/lib/graphql/language.rb +1 -0
  11. data/lib/graphql/language/cache.rb +37 -0
  12. data/lib/graphql/language/parser.rb +15 -5
  13. data/lib/graphql/language/parser.y +15 -5
  14. data/lib/graphql/pagination/active_record_relation_connection.rb +7 -0
  15. data/lib/graphql/pagination/connection.rb +6 -1
  16. data/lib/graphql/pagination/connections.rb +2 -1
  17. data/lib/graphql/pagination/relation_connection.rb +12 -1
  18. data/lib/graphql/query.rb +1 -3
  19. data/lib/graphql/query/null_context.rb +7 -1
  20. data/lib/graphql/query/validation_pipeline.rb +1 -1
  21. data/lib/graphql/railtie.rb +9 -1
  22. data/lib/graphql/rake_task.rb +3 -0
  23. data/lib/graphql/schema.rb +23 -37
  24. data/lib/graphql/schema/argument.rb +3 -1
  25. data/lib/graphql/schema/field/connection_extension.rb +1 -0
  26. data/lib/graphql/schema/input_object.rb +2 -2
  27. data/lib/graphql/schema/loader.rb +8 -0
  28. data/lib/graphql/schema/member/base_dsl_methods.rb +3 -15
  29. data/lib/graphql/schema/object.rb +19 -5
  30. data/lib/graphql/schema/resolver.rb +24 -22
  31. data/lib/graphql/schema/scalar.rb +3 -1
  32. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +3 -1
  33. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +6 -2
  34. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  35. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  36. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  37. data/lib/graphql/static_validation/rules/fields_will_merge.rb +17 -8
  38. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  39. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  40. data/lib/graphql/static_validation/validator.rb +5 -0
  41. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +4 -3
  42. data/lib/graphql/subscriptions/broadcast_analyzer.rb +0 -3
  43. data/lib/graphql/subscriptions/serialize.rb +11 -1
  44. data/lib/graphql/tracing/active_support_notifications_tracing.rb +2 -1
  45. data/lib/graphql/types/relay/base_connection.rb +4 -0
  46. data/lib/graphql/types/relay/connection_behaviors.rb +38 -5
  47. data/lib/graphql/types/relay/edge_behaviors.rb +12 -1
  48. data/lib/graphql/version.rb +1 -1
  49. metadata +7 -6
@@ -169,6 +169,12 @@ module GraphQL
169
169
  def build_fields(type_defn, fields, type_resolver)
170
170
  loader = self
171
171
  fields.each do |field_hash|
172
+ unwrapped_field_hash = field_hash
173
+ while (of_type = unwrapped_field_hash["ofType"])
174
+ unwrapped_field_hash = of_type
175
+ end
176
+ type_name = unwrapped_field_hash["name"]
177
+
172
178
  type_defn.field(
173
179
  field_hash["name"],
174
180
  type: type_resolver.call(field_hash["type"]),
@@ -176,6 +182,8 @@ module GraphQL
176
182
  deprecation_reason: field_hash["deprecationReason"],
177
183
  null: true,
178
184
  camelize: false,
185
+ connection_extension: nil,
186
+ connection: type_name.end_with?("Connection"),
179
187
  ) do
180
188
  if field_hash["args"].any?
181
189
  loader.build_arguments(self, field_hash["args"], type_resolver)
@@ -113,27 +113,15 @@ module GraphQL
113
113
  end
114
114
 
115
115
  def visible?(context)
116
- if @mutation
117
- @mutation.visible?(context)
118
- else
119
- true
120
- end
116
+ true
121
117
  end
122
118
 
123
119
  def accessible?(context)
124
- if @mutation
125
- @mutation.accessible?(context)
126
- else
127
- true
128
- end
120
+ true
129
121
  end
130
122
 
131
123
  def authorized?(object, context)
132
- if @mutation
133
- @mutation.authorized?(object, context)
134
- else
135
- true
136
- end
124
+ true
137
125
  end
138
126
  end
139
127
  end
@@ -48,12 +48,26 @@ module GraphQL
48
48
  # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
49
49
  # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
50
50
  def authorized_new(object, context)
51
- auth_val = context.query.with_error_handling do
52
- begin
53
- authorized?(object, context)
54
- rescue GraphQL::UnauthorizedError => err
55
- context.schema.unauthorized_object(err)
51
+ trace_payload = { context: context, type: self, object: object, path: context[:current_path] }
52
+
53
+ maybe_lazy_auth_val = context.query.trace("authorized", trace_payload) do
54
+ context.query.with_error_handling do
55
+ begin
56
+ authorized?(object, context)
57
+ rescue GraphQL::UnauthorizedError => err
58
+ context.schema.unauthorized_object(err)
59
+ end
60
+ end
61
+ end
62
+
63
+ auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
64
+ GraphQL::Execution::Lazy.new do
65
+ context.query.trace("authorized_lazy", trace_payload) do
66
+ context.schema.sync_lazy(maybe_lazy_auth_val)
67
+ end
56
68
  end
69
+ else
70
+ maybe_lazy_auth_val
57
71
  end
58
72
 
59
73
  context.schema.after_lazy(auth_val) do |is_authorized|
@@ -333,30 +333,32 @@ module GraphQL
333
333
  arg_defn = super(*args, from_resolver: true, **kwargs)
334
334
  own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
335
335
 
336
- if loads && arg_defn.type.list?
337
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
338
- def load_#{arg_defn.keyword}(values)
339
- argument = @arguments_by_keyword[:#{arg_defn.keyword}]
340
- lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
341
- context.schema.after_lazy(values) do |values2|
342
- GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value, context) })
336
+ if !method_defined?(:"load_#{arg_defn.keyword}")
337
+ if loads && arg_defn.type.list?
338
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
339
+ def load_#{arg_defn.keyword}(values)
340
+ argument = @arguments_by_keyword[:#{arg_defn.keyword}]
341
+ lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
342
+ context.schema.after_lazy(values) do |values2|
343
+ GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value, context) })
344
+ end
343
345
  end
346
+ RUBY
347
+ elsif loads
348
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
349
+ def load_#{arg_defn.keyword}(value)
350
+ argument = @arguments_by_keyword[:#{arg_defn.keyword}]
351
+ lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
352
+ load_application_object(argument, lookup_as_type, value, context)
353
+ end
354
+ RUBY
355
+ else
356
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
357
+ def load_#{arg_defn.keyword}(value)
358
+ value
359
+ end
360
+ RUBY
344
361
  end
345
- RUBY
346
- elsif loads
347
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
348
- def load_#{arg_defn.keyword}(value)
349
- argument = @arguments_by_keyword[:#{arg_defn.keyword}]
350
- lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
351
- load_application_object(argument, lookup_as_type, value, context)
352
- end
353
- RUBY
354
- else
355
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
356
- def load_#{arg_defn.keyword}(value)
357
- value
358
- end
359
- RUBY
360
362
  end
361
363
 
362
364
  arg_defn
@@ -44,7 +44,9 @@ module GraphQL
44
44
  def validate_non_null_input(value, ctx)
45
45
  result = Query::InputValidationResult.new
46
46
  coerced_result = begin
47
- coerce_input(value, ctx)
47
+ ctx.query.with_error_handling do
48
+ coerce_input(value, ctx)
49
+ end
48
50
  rescue GraphQL::CoercionError => err
49
51
  err
50
52
  end
@@ -41,7 +41,9 @@ module GraphQL
41
41
  error_options = {
42
42
  nodes: parent,
43
43
  type: kind_of_node,
44
- argument: node.name
44
+ argument_name: node.name,
45
+ argument: arg_defn,
46
+ value: node.value
45
47
  }
46
48
  if coerce_extensions
47
49
  error_options[:coerce_extensions] = coerce_extensions
@@ -4,13 +4,17 @@ module GraphQL
4
4
  class ArgumentLiteralsAreCompatibleError < StaticValidation::Error
5
5
  attr_reader :type_name
6
6
  attr_reader :argument_name
7
+ attr_reader :argument
8
+ attr_reader :value
7
9
 
8
- def initialize(message, path: nil, nodes: [], type:, argument: nil, extensions: nil, coerce_extensions: nil)
10
+ def initialize(message, path: nil, nodes: [], type:, argument_name: nil, extensions: nil, coerce_extensions: nil, argument: nil, value: nil)
9
11
  super(message, path: path, nodes: nodes)
10
12
  @type_name = type
11
- @argument_name = argument
13
+ @argument_name = argument_name
12
14
  @extensions = extensions
13
15
  @coerce_extensions = coerce_extensions
16
+ @argument = argument
17
+ @value = value
14
18
  end
15
19
 
16
20
  # A hash representation of this Message
@@ -15,7 +15,8 @@ module GraphQL
15
15
  nodes: node,
16
16
  name: error_arg_name,
17
17
  type: kind_of_node,
18
- argument: node.name
18
+ argument_name: node.name,
19
+ parent: parent_defn
19
20
  ))
20
21
  else
21
22
  # Some other weird error
@@ -5,12 +5,14 @@ module GraphQL
5
5
  attr_reader :name
6
6
  attr_reader :type_name
7
7
  attr_reader :argument_name
8
+ attr_reader :parent
8
9
 
9
- def initialize(message, path: nil, nodes: [], name:, type:, argument:)
10
+ def initialize(message, path: nil, nodes: [], name:, type:, argument_name:, parent:)
10
11
  super(message, path: path, nodes: nodes)
11
12
  @name = name
12
13
  @type_name = type
13
- @argument_name = argument
14
+ @argument_name = argument_name
15
+ @parent = parent
14
16
  end
15
17
 
16
18
  # A hash representation of this Message
@@ -4,7 +4,7 @@ module GraphQL
4
4
  module DirectivesAreDefined
5
5
  def initialize(*)
6
6
  super
7
- @directive_names = context.schema.directives.keys
7
+ @directive_names = context.warden.directives.map(&:graphql_name)
8
8
  end
9
9
 
10
10
  def on_directive(node, parent)
@@ -373,17 +373,26 @@ module GraphQL
373
373
  # In this context, `parents` represends the "self scope" of the field,
374
374
  # what types may be found at this point in the query.
375
375
  def mutually_exclusive?(parents1, parents2)
376
- parents1.each do |type1|
377
- parents2.each do |type2|
378
- # If the types we're comparing are both different object types,
379
- # they have to be mutually exclusive.
380
- if type1 != type2 && type1.kind.object? && type2.kind.object?
381
- return true
376
+ if parents1.empty? || parents2.empty?
377
+ false
378
+ elsif parents1.length == parents2.length
379
+ parents1.length.times.any? do |i|
380
+ type1 = parents1[i - 1]
381
+ type2 = parents2[i - 1]
382
+ if type1 == type2
383
+ # If the types we're comparing are the same type,
384
+ # then they aren't mutually exclusive
385
+ false
386
+ else
387
+ # Check if these two scopes have _any_ types in common.
388
+ possible_right_types = context.query.possible_types(type1)
389
+ possible_left_types = context.query.possible_types(type2)
390
+ (possible_right_types & possible_left_types).empty?
382
391
  end
383
392
  end
393
+ else
394
+ true
384
395
  end
385
-
386
- false
387
396
  end
388
397
  end
389
398
  end
@@ -23,7 +23,7 @@ module GraphQL
23
23
  defn = if arg_defn && arg_defn.type.unwrap.kind.input_object?
24
24
  arg_defn.type.unwrap
25
25
  else
26
- context.field_definition
26
+ context.directive_definition || context.field_definition
27
27
  end
28
28
 
29
29
  parent_type = context.warden.get_argument(defn, parent_name(parent, defn))
@@ -29,8 +29,8 @@ module GraphQL
29
29
  context.directive_definition.arguments
30
30
  when GraphQL::Language::Nodes::InputObject
31
31
  arg_type = context.argument_definition.type.unwrap
32
- if arg_type.is_a?(GraphQL::InputObjectType)
33
- arguments = arg_type.input_fields
32
+ if arg_type.kind.input_object?
33
+ arguments = arg_type.arguments
34
34
  else
35
35
  # This is some kind of error
36
36
  nil
@@ -73,6 +73,11 @@ module GraphQL
73
73
  irep: irep,
74
74
  }
75
75
  end
76
+ rescue GraphQL::ExecutionError => e
77
+ {
78
+ errors: [e],
79
+ irep: nil,
80
+ }
76
81
  end
77
82
 
78
83
  # Invoked when static validation times out.
@@ -34,12 +34,12 @@ module GraphQL
34
34
  # channel: self,
35
35
  # }
36
36
  #
37
- # result = MySchema.execute({
37
+ # result = MySchema.execute(
38
38
  # query: query,
39
39
  # context: context,
40
40
  # variables: variables,
41
41
  # operation_name: operation_name
42
- # })
42
+ # )
43
43
  #
44
44
  # payload = {
45
45
  # result: result.to_h,
@@ -146,14 +146,15 @@ module GraphQL
146
146
  def setup_stream(channel, initial_event)
147
147
  topic = initial_event.topic
148
148
  channel.stream_from(stream_event_name(initial_event), coder: @action_cable_coder) do |message|
149
- object = @serializer.load(message)
150
149
  events_by_fingerprint = @events[topic]
150
+ object = nil
151
151
  events_by_fingerprint.each do |_fingerprint, events|
152
152
  if events.any? && events.first == initial_event
153
153
  # The fingerprint has told us that this response should be shared by all subscribers,
154
154
  # so just run it once, then deliver the result to every subscriber
155
155
  first_event = events.first
156
156
  first_subscription_id = first_event.context.fetch(:subscription_id)
157
+ object ||= @serializer.load(message)
157
158
  result = execute_update(first_subscription_id, first_event, object)
158
159
  # Having calculated the result _once_, send the same payload to all subscribers
159
160
  events.each do |event|
@@ -35,9 +35,6 @@ module GraphQL
35
35
  pt = @query.possible_types(current_type)
36
36
  pt.each do |object_type|
37
37
  ot_field = @query.get_field(object_type, current_field.graphql_name)
38
- if !ot_field
39
- binding.pry
40
- end
41
38
  # Inherited fields would be exactly the same object;
42
39
  # only check fields that are overrides of the inherited one
43
40
  if ot_field && ot_field != current_field
@@ -55,7 +55,14 @@ module GraphQL
55
55
  # @return [Object] An object that load Global::Identification recursive
56
56
  def load_value(value)
57
57
  if value.is_a?(Array)
58
- value.map{|item| load_value(item)}
58
+ is_gids = (v1 = value[0]).is_a?(Hash) && v1.size == 1 && v1[GLOBALID_KEY]
59
+ if is_gids
60
+ # Assume it's an array of global IDs
61
+ ids = value.map { |v| v[GLOBALID_KEY] }
62
+ GlobalID::Locator.locate_many(ids)
63
+ else
64
+ value.map { |item| load_value(item) }
65
+ end
59
66
  elsif value.is_a?(Hash)
60
67
  if value.size == 1
61
68
  case value.keys.first # there's only 1 key
@@ -70,6 +77,9 @@ module GraphQL
70
77
  when OPEN_STRUCT_KEY
71
78
  ostruct_values = load_value(value[OPEN_STRUCT_KEY])
72
79
  OpenStruct.new(ostruct_values)
80
+ else
81
+ key = value.keys.first
82
+ { key => load_value(value[key]) }
73
83
  end
74
84
  else
75
85
  loaded_h = {}
@@ -3,8 +3,9 @@
3
3
  module GraphQL
4
4
  module Tracing
5
5
  # This implementation forwards events to ActiveSupport::Notifications
6
- # with a `graphql.` prefix.
6
+ # with a `graphql` suffix.
7
7
  #
8
+ # @see KEYS for event names
8
9
  module ActiveSupportNotificationsTracing
9
10
  # A cache of frequently-used keys to avoid needless string allocations
10
11
  KEYS = {
@@ -24,6 +24,10 @@ module GraphQL
24
24
  # end
25
25
  # class Types::PostConnection < Types::BaseConnection
26
26
  # edge_type(Types::PostEdge)
27
+ # edges_nullable(true)
28
+ # edge_nullable(true)
29
+ # node_nullable(true)
30
+ # has_nodes_field(true)
27
31
  # end
28
32
  #
29
33
  # @see Relay::BaseEdge for edge types
@@ -11,7 +11,10 @@ module GraphQL
11
11
  child_class.extend(ClassMethods)
12
12
  child_class.extend(Relay::DefaultRelay)
13
13
  child_class.default_relay(true)
14
+ child_class.has_nodes_field(true)
14
15
  child_class.node_nullable(true)
16
+ child_class.edges_nullable(true)
17
+ child_class.edge_nullable(true)
15
18
  add_page_info_field(child_class)
16
19
  end
17
20
 
@@ -32,7 +35,7 @@ module GraphQL
32
35
  # It's called when you subclass this base connection, trying to use the
33
36
  # class name to set defaults. You can call it again in the class definition
34
37
  # to override the default (or provide a value, if the default lookup failed).
35
- def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field: true, node_nullable: self.node_nullable)
38
+ def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field: self.has_nodes_field, node_nullable: self.node_nullable, edges_nullable: self.edges_nullable, edge_nullable: self.edge_nullable)
36
39
  # Set this connection's graphql name
37
40
  node_type_name = node_type.graphql_name
38
41
 
@@ -40,8 +43,8 @@ module GraphQL
40
43
  @edge_type = edge_type_class
41
44
  @edge_class = edge_class
42
45
 
43
- field :edges, [edge_type_class, null: true],
44
- null: true,
46
+ field :edges, [edge_type_class, null: edge_nullable],
47
+ null: edges_nullable,
45
48
  description: "A list of edges.",
46
49
  legacy_edge_class: edge_class, # This is used by the old runtime only, for EdgesInstrumentation
47
50
  connection: false
@@ -77,9 +80,39 @@ module GraphQL
77
80
  # Use `node_nullable(false)` in your base class to make non-null `node` and `nodes` fields.
78
81
  def node_nullable(new_value = nil)
79
82
  if new_value.nil?
80
- @node_nullable || superclass.node_nullable
83
+ defined?(@node_nullable) ? @node_nullable : superclass.node_nullable
81
84
  else
82
- @node_nullable ||= new_value
85
+ @node_nullable = new_value
86
+ end
87
+ end
88
+
89
+ # Set the default `edges_nullable` for this class and its child classes. (Defaults to `true`.)
90
+ # Use `edges_nullable(false)` in your base class to make non-null `edges` fields.
91
+ def edges_nullable(new_value = nil)
92
+ if new_value.nil?
93
+ defined?(@edges_nullable) ? @edges_nullable : superclass.edges_nullable
94
+ else
95
+ @edges_nullable = new_value
96
+ end
97
+ end
98
+
99
+ # Set the default `edge_nullable` for this class and its child classes. (Defaults to `true`.)
100
+ # Use `edge_nullable(false)` in your base class to make non-null `edge` fields.
101
+ def edge_nullable(new_value = nil)
102
+ if new_value.nil?
103
+ defined?(@edge_nullable) ? @edge_nullable : superclass.edge_nullable
104
+ else
105
+ @edge_nullable = new_value
106
+ end
107
+ end
108
+
109
+ # Set the default `nodes_field` for this class and its child classes. (Defaults to `true`.)
110
+ # Use `nodes_field(false)` in your base class to prevent adding of a nodes field.
111
+ def has_nodes_field(new_value = nil)
112
+ if new_value.nil?
113
+ defined?(@nodes_field) ? @nodes_field : superclass.has_nodes_field
114
+ else
115
+ @nodes_field = new_value
83
116
  end
84
117
  end
85
118