graphql 1.12.6 → 1.12.11

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/install_generator.rb +1 -1
  3. data/lib/generators/graphql/templates/graphql_controller.erb +2 -2
  4. data/lib/graphql.rb +10 -10
  5. data/lib/graphql/backtrace/table.rb +14 -2
  6. data/lib/graphql/dataloader.rb +44 -15
  7. data/lib/graphql/execution/errors.rb +109 -11
  8. data/lib/graphql/execution/execute.rb +1 -1
  9. data/lib/graphql/execution/interpreter.rb +4 -8
  10. data/lib/graphql/execution/interpreter/runtime.rb +207 -188
  11. data/lib/graphql/introspection.rb +1 -1
  12. data/lib/graphql/introspection/directive_type.rb +7 -3
  13. data/lib/graphql/introspection/schema_type.rb +1 -1
  14. data/lib/graphql/pagination/active_record_relation_connection.rb +7 -0
  15. data/lib/graphql/pagination/connections.rb +1 -1
  16. data/lib/graphql/pagination/relation_connection.rb +8 -1
  17. data/lib/graphql/query.rb +1 -3
  18. data/lib/graphql/query/null_context.rb +7 -1
  19. data/lib/graphql/query/validation_pipeline.rb +1 -1
  20. data/lib/graphql/rake_task.rb +3 -0
  21. data/lib/graphql/schema.rb +49 -237
  22. data/lib/graphql/schema/addition.rb +238 -0
  23. data/lib/graphql/schema/argument.rb +55 -36
  24. data/lib/graphql/schema/directive/transform.rb +13 -1
  25. data/lib/graphql/schema/input_object.rb +2 -2
  26. data/lib/graphql/schema/loader.rb +8 -0
  27. data/lib/graphql/schema/member/base_dsl_methods.rb +3 -15
  28. data/lib/graphql/schema/object.rb +19 -5
  29. data/lib/graphql/schema/resolver.rb +46 -24
  30. data/lib/graphql/schema/scalar.rb +3 -1
  31. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +3 -1
  32. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +6 -2
  33. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  34. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  35. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  36. data/lib/graphql/static_validation/rules/fields_will_merge.rb +17 -8
  37. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  38. data/lib/graphql/static_validation/validator.rb +5 -0
  39. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +4 -3
  40. data/lib/graphql/subscriptions/broadcast_analyzer.rb +0 -3
  41. data/lib/graphql/subscriptions/serialize.rb +11 -1
  42. data/lib/graphql/types/relay/base_connection.rb +4 -0
  43. data/lib/graphql/types/relay/connection_behaviors.rb +21 -10
  44. data/lib/graphql/types/relay/edge_behaviors.rb +12 -1
  45. data/lib/graphql/version.rb +1 -1
  46. metadata +3 -3
  47. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
@@ -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
@@ -372,16 +374,36 @@ module GraphQL
372
374
  # @param extension [Class] Extension class
373
375
  # @param options [Hash] Optional extension options
374
376
  def extension(extension, **options)
375
- extensions << {extension => options}
377
+ @own_extensions ||= []
378
+ @own_extensions << {extension => options}
376
379
  end
377
380
 
378
381
  # @api private
379
382
  def extensions
380
- @extensions ||= []
383
+ own_exts = @own_extensions
384
+ # Jump through some hoops to avoid creating arrays when we don't actually need them
385
+ if superclass.respond_to?(:extensions)
386
+ s_exts = superclass.extensions
387
+ if own_exts
388
+ if s_exts.any?
389
+ own_exts + s_exts
390
+ else
391
+ own_exts
392
+ end
393
+ else
394
+ s_exts
395
+ end
396
+ else
397
+ own_exts || EMPTY_ARRAY
398
+ end
381
399
  end
382
400
 
383
401
  private
384
402
 
403
+ def own_extensions
404
+ @own_extensions
405
+ end
406
+
385
407
  def own_arguments_loads_as_type
386
408
  @own_arguments_loads_as_type ||= {}
387
409
  end
@@ -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))
@@ -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 = {}
@@ -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,6 +11,7 @@ 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)
15
16
  child_class.edges_nullable(true)
16
17
  child_class.edge_nullable(true)
@@ -34,7 +35,7 @@ module GraphQL
34
35
  # It's called when you subclass this base connection, trying to use the
35
36
  # class name to set defaults. You can call it again in the class definition
36
37
  # to override the default (or provide a value, if the default lookup failed).
37
- 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, edges_nullable: self.edges_nullable, edge_nullable: self.edge_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)
38
39
  # Set this connection's graphql name
39
40
  node_type_name = node_type.graphql_name
40
41
 
@@ -79,9 +80,9 @@ module GraphQL
79
80
  # Use `node_nullable(false)` in your base class to make non-null `node` and `nodes` fields.
80
81
  def node_nullable(new_value = nil)
81
82
  if new_value.nil?
82
- @node_nullable || superclass.node_nullable
83
+ defined?(@node_nullable) ? @node_nullable : superclass.node_nullable
83
84
  else
84
- @node_nullable ||= new_value
85
+ @node_nullable = new_value
85
86
  end
86
87
  end
87
88
 
@@ -89,21 +90,31 @@ module GraphQL
89
90
  # Use `edges_nullable(false)` in your base class to make non-null `edges` fields.
90
91
  def edges_nullable(new_value = nil)
91
92
  if new_value.nil?
92
- @edges_nullable || superclass.edges_nullable
93
+ defined?(@edges_nullable) ? @edges_nullable : superclass.edges_nullable
93
94
  else
94
- @edges_nullable ||= new_value
95
+ @edges_nullable = new_value
95
96
  end
96
- end
97
-
97
+ end
98
+
98
99
  # Set the default `edge_nullable` for this class and its child classes. (Defaults to `true`.)
99
100
  # Use `edge_nullable(false)` in your base class to make non-null `edge` fields.
100
101
  def edge_nullable(new_value = nil)
101
102
  if new_value.nil?
102
- @edge_nullable || superclass.edge_nullable
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
103
114
  else
104
- @edge_nullable ||= new_value
115
+ @nodes_field = new_value
105
116
  end
106
- end
117
+ end
107
118
 
108
119
  private
109
120
 
@@ -8,6 +8,7 @@ module GraphQL
8
8
  child_class.description("An edge in a connection.")
9
9
  child_class.field(:cursor, String, null: false, description: "A cursor for use in pagination.")
10
10
  child_class.extend(ClassMethods)
11
+ child_class.node_nullable(true)
11
12
  end
12
13
 
13
14
  module ClassMethods
@@ -15,7 +16,7 @@ module GraphQL
15
16
  #
16
17
  # @param node_type [Class] A `Schema::Object` subclass
17
18
  # @param null [Boolean]
18
- def node_type(node_type = nil, null: true)
19
+ def node_type(node_type = nil, null: self.node_nullable)
19
20
  if node_type
20
21
  @node_type = node_type
21
22
  # Add a default `node` field
@@ -35,6 +36,16 @@ module GraphQL
35
36
  def visible?(ctx)
36
37
  node_type.visible?(ctx)
37
38
  end
39
+
40
+ # Set the default `node_nullable` for this class and its child classes. (Defaults to `true`.)
41
+ # Use `node_nullable(false)` in your base class to make non-null `node` field.
42
+ def node_nullable(new_value = nil)
43
+ if new_value.nil?
44
+ defined?(@node_nullable) ? @node_nullable : superclass.node_nullable
45
+ else
46
+ @node_nullable = new_value
47
+ end
48
+ end
38
49
  end
39
50
  end
40
51
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.12.6"
3
+ VERSION = "1.12.11"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.6
4
+ version: 1.12.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-11 00:00:00.000000000 Z
11
+ date: 2021-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -369,7 +369,6 @@ files:
369
369
  - lib/graphql/execution/interpreter/arguments_cache.rb
370
370
  - lib/graphql/execution/interpreter/execution_errors.rb
371
371
  - lib/graphql/execution/interpreter/handles_raw_value.rb
372
- - lib/graphql/execution/interpreter/hash_response.rb
373
372
  - lib/graphql/execution/interpreter/resolve.rb
374
373
  - lib/graphql/execution/interpreter/runtime.rb
375
374
  - lib/graphql/execution/lazy.rb
@@ -485,6 +484,7 @@ files:
485
484
  - lib/graphql/runtime_type_error.rb
486
485
  - lib/graphql/scalar_type.rb
487
486
  - lib/graphql/schema.rb
487
+ - lib/graphql/schema/addition.rb
488
488
  - lib/graphql/schema/argument.rb
489
489
  - lib/graphql/schema/base_64_bp.rb
490
490
  - lib/graphql/schema/base_64_encoder.rb