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 +4 -4
- data/lib/generators/graphql/install_generator.rb +27 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +1 -1
- data/lib/graphql/execution/interpreter.rb +13 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +10 -1
- data/lib/graphql/execution/interpreter/runtime.rb +72 -64
- data/lib/graphql/interface_type.rb +5 -0
- data/lib/graphql/language/nodes.rb +4 -4
- data/lib/graphql/object_type.rb +44 -35
- data/lib/graphql/pagination/connection.rb +24 -4
- data/lib/graphql/pagination/relation_connection.rb +19 -11
- data/lib/graphql/query.rb +28 -1
- data/lib/graphql/query/arguments.rb +2 -1
- data/lib/graphql/query/fingerprint.rb +24 -0
- data/lib/graphql/rake_task.rb +9 -9
- data/lib/graphql/schema.rb +90 -92
- data/lib/graphql/schema/field.rb +33 -13
- data/lib/graphql/schema/field/connection_extension.rb +3 -1
- data/lib/graphql/schema/input_object.rb +14 -2
- data/lib/graphql/schema/interface.rb +10 -0
- data/lib/graphql/schema/loader.rb +1 -1
- data/lib/graphql/schema/member/has_arguments.rb +21 -9
- data/lib/graphql/schema/object.rb +53 -17
- data/lib/graphql/schema/possible_types.rb +9 -4
- data/lib/graphql/schema/warden.rb +48 -7
- data/lib/graphql/types/big_int.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b006fe34754dbc2e3ee7ef459e05bdf325755538e013efd96c9eef3990af9a7
|
4
|
+
data.tar.gz: b655b349e59ea3a34d6ed71fb3870af1cc91afc75bf7147cb0d9b436b78a97c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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: {
|
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
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
field_ast_nodes
|
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
|
-
|
202
|
+
@interpreter_context[:current_arguments] = kwarg_arguments
|
202
203
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
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
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
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
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
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
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
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
|
42
|
+
def ==(other)
|
43
43
|
return true if equal?(other)
|
44
|
-
other.
|
45
|
-
other.scalars
|
46
|
-
other.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
|
data/lib/graphql/object_type.rb
CHANGED
@@ -17,17 +17,15 @@ module GraphQL
|
|
17
17
|
def initialize
|
18
18
|
super
|
19
19
|
@fields = {}
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@
|
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
|
-
@
|
28
|
-
@
|
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
|
-
@
|
39
|
-
@
|
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
|
-
|
49
|
-
|
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
|
-
|
82
|
-
|
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 :
|
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
|
-
|
101
|
-
|
102
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
121
|
+
@clean_inherited_fields.merge!(iface.fields)
|
122
|
+
else
|
123
|
+
pp iface
|
114
124
|
end
|
115
125
|
end
|
116
|
-
@clean_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:
|
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
|
-
|
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
|
-
@
|
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
|