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 +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
|