graphql 1.10.5 → 1.10.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ace6be63c92b2a2139381664968aef4eeed01a5b1e3936af31ef3b0d7735b944
4
- data.tar.gz: 6d572562697f474dc82f81e3c28e9d234bf9348ef2446db41a10fa4b6d52cec4
3
+ metadata.gz: 1b006fe34754dbc2e3ee7ef459e05bdf325755538e013efd96c9eef3990af9a7
4
+ data.tar.gz: b655b349e59ea3a34d6ed71fb3870af1cc91afc75bf7147cb0d9b436b78a97c8
5
5
  SHA512:
6
- metadata.gz: 4da367c261c3fd79192aea827be4b0cebc864820d4dba6ed74a3c57d9c7772511bf6b96bdd4316e9186bd751954a964b09bc1e455fed4f687edb958d869f25e5
7
- data.tar.gz: a5d4c7c349cebf6ce1e064763c47a61e6018a29d4b563ce833390ed5d378f5dd977b884f2a1809a7935e43902a9e33e23682662192cc3ff5ab8079c75c1b5fb6
6
+ metadata.gz: 9a07c35f18dfdb53924fc9ff8466c3255c7235f855d5ea7380738e4cde90c47c6cd12aaa505a27ee1c3ec3bee84a8a82ed9ec113cbbd362ef152a8b1ec6cdf38
7
+ data.tar.gz: 23e61762fd23870cb651334aa84a9e83bb9dda486b114ea8bb2b937eeb77705bac200255b321983caf4f81d6efd0b231a992b618563b86d6446b68585ec429bc
@@ -87,6 +87,11 @@ module Graphql
87
87
  default: false,
88
88
  desc: "Include GraphQL::Batch installation"
89
89
 
90
+ class_option :playground,
91
+ type: :boolean,
92
+ default: false,
93
+ desc: "Use GraphQL Playground over Graphiql as IDE"
94
+
90
95
  # These two options are taken from Rails' own generators'
91
96
  class_option :api,
92
97
  type: :boolean,
@@ -140,6 +145,28 @@ RUBY
140
145
  end
141
146
  end
142
147
 
148
+ if options[:playground]
149
+ gem("graphql_playground-rails", group: :development)
150
+
151
+ log :route, 'graphql_playground-rails'
152
+ shell.mute do
153
+ if Rails::VERSION::STRING > "5.2"
154
+ route <<-RUBY
155
+ if Rails.env.development?
156
+ mount GraphqlPlayground::Rails::Engine, at: "/playground", graphql_path: "/graphql"
157
+ end
158
+ RUBY
159
+ else
160
+ route <<-RUBY
161
+ if Rails.env.development?
162
+ mount GraphqlPlayground::Rails::Engine, at: "/playground", graphql_path: "/graphql"
163
+ end
164
+ RUBY
165
+ end
166
+ end
167
+
168
+ end
169
+
143
170
  if gemfile_modified?
144
171
  say "Gemfile has been modified, make sure you `bundle install`"
145
172
  end
@@ -43,6 +43,6 @@ class GraphqlController < ApplicationController
43
43
  logger.error e.message
44
44
  logger.error e.backtrace.join("\n")
45
45
 
46
- render json: { error: { message: e.message, backtrace: e.backtrace }, data: {} }, status: 500
46
+ render json: { errors: [{ message: e.message, backtrace: e.backtrace }], data: {} }, status: 500
47
47
  end
48
48
  end
@@ -92,6 +92,19 @@ module GraphQL
92
92
  Interpreter::Resolve.resolve_all(final_values)
93
93
  end
94
94
  end
95
+
96
+ class ListResultFailedError < GraphQL::Error
97
+ def initialize(value:, path:, field:)
98
+ message = "Failed to build a GraphQL list result for field `#{field.path}` at path `#{path.join(".")}`.\n".dup
99
+
100
+ message << "Expected `#{value.inspect}` to implement `.each` to satisfy the GraphQL return type `#{field.type.to_type_signature}`.\n"
101
+
102
+ if field.connection?
103
+ message << "\nThis field was treated as a Relay-style connection; add `connection: false` to the `field(...)` to disable this behavior."
104
+ end
105
+ super(message)
106
+ end
107
+ end
95
108
  end
96
109
  end
97
110
  end
@@ -12,7 +12,16 @@ module GraphQL
12
12
  # First, normalize all AST or Ruby values to a plain Ruby hash
13
13
  args_hash = prepare_args_hash(ast_node)
14
14
  # Then call into the schema to coerce those incoming values
15
- arg_owner.coerce_arguments(parent_object, args_hash, query.context)
15
+ args = arg_owner.coerce_arguments(parent_object, args_hash, query.context)
16
+
17
+ h3[parent_object] = if args.is_a?(GraphQL::Execution::Lazy)
18
+ args.then { |resolved_args|
19
+ # when this promise is resolved, update the cache with the resolved value
20
+ h3[parent_object] = resolved_args
21
+ }
22
+ else
23
+ args
24
+ end
16
25
  end
17
26
  end
18
27
  end
@@ -174,69 +174,71 @@ module GraphQL
174
174
  next
175
175
  end
176
176
 
177
- # It might turn out that making arguments for every field is slow.
178
- # If we have to cache them, we'll need a more subtle approach here.
179
- field_defn.extras.each do |extra|
180
- case extra
181
- when :ast_node
182
- kwarg_arguments[:ast_node] = ast_node
183
- when :execution_errors
184
- kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
185
- when :path
186
- kwarg_arguments[:path] = next_path
187
- when :lookahead
188
- if !field_ast_nodes
189
- field_ast_nodes = [ast_node]
177
+ after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |kwarg_arguments|
178
+ # It might turn out that making arguments for every field is slow.
179
+ # If we have to cache them, we'll need a more subtle approach here.
180
+ field_defn.extras.each do |extra|
181
+ case extra
182
+ when :ast_node
183
+ kwarg_arguments[:ast_node] = ast_node
184
+ when :execution_errors
185
+ kwarg_arguments[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
186
+ when :path
187
+ kwarg_arguments[:path] = next_path
188
+ when :lookahead
189
+ if !field_ast_nodes
190
+ field_ast_nodes = [ast_node]
191
+ end
192
+ kwarg_arguments[:lookahead] = Execution::Lookahead.new(
193
+ query: query,
194
+ ast_nodes: field_ast_nodes,
195
+ field: field_defn,
196
+ )
197
+ else
198
+ kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
190
199
  end
191
- kwarg_arguments[:lookahead] = Execution::Lookahead.new(
192
- query: query,
193
- ast_nodes: field_ast_nodes,
194
- field: field_defn,
195
- )
196
- else
197
- kwarg_arguments[extra] = field_defn.fetch_extra(extra, context)
198
200
  end
199
- end
200
201
 
201
- @interpreter_context[:current_arguments] = kwarg_arguments
202
+ @interpreter_context[:current_arguments] = kwarg_arguments
202
203
 
203
- # Optimize for the case that field is selected only once
204
- if field_ast_nodes.nil? || field_ast_nodes.size == 1
205
- next_selections = ast_node.selections
206
- else
207
- next_selections = []
208
- field_ast_nodes.each { |f| next_selections.concat(f.selections) }
209
- end
204
+ # Optimize for the case that field is selected only once
205
+ if field_ast_nodes.nil? || field_ast_nodes.size == 1
206
+ next_selections = ast_node.selections
207
+ else
208
+ next_selections = []
209
+ field_ast_nodes.each { |f| next_selections.concat(f.selections) }
210
+ end
210
211
 
211
- field_result = resolve_with_directives(object, ast_node) do
212
- # Actually call the field resolver and capture the result
213
- app_result = begin
214
- query.with_error_handling do
215
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
216
- field_defn.resolve(object, kwarg_arguments, context)
212
+ field_result = resolve_with_directives(object, ast_node) do
213
+ # Actually call the field resolver and capture the result
214
+ app_result = begin
215
+ query.with_error_handling do
216
+ query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
217
+ field_defn.resolve(object, kwarg_arguments, context)
218
+ end
217
219
  end
220
+ rescue GraphQL::ExecutionError => err
221
+ err
218
222
  end
219
- rescue GraphQL::ExecutionError => err
220
- err
221
- end
222
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
223
- continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
224
- if RawValue === continue_value
225
- # Write raw value directly to the response without resolving nested objects
226
- write_in_response(next_path, continue_value.resolve)
227
- elsif HALT != continue_value
228
- continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
223
+ after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
224
+ continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
225
+ if RawValue === continue_value
226
+ # Write raw value directly to the response without resolving nested objects
227
+ write_in_response(next_path, continue_value.resolve)
228
+ elsif HALT != continue_value
229
+ continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
230
+ end
229
231
  end
230
232
  end
231
- end
232
233
 
233
- # If this field is a root mutation field, immediately resolve
234
- # all of its child fields before moving on to the next root mutation field.
235
- # (Subselections of this mutation will still be resolved level-by-level.)
236
- if root_operation_type == "mutation"
237
- Interpreter::Resolve.resolve_all([field_result])
238
- else
239
- field_result
234
+ # If this field is a root mutation field, immediately resolve
235
+ # all of its child fields before moving on to the next root mutation field.
236
+ # (Subselections of this mutation will still be resolved level-by-level.)
237
+ if root_operation_type == "mutation"
238
+ Interpreter::Resolve.resolve_all([field_result])
239
+ else
240
+ field_result
241
+ end
240
242
  end
241
243
  end
242
244
  end
@@ -330,20 +332,26 @@ module GraphQL
330
332
  inner_type = type.of_type
331
333
  idx = 0
332
334
  scoped_context = context.scoped_context
333
- value.each do |inner_value|
334
- next_path = path.dup
335
- next_path << idx
336
- next_path.freeze
337
- idx += 1
338
- set_type_at_path(next_path, inner_type)
339
- # This will update `response_list` with the lazy
340
- after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
341
- continue_value = continue_value(next_path, inner_inner_value, field, inner_type.non_null?, ast_node)
342
- if HALT != continue_value
343
- continue_field(next_path, continue_value, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
335
+ begin
336
+ value.each do |inner_value|
337
+ next_path = path.dup
338
+ next_path << idx
339
+ next_path.freeze
340
+ idx += 1
341
+ set_type_at_path(next_path, inner_type)
342
+ # This will update `response_list` with the lazy
343
+ after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
344
+ continue_value = continue_value(next_path, inner_inner_value, field, inner_type.non_null?, ast_node)
345
+ if HALT != continue_value
346
+ continue_field(next_path, continue_value, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
347
+ end
344
348
  end
345
349
  end
350
+ rescue NoMethodError
351
+ # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
352
+ raise ListResultFailedError.new(value: value, field: field, path: path)
346
353
  end
354
+
347
355
  response_list
348
356
  when "NON_NULL"
349
357
  inner_type = type.of_type
@@ -5,6 +5,7 @@ module GraphQL
5
5
  accepts_definitions :fields, :orphan_types, :resolve_type, field: GraphQL::Define::AssignObjectField
6
6
 
7
7
  attr_accessor :fields, :orphan_types, :resolve_type_proc
8
+ attr_writer :type_membership_class
8
9
  ensure_defined :fields, :orphan_types, :resolve_type_proc, :resolve_type
9
10
 
10
11
  def initialize
@@ -61,5 +62,9 @@ module GraphQL
61
62
  type_name = type.is_a?(String) ? type : type.graphql_name
62
63
  !get_possible_type(type_name, ctx).nil?
63
64
  end
65
+
66
+ def type_membership_class
67
+ @type_membership_class || GraphQL::Schema::TypeMembership
68
+ end
64
69
  end
65
70
  end
@@ -39,11 +39,11 @@ module GraphQL
39
39
 
40
40
  # Value equality
41
41
  # @return [Boolean] True if `self` is equivalent to `other`
42
- def eql?(other)
42
+ def ==(other)
43
43
  return true if equal?(other)
44
- other.is_a?(self.class) &&
45
- other.scalars.eql?(self.scalars) &&
46
- other.children.eql?(self.children)
44
+ other.kind_of?(self.class) &&
45
+ other.scalars == self.scalars &&
46
+ other.children == self.children
47
47
  end
48
48
 
49
49
  NO_CHILDREN = [].freeze
@@ -17,17 +17,15 @@ module GraphQL
17
17
  def initialize
18
18
  super
19
19
  @fields = {}
20
- @interface_fields = {}
21
- @dirty_interfaces = []
22
- @dirty_inherited_interfaces = []
20
+ @clean_inherited_fields = nil
21
+ @structural_interface_type_memberships = []
22
+ @inherited_interface_type_memberships = []
23
23
  end
24
24
 
25
25
  def initialize_copy(other)
26
26
  super
27
- @clean_interfaces = nil
28
- @clean_inherited_interfaces = nil
29
- @dirty_interfaces = other.dirty_interfaces.dup
30
- @dirty_inherited_interfaces = other.dirty_inherited_interfaces.dup
27
+ @structural_interface_type_memberships = other.structural_interface_type_memberships.dup
28
+ @inherited_interface_type_memberships = other.inherited_interface_type_memberships.dup
31
29
  @fields = other.fields.dup
32
30
  end
33
31
 
@@ -35,18 +33,27 @@ module GraphQL
35
33
  # @param new_interfaces [Array<GraphQL::Interface>] interfaces that this type implements
36
34
  # @deprecated Use `implements` instead of `interfaces`.
37
35
  def interfaces=(new_interfaces)
38
- @clean_interfaces = nil
39
- @clean_inherited_interfaces = nil
36
+ @structural_interface_type_memberships = []
37
+ @inherited_interface_type_memberships = []
40
38
  @clean_inherited_fields = nil
41
-
42
- @dirty_inherited_interfaces = []
43
- @dirty_inherited_fields = {}
44
39
  implements(new_interfaces, inherit: true)
45
40
  end
46
41
 
47
- def interfaces
48
- load_interfaces
49
- @clean_interfaces
42
+ def interfaces(ctx = GraphQL::Query::NullContext)
43
+ ensure_defined
44
+ visible_ifaces = []
45
+ unfiltered = ctx == GraphQL::Query::NullContext
46
+ [@structural_interface_type_memberships, @inherited_interface_type_memberships].each do |tms|
47
+ tms.each do |type_membership|
48
+ if unfiltered || type_membership.visible?(ctx)
49
+ # if this is derived from a class-based object, we have to
50
+ # get the `.graphql_definition` of the attached interface.
51
+ visible_ifaces << GraphQL::BaseType.resolve_related_type(type_membership.abstract_type)
52
+ end
53
+ end
54
+ end
55
+
56
+ visible_ifaces
50
57
  end
51
58
 
52
59
  def kind
@@ -71,24 +78,30 @@ module GraphQL
71
78
  # This declaration will be validated when the schema is defined.
72
79
  # @param interfaces [Array<GraphQL::Interface>] add a new interface that this type implements
73
80
  # @param inherits [Boolean] If true, copy the interfaces' field definitions to this type
74
- def implements(interfaces, inherit: false)
81
+ def implements(interfaces, inherit: false, **options)
75
82
  if !interfaces.is_a?(Array)
76
83
  raise ArgumentError, "`implements(interfaces)` must be an array, not #{interfaces.class} (#{interfaces})"
77
84
  end
78
-
79
- @clean_interfaces = nil
80
85
  @clean_inherited_fields = nil
81
- dirty_ifaces = inherit ? @dirty_inherited_interfaces : @dirty_interfaces
82
- dirty_ifaces.concat(interfaces)
86
+
87
+ type_memberships = inherit ? @inherited_interface_type_memberships : @structural_interface_type_memberships
88
+ interfaces.each do |iface|
89
+ iface = BaseType.resolve_related_type(iface)
90
+ if iface.is_a?(GraphQL::InterfaceType)
91
+ type_memberships << iface.type_membership_class.new(iface, self, options)
92
+ end
93
+ end
83
94
  end
84
95
 
85
96
  def resolve_type_proc
86
97
  nil
87
98
  end
88
99
 
100
+ attr_writer :structural_interface_type_memberships
101
+
89
102
  protected
90
103
 
91
- attr_reader :dirty_interfaces, :dirty_inherited_interfaces
104
+ attr_reader :structural_interface_type_memberships, :inherited_interface_type_memberships
92
105
 
93
106
  private
94
107
 
@@ -97,24 +110,20 @@ module GraphQL
97
110
  end
98
111
 
99
112
  def interface_fields
100
- load_interfaces
101
- @clean_inherited_fields
102
- end
103
-
104
- def load_interfaces
105
- @clean_interfaces ||= begin
113
+ if @clean_inherited_fields
114
+ @clean_inherited_fields
115
+ else
106
116
  ensure_defined
107
- clean_ifaces = normalize_interfaces(@dirty_interfaces)
108
- clean_inherited_ifaces = normalize_interfaces(@dirty_inherited_interfaces)
109
- inherited_fields = {}
110
- clean_inherited_ifaces.each do |iface|
111
- # This will be found later in schema validation:
117
+ @clean_inherited_fields = {}
118
+ @inherited_interface_type_memberships.each do |type_membership|
119
+ iface = GraphQL::BaseType.resolve_related_type(type_membership.abstract_type)
112
120
  if iface.is_a?(GraphQL::InterfaceType)
113
- inherited_fields.merge!(iface.fields)
121
+ @clean_inherited_fields.merge!(iface.fields)
122
+ else
123
+ pp iface
114
124
  end
115
125
  end
116
- @clean_inherited_fields = inherited_fields
117
- clean_inherited_ifaces + clean_ifaces
126
+ @clean_inherited_fields
118
127
  end
119
128
  end
120
129
  end
@@ -54,19 +54,39 @@ module GraphQL
54
54
  # @param last [Integer, nil] Limit parameter from the client, if provided
55
55
  # @param before [String, nil] A cursor for pagination, if the client provided one.
56
56
  # @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: nil, last: nil, before: nil)
57
+ def initialize(items, context: nil, first: nil, after: nil, max_page_size: :not_given, last: nil, before: nil)
58
58
  @items = items
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
- @max_page_size = max_page_size
64
+
65
+ # This is only true if the object was _initialized_ with an override
66
+ # or if one is assigned later.
67
+ @has_max_page_size_override = max_page_size != :not_given
68
+ @max_page_size = if max_page_size == :not_given
69
+ nil
70
+ else
71
+ max_page_size
72
+ end
73
+ end
74
+
75
+ def max_page_size=(new_value)
76
+ @has_max_page_size_override = true
77
+ @max_page_size = new_value
65
78
  end
66
79
 
67
- attr_writer :max_page_size
68
80
  def max_page_size
69
- @max_page_size ||= context.schema.default_max_page_size
81
+ if @has_max_page_size_override
82
+ @max_page_size
83
+ else
84
+ context.schema.default_max_page_size
85
+ end
86
+ end
87
+
88
+ def has_max_page_size_override?
89
+ @has_max_page_size_override
70
90
  end
71
91
 
72
92
  attr_writer :first