graphql 1.10.5 → 1.10.6

Sign up to get free protection for your applications and to get access to all the features.
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