graphql 1.12.3 → 1.12.8

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +4 -1
  3. data/lib/generators/graphql/loader_generator.rb +1 -0
  4. data/lib/generators/graphql/mutation_generator.rb +1 -0
  5. data/lib/generators/graphql/relay.rb +55 -0
  6. data/lib/generators/graphql/relay_generator.rb +4 -46
  7. data/lib/generators/graphql/type_generator.rb +1 -0
  8. data/lib/graphql.rb +4 -2
  9. data/lib/graphql/backtrace/inspect_result.rb +0 -1
  10. data/lib/graphql/backtrace/table.rb +0 -1
  11. data/lib/graphql/backtrace/traced_error.rb +0 -1
  12. data/lib/graphql/backtrace/tracer.rb +4 -8
  13. data/lib/graphql/dataloader.rb +102 -92
  14. data/lib/graphql/dataloader/null_dataloader.rb +5 -5
  15. data/lib/graphql/dataloader/request.rb +1 -6
  16. data/lib/graphql/dataloader/request_all.rb +1 -4
  17. data/lib/graphql/dataloader/source.rb +20 -6
  18. data/lib/graphql/execution/errors.rb +109 -11
  19. data/lib/graphql/execution/interpreter.rb +2 -2
  20. data/lib/graphql/execution/interpreter/arguments_cache.rb +37 -14
  21. data/lib/graphql/execution/interpreter/resolve.rb +33 -25
  22. data/lib/graphql/execution/interpreter/runtime.rb +41 -78
  23. data/lib/graphql/execution/multiplex.rb +21 -22
  24. data/lib/graphql/introspection.rb +1 -1
  25. data/lib/graphql/introspection/directive_type.rb +7 -3
  26. data/lib/graphql/language.rb +1 -0
  27. data/lib/graphql/language/cache.rb +37 -0
  28. data/lib/graphql/language/parser.rb +15 -5
  29. data/lib/graphql/language/parser.y +15 -5
  30. data/lib/graphql/object_type.rb +0 -2
  31. data/lib/graphql/pagination/active_record_relation_connection.rb +7 -0
  32. data/lib/graphql/pagination/connection.rb +15 -1
  33. data/lib/graphql/pagination/connections.rb +1 -0
  34. data/lib/graphql/pagination/relation_connection.rb +12 -1
  35. data/lib/graphql/parse_error.rb +0 -1
  36. data/lib/graphql/query.rb +9 -5
  37. data/lib/graphql/query/arguments_cache.rb +0 -1
  38. data/lib/graphql/query/context.rb +1 -3
  39. data/lib/graphql/query/executor.rb +0 -1
  40. data/lib/graphql/query/null_context.rb +3 -2
  41. data/lib/graphql/query/validation_pipeline.rb +1 -1
  42. data/lib/graphql/query/variable_validation_error.rb +1 -1
  43. data/lib/graphql/railtie.rb +9 -1
  44. data/lib/graphql/relay/range_add.rb +10 -5
  45. data/lib/graphql/schema.rb +14 -27
  46. data/lib/graphql/schema/argument.rb +61 -0
  47. data/lib/graphql/schema/field.rb +10 -5
  48. data/lib/graphql/schema/field/connection_extension.rb +1 -0
  49. data/lib/graphql/schema/find_inherited_value.rb +3 -1
  50. data/lib/graphql/schema/input_object.rb +6 -2
  51. data/lib/graphql/schema/member/has_arguments.rb +43 -56
  52. data/lib/graphql/schema/member/has_fields.rb +1 -4
  53. data/lib/graphql/schema/member/instrumentation.rb +0 -1
  54. data/lib/graphql/schema/resolver.rb +28 -1
  55. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +3 -1
  56. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +6 -2
  57. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  58. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  59. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  60. data/lib/graphql/subscriptions/broadcast_analyzer.rb +0 -3
  61. data/lib/graphql/subscriptions/event.rb +0 -1
  62. data/lib/graphql/subscriptions/instrumentation.rb +0 -1
  63. data/lib/graphql/subscriptions/serialize.rb +3 -1
  64. data/lib/graphql/tracing/active_support_notifications_tracing.rb +2 -1
  65. data/lib/graphql/types/relay/base_connection.rb +4 -0
  66. data/lib/graphql/types/relay/connection_behaviors.rb +38 -5
  67. data/lib/graphql/types/relay/edge_behaviors.rb +12 -1
  68. data/lib/graphql/version.rb +1 -1
  69. data/readme.md +1 -1
  70. metadata +8 -90
@@ -81,79 +81,66 @@ module GraphQL
81
81
  end
82
82
 
83
83
  # @api private
84
+ # If given a block, it will eventually yield the loaded args to the block.
85
+ #
86
+ # If no block is given, it will immediately dataload (but might return a Lazy).
87
+ #
84
88
  # @param values [Hash<String, Object>]
85
89
  # @param context [GraphQL::Query::Context]
86
- # @return [Hash<Symbol, Object>, Execution::Lazy<Hash>]
87
- def coerce_arguments(parent_object, values, context)
90
+ # @yield [Interpreter::Arguments, Execution::Lazy<Interpeter::Arguments>]
91
+ # @return [Interpreter::Arguments, Execution::Lazy<Interpeter::Arguments>]
92
+ def coerce_arguments(parent_object, values, context, &block)
88
93
  # Cache this hash to avoid re-merging it
89
94
  arg_defns = self.arguments
95
+ total_args_count = arg_defns.size
90
96
 
91
- if arg_defns.empty?
92
- GraphQL::Execution::Interpreter::Arguments::EMPTY
97
+ if total_args_count == 0
98
+ final_args = GraphQL::Execution::Interpreter::Arguments::EMPTY
99
+ if block_given?
100
+ block.call(final_args)
101
+ nil
102
+ else
103
+ final_args
104
+ end
93
105
  else
106
+ finished_args = nil
94
107
  argument_values = {}
95
- arg_lazies = arg_defns.map do |arg_name, arg_defn|
96
- arg_key = arg_defn.keyword
97
- has_value = false
98
- default_used = false
99
- if values.key?(arg_name)
100
- has_value = true
101
- value = values[arg_name]
102
- elsif values.key?(arg_key)
103
- has_value = true
104
- value = values[arg_key]
105
- elsif arg_defn.default_value?
106
- has_value = true
107
- value = arg_defn.default_value
108
- default_used = true
109
- end
110
-
111
- if has_value
112
- loads = arg_defn.loads
113
- loaded_value = nil
114
- coerced_value = context.schema.error_handler.with_error_handling(context) do
115
- arg_defn.type.coerce_input(value, context)
116
- end
117
-
118
- # TODO this should probably be inside after_lazy
119
- if loads && !arg_defn.from_resolver?
120
- loaded_value = if arg_defn.type.list?
121
- loaded_values = coerced_value.map { |val| load_application_object(arg_defn, loads, val, context) }
122
- context.schema.after_any_lazies(loaded_values) { |result| result }
108
+ resolved_args_count = 0
109
+ raised_error = false
110
+ arg_defns.each do |arg_name, arg_defn|
111
+ context.dataloader.append_job do
112
+ begin
113
+ arg_defn.coerce_into_values(parent_object, values, context, argument_values)
114
+ rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
115
+ raised_error = true
116
+ if block_given?
117
+ block.call(err)
123
118
  else
124
- load_application_object(arg_defn, loads, coerced_value, context)
119
+ finished_args = err
125
120
  end
126
121
  end
127
122
 
128
- coerced_value = if loaded_value
129
- loaded_value
130
- else
131
- coerced_value
132
- end
123
+ resolved_args_count += 1
124
+ if resolved_args_count == total_args_count && !raised_error
125
+ finished_args = context.schema.after_any_lazies(argument_values.values) {
126
+ GraphQL::Execution::Interpreter::Arguments.new(
127
+ argument_values: argument_values,
128
+ )
129
+ }
133
130
 
134
- context.schema.after_lazy(coerced_value) do |coerced_value|
135
- validate_directive_argument(arg_defn, coerced_value)
136
- prepared_value = context.schema.error_handler.with_error_handling(context) do
137
- arg_defn.prepare_value(parent_object, coerced_value, context: context)
131
+ if block_given?
132
+ block.call(finished_args)
138
133
  end
139
-
140
- # TODO code smell to access such a deeply-nested constant in a distant module
141
- argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
142
- value: prepared_value,
143
- definition: arg_defn,
144
- default_used: default_used,
145
- )
146
134
  end
147
- else
148
- # has_value is false
149
- validate_directive_argument(arg_defn, nil)
150
135
  end
151
136
  end
152
137
 
153
- context.schema.after_any_lazies(arg_lazies) do
154
- GraphQL::Execution::Interpreter::Arguments.new(
155
- argument_values: argument_values,
156
- )
138
+ if block_given?
139
+ nil
140
+ else
141
+ # This API returns eagerly, gotta run it now
142
+ context.dataloader.run
143
+ finished_args
157
144
  end
158
145
  end
159
146
  end
@@ -74,11 +74,8 @@ module GraphQL
74
74
  @field_class = new_field_class
75
75
  elsif defined?(@field_class) && @field_class
76
76
  @field_class
77
- elsif self.is_a?(Class)
78
- superclass.respond_to?(:field_class) ? superclass.field_class : GraphQL::Schema::Field
79
77
  else
80
- ancestor = ancestors[1..-1].find { |a| a.respond_to?(:field_class) && a.field_class }
81
- ancestor ? ancestor.field_class : GraphQL::Schema::Field
78
+ find_inherited_value(:field_class, GraphQL::Schema::Field)
82
79
  end
83
80
  end
84
81
 
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- # test_via: ../object.rb
3
2
 
4
3
  module GraphQL
5
4
  class Schema
@@ -276,8 +276,29 @@ module GraphQL
276
276
  end
277
277
  end
278
278
 
279
+ # Get or set the `max_page_size:` which will be configured for fields using this resolver
280
+ # (`nil` means "unlimited max page size".)
281
+ # @param max_page_size [Integer, nil] Set a new value
282
+ # @return [Integer, nil] The `max_page_size` assigned to fields that use this resolver
283
+ def max_page_size(new_max_page_size = :not_given)
284
+ if new_max_page_size != :not_given
285
+ @max_page_size = new_max_page_size
286
+ elsif defined?(@max_page_size)
287
+ @max_page_size
288
+ elsif superclass.respond_to?(:max_page_size)
289
+ superclass.max_page_size
290
+ else
291
+ nil
292
+ end
293
+ end
294
+
295
+ # @return [Boolean] `true` if this resolver or a superclass has an assigned `max_page_size`
296
+ def has_max_page_size?
297
+ defined?(@max_page_size) || (superclass.respond_to?(:has_max_page_size?) && superclass.has_max_page_size?)
298
+ end
299
+
279
300
  def field_options
280
- {
301
+ field_opts = {
281
302
  type: type_expr,
282
303
  description: description,
283
304
  extras: extras,
@@ -289,6 +310,12 @@ module GraphQL
289
310
  extensions: extensions,
290
311
  broadcastable: broadcastable?,
291
312
  }
313
+
314
+ if has_max_page_size?
315
+ field_opts[:max_page_size] = max_page_size
316
+ end
317
+
318
+ field_opts
292
319
  end
293
320
 
294
321
  # A non-normalized type configuration, without `null` applied
@@ -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
@@ -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
@@ -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
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- # test_via: ../subscriptions.rb
3
2
  module GraphQL
4
3
  class Subscriptions
5
4
  # This thing can be:
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- # test_via: ../subscriptions.rb
3
2
  module GraphQL
4
3
  class Subscriptions
5
4
  # Wrap the root fields of the subscription type with special logic for:
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- # test_via: ../subscriptions.rb
3
2
  require "set"
4
3
  module GraphQL
5
4
  class Subscriptions
@@ -71,6 +70,9 @@ module GraphQL
71
70
  when OPEN_STRUCT_KEY
72
71
  ostruct_values = load_value(value[OPEN_STRUCT_KEY])
73
72
  OpenStruct.new(ostruct_values)
73
+ else
74
+ key = value.keys.first
75
+ { key => load_value(value[key]) }
74
76
  end
75
77
  else
76
78
  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
 
@@ -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.3"
3
+ VERSION = "1.12.8"
4
4
  end
data/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # graphql <img src="https://cloud.githubusercontent.com/assets/2231765/9094460/cb43861e-3b66-11e5-9fbf-71066ff3ab13.png" height="40" alt="graphql-ruby"/>
2
2
 
3
- [![Build Status](https://travis-ci.org/rmosolgo/graphql-ruby.svg?branch=master)](https://travis-ci.org/rmosolgo/graphql-ruby)
3
+ [![CI Suite](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml/badge.svg)](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml)
4
4
  [![Gem Version](https://badge.fury.io/rb/graphql.svg)](https://rubygems.org/gems/graphql)
5
5
  [![Code Climate](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/gpa.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
6
6
  [![Test Coverage](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/coverage.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)