graphql 1.12.6 → 1.12.11
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.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install_generator.rb +1 -1
- data/lib/generators/graphql/templates/graphql_controller.erb +2 -2
- data/lib/graphql.rb +10 -10
- data/lib/graphql/backtrace/table.rb +14 -2
- data/lib/graphql/dataloader.rb +44 -15
- data/lib/graphql/execution/errors.rb +109 -11
- data/lib/graphql/execution/execute.rb +1 -1
- data/lib/graphql/execution/interpreter.rb +4 -8
- data/lib/graphql/execution/interpreter/runtime.rb +207 -188
- data/lib/graphql/introspection.rb +1 -1
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/introspection/schema_type.rb +1 -1
- data/lib/graphql/pagination/active_record_relation_connection.rb +7 -0
- data/lib/graphql/pagination/connections.rb +1 -1
- data/lib/graphql/pagination/relation_connection.rb +8 -1
- data/lib/graphql/query.rb +1 -3
- data/lib/graphql/query/null_context.rb +7 -1
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/rake_task.rb +3 -0
- data/lib/graphql/schema.rb +49 -237
- data/lib/graphql/schema/addition.rb +238 -0
- data/lib/graphql/schema/argument.rb +55 -36
- data/lib/graphql/schema/directive/transform.rb +13 -1
- data/lib/graphql/schema/input_object.rb +2 -2
- data/lib/graphql/schema/loader.rb +8 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +3 -15
- data/lib/graphql/schema/object.rb +19 -5
- data/lib/graphql/schema/resolver.rb +46 -24
- data/lib/graphql/schema/scalar.rb +3 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +3 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +6 -2
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +17 -8
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
- data/lib/graphql/static_validation/validator.rb +5 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +4 -3
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +0 -3
- data/lib/graphql/subscriptions/serialize.rb +11 -1
- data/lib/graphql/types/relay/base_connection.rb +4 -0
- data/lib/graphql/types/relay/connection_behaviors.rb +21 -10
- data/lib/graphql/types/relay/edge_behaviors.rb +12 -1
- data/lib/graphql/version.rb +1 -1
- metadata +3 -3
- data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
@@ -0,0 +1,238 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Addition
|
6
|
+
attr_reader :directives, :possible_types, :types, :union_memberships, :references, :arguments_with_default_values
|
7
|
+
|
8
|
+
def initialize(schema:, own_types:, new_types:)
|
9
|
+
@schema = schema
|
10
|
+
@own_types = own_types
|
11
|
+
@directives = Set.new
|
12
|
+
@possible_types = {}
|
13
|
+
@types = {}
|
14
|
+
@union_memberships = {}
|
15
|
+
@references = Hash.new { |h, k| h[k] = [] }
|
16
|
+
@arguments_with_default_values = []
|
17
|
+
add_type_and_traverse(new_types)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def references_to(thing, from:)
|
23
|
+
@references[thing] << from
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_type(name)
|
27
|
+
@types[name] || @schema.get_type(name)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Lookup using `own_types` here because it's ok to override
|
31
|
+
# inherited types by name
|
32
|
+
def get_local_type(name)
|
33
|
+
@types[name] || @own_types[name]
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_directives_from(owner)
|
37
|
+
dirs = owner.directives.map(&:class)
|
38
|
+
@directives.merge(dirs)
|
39
|
+
add_type_and_traverse(dirs)
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_type_and_traverse(new_types)
|
43
|
+
late_types = []
|
44
|
+
new_types.each { |t| add_type(t, owner: nil, late_types: late_types, path: [t.graphql_name]) }
|
45
|
+
missed_late_types = 0
|
46
|
+
while (late_type_vals = late_types.shift)
|
47
|
+
type_owner, lt = late_type_vals
|
48
|
+
if lt.is_a?(String)
|
49
|
+
type = Member::BuildType.constantize(lt)
|
50
|
+
# Reset the counter, since we might succeed next go-round
|
51
|
+
missed_late_types = 0
|
52
|
+
update_type_owner(type_owner, type)
|
53
|
+
add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name])
|
54
|
+
elsif lt.is_a?(LateBoundType)
|
55
|
+
if (type = get_type(lt.name))
|
56
|
+
# Reset the counter, since we might succeed next go-round
|
57
|
+
missed_late_types = 0
|
58
|
+
update_type_owner(type_owner, type)
|
59
|
+
add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name])
|
60
|
+
else
|
61
|
+
missed_late_types += 1
|
62
|
+
# Add it back to the list, maybe we'll be able to resolve it later.
|
63
|
+
late_types << [type_owner, lt]
|
64
|
+
if missed_late_types == late_types.size
|
65
|
+
# We've looked at all of them and haven't resolved one.
|
66
|
+
raise UnresolvedLateBoundTypeError.new(type: lt)
|
67
|
+
else
|
68
|
+
# Try the next one
|
69
|
+
end
|
70
|
+
end
|
71
|
+
else
|
72
|
+
raise ArgumentError, "Unexpected late type: #{lt.inspect}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def update_type_owner(owner, type)
|
79
|
+
case owner
|
80
|
+
when Class
|
81
|
+
if owner.kind.union?
|
82
|
+
# It's a union with possible_types
|
83
|
+
# Replace the item by class name
|
84
|
+
owner.assign_type_membership_object_type(type)
|
85
|
+
@possible_types[owner.graphql_name] = owner.possible_types
|
86
|
+
elsif type.kind.interface? && owner.kind.object?
|
87
|
+
new_interfaces = []
|
88
|
+
owner.interfaces.each do |int_t|
|
89
|
+
if int_t.is_a?(String) && int_t == type.graphql_name
|
90
|
+
new_interfaces << type
|
91
|
+
elsif int_t.is_a?(LateBoundType) && int_t.graphql_name == type.graphql_name
|
92
|
+
new_interfaces << type
|
93
|
+
else
|
94
|
+
# Don't re-add proper interface definitions,
|
95
|
+
# they were probably already added, maybe with options.
|
96
|
+
end
|
97
|
+
end
|
98
|
+
owner.implements(*new_interfaces)
|
99
|
+
new_interfaces.each do |int|
|
100
|
+
pt = @possible_types[int.graphql_name] ||= []
|
101
|
+
if !pt.include?(owner)
|
102
|
+
pt << owner
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
when nil
|
108
|
+
# It's a root type
|
109
|
+
@types[type.graphql_name] = type
|
110
|
+
when GraphQL::Schema::Field, GraphQL::Schema::Argument
|
111
|
+
orig_type = owner.type
|
112
|
+
# Apply list/non-null wrapper as needed
|
113
|
+
if orig_type.respond_to?(:of_type)
|
114
|
+
transforms = []
|
115
|
+
while (orig_type.respond_to?(:of_type))
|
116
|
+
if orig_type.kind.non_null?
|
117
|
+
transforms << :to_non_null_type
|
118
|
+
elsif orig_type.kind.list?
|
119
|
+
transforms << :to_list_type
|
120
|
+
else
|
121
|
+
raise "Invariant: :of_type isn't non-null or list"
|
122
|
+
end
|
123
|
+
orig_type = orig_type.of_type
|
124
|
+
end
|
125
|
+
transforms.reverse_each { |t| type = type.public_send(t) }
|
126
|
+
end
|
127
|
+
owner.type = type
|
128
|
+
else
|
129
|
+
raise "Unexpected update: #{owner.inspect} #{type.inspect}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def add_type(type, owner:, late_types:, path:)
|
134
|
+
if type.respond_to?(:metadata) && type.metadata.is_a?(Hash)
|
135
|
+
type_class = type.metadata[:type_class]
|
136
|
+
if type_class.nil?
|
137
|
+
raise ArgumentError, "Can't add legacy type: #{type} (#{type.class})"
|
138
|
+
else
|
139
|
+
type = type_class
|
140
|
+
end
|
141
|
+
elsif type.is_a?(String) || type.is_a?(GraphQL::Schema::LateBoundType)
|
142
|
+
late_types << [owner, type]
|
143
|
+
return
|
144
|
+
end
|
145
|
+
|
146
|
+
if owner.is_a?(Class) && owner < GraphQL::Schema::Union
|
147
|
+
um = @union_memberships[type.graphql_name] ||= []
|
148
|
+
um << owner
|
149
|
+
end
|
150
|
+
|
151
|
+
if (prev_type = get_local_type(type.graphql_name))
|
152
|
+
if prev_type != type
|
153
|
+
raise DuplicateTypeNamesError.new(
|
154
|
+
type_name: type.graphql_name,
|
155
|
+
first_definition: prev_type,
|
156
|
+
second_definition: type,
|
157
|
+
path: path,
|
158
|
+
)
|
159
|
+
else
|
160
|
+
# This type was already added
|
161
|
+
end
|
162
|
+
elsif type.is_a?(Class) && type < GraphQL::Schema::Directive
|
163
|
+
@directives << type
|
164
|
+
type.arguments.each do |name, arg|
|
165
|
+
arg_type = arg.type.unwrap
|
166
|
+
references_to(arg_type, from: arg)
|
167
|
+
add_type(arg_type, owner: arg, late_types: late_types, path: path + [name])
|
168
|
+
if arg.default_value?
|
169
|
+
@arguments_with_default_values << arg
|
170
|
+
end
|
171
|
+
end
|
172
|
+
else
|
173
|
+
@types[type.graphql_name] = type
|
174
|
+
add_directives_from(type)
|
175
|
+
if type.kind.fields?
|
176
|
+
type.fields.each do |name, field|
|
177
|
+
field_type = field.type.unwrap
|
178
|
+
references_to(field_type, from: field)
|
179
|
+
field_path = path + [name]
|
180
|
+
add_type(field_type, owner: field, late_types: late_types, path: field_path)
|
181
|
+
add_directives_from(field)
|
182
|
+
field.arguments.each do |arg_name, arg|
|
183
|
+
add_directives_from(arg)
|
184
|
+
arg_type = arg.type.unwrap
|
185
|
+
references_to(arg_type, from: arg)
|
186
|
+
add_type(arg_type, owner: arg, late_types: late_types, path: field_path + [arg_name])
|
187
|
+
if arg.default_value?
|
188
|
+
@arguments_with_default_values << arg
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
if type.kind.input_object?
|
194
|
+
type.arguments.each do |arg_name, arg|
|
195
|
+
add_directives_from(arg)
|
196
|
+
arg_type = arg.type.unwrap
|
197
|
+
references_to(arg_type, from: arg)
|
198
|
+
add_type(arg_type, owner: arg, late_types: late_types, path: path + [arg_name])
|
199
|
+
if arg.default_value?
|
200
|
+
@arguments_with_default_values << arg
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
if type.kind.union?
|
205
|
+
@possible_types[type.graphql_name] = type.possible_types
|
206
|
+
type.possible_types.each do |t|
|
207
|
+
add_type(t, owner: type, late_types: late_types, path: path + ["possible_types"])
|
208
|
+
end
|
209
|
+
end
|
210
|
+
if type.kind.interface?
|
211
|
+
type.orphan_types.each do |t|
|
212
|
+
add_type(t, owner: type, late_types: late_types, path: path + ["orphan_types"])
|
213
|
+
end
|
214
|
+
end
|
215
|
+
if type.kind.object?
|
216
|
+
@possible_types[type.graphql_name] = [type]
|
217
|
+
type.interface_type_memberships.each do |interface_type_membership|
|
218
|
+
case interface_type_membership
|
219
|
+
when Schema::TypeMembership
|
220
|
+
interface_type = interface_type_membership.abstract_type
|
221
|
+
# We can get these now; we'll have to get late-bound types later
|
222
|
+
if interface_type.is_a?(Module)
|
223
|
+
implementers = @possible_types[interface_type.graphql_name] ||= []
|
224
|
+
implementers << type
|
225
|
+
end
|
226
|
+
when String, Schema::LateBoundType
|
227
|
+
interface_type = interface_type_membership
|
228
|
+
else
|
229
|
+
raise ArgumentError, "Invariant: unexpected type membership for #{type.graphql_name}: #{interface_type_membership.class} (#{interface_type_membership.inspect})"
|
230
|
+
end
|
231
|
+
add_type(interface_type, owner: type, late_types: late_types, path: path + ["implements"])
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -240,60 +240,79 @@ module GraphQL
|
|
240
240
|
def coerce_into_values(parent_object, values, context, argument_values)
|
241
241
|
arg_name = graphql_name
|
242
242
|
arg_key = keyword
|
243
|
-
has_value = false
|
244
243
|
default_used = false
|
244
|
+
|
245
245
|
if values.key?(arg_name)
|
246
|
-
has_value = true
|
247
246
|
value = values[arg_name]
|
248
247
|
elsif values.key?(arg_key)
|
249
|
-
has_value = true
|
250
248
|
value = values[arg_key]
|
251
249
|
elsif default_value?
|
252
|
-
has_value = true
|
253
250
|
value = default_value
|
254
251
|
default_used = true
|
252
|
+
else
|
253
|
+
# no value at all
|
254
|
+
owner.validate_directive_argument(self, nil)
|
255
|
+
return
|
255
256
|
end
|
256
257
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
end
|
258
|
+
loaded_value = nil
|
259
|
+
coerced_value = context.schema.error_handler.with_error_handling(context) do
|
260
|
+
type.coerce_input(value, context)
|
261
|
+
end
|
262
262
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
263
|
+
# TODO this should probably be inside after_lazy
|
264
|
+
if loads && !from_resolver?
|
265
|
+
loaded_value = if type.list?
|
266
|
+
loaded_values = coerced_value.map { |val| owner.load_application_object(self, loads, val, context) }
|
267
|
+
context.schema.after_any_lazies(loaded_values) { |result| result }
|
268
|
+
else
|
269
|
+
context.query.with_error_handling do
|
269
270
|
owner.load_application_object(self, loads, coerced_value, context)
|
270
271
|
end
|
271
272
|
end
|
273
|
+
end
|
272
274
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
275
|
+
coerced_value = if loaded_value
|
276
|
+
loaded_value
|
277
|
+
else
|
278
|
+
coerced_value
|
279
|
+
end
|
280
|
+
|
281
|
+
# If this isn't lazy, then the block returns eagerly and assigns the result here
|
282
|
+
# If it _is_ lazy, then we write the lazy to the hash, then update it later
|
283
|
+
argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |coerced_value|
|
284
|
+
owner.validate_directive_argument(self, coerced_value)
|
285
|
+
prepared_value = context.schema.error_handler.with_error_handling(context) do
|
286
|
+
prepare_value(parent_object, coerced_value, context: context)
|
277
287
|
end
|
278
288
|
|
279
|
-
#
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
289
|
+
# TODO code smell to access such a deeply-nested constant in a distant module
|
290
|
+
argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
|
291
|
+
value: prepared_value,
|
292
|
+
definition: self,
|
293
|
+
default_used: default_used,
|
294
|
+
)
|
295
|
+
end
|
296
|
+
end
|
286
297
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
298
|
+
# @api private
|
299
|
+
def validate_default_value
|
300
|
+
coerced_default_value = begin
|
301
|
+
type.coerce_isolated_result(default_value) unless default_value.nil?
|
302
|
+
rescue GraphQL::Schema::Enum::UnresolvedValueError
|
303
|
+
# It raises this, which is helpful at runtime, but not here...
|
304
|
+
default_value
|
305
|
+
end
|
306
|
+
res = type.valid_isolated_input?(coerced_default_value)
|
307
|
+
if !res
|
308
|
+
raise InvalidDefaultValueError.new(self)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
class InvalidDefaultValueError < GraphQL::Error
|
313
|
+
def initialize(argument)
|
314
|
+
message = "`#{argument.path}` has an invalid default value: `#{argument.default_value.inspect}` isn't accepted by `#{argument.type.to_type_signature}`; update the default value or the argument type."
|
315
|
+
super(message)
|
297
316
|
end
|
298
317
|
end
|
299
318
|
|
@@ -39,7 +39,19 @@ module GraphQL
|
|
39
39
|
transform_name = arguments[:by]
|
40
40
|
if TRANSFORMS.include?(transform_name) && return_value.respond_to?(transform_name)
|
41
41
|
return_value = return_value.public_send(transform_name)
|
42
|
-
context.namespace(:interpreter)[:runtime].
|
42
|
+
response = context.namespace(:interpreter)[:runtime].response
|
43
|
+
*keys, last = path
|
44
|
+
keys.each do |key|
|
45
|
+
if response && (response = response[key])
|
46
|
+
next
|
47
|
+
else
|
48
|
+
break
|
49
|
+
end
|
50
|
+
end
|
51
|
+
if response
|
52
|
+
response[last] = return_value
|
53
|
+
end
|
54
|
+
nil
|
43
55
|
end
|
44
56
|
end
|
45
57
|
end
|
@@ -226,8 +226,8 @@ module GraphQL
|
|
226
226
|
# It's funny to think of a _result_ of an input object.
|
227
227
|
# This is used for rendering the default value in introspection responses.
|
228
228
|
def coerce_result(value, ctx)
|
229
|
-
# Allow the application to provide values as :
|
230
|
-
value = value.reduce({}) { |memo, (k, v)| memo[k.to_s] = v; memo }
|
229
|
+
# Allow the application to provide values as :snake_symbols, and convert them to the camelStrings
|
230
|
+
value = value.reduce({}) { |memo, (k, v)| memo[Member::BuildType.camelize(k.to_s)] = v; memo }
|
231
231
|
|
232
232
|
result = {}
|
233
233
|
|
@@ -169,6 +169,12 @@ module GraphQL
|
|
169
169
|
def build_fields(type_defn, fields, type_resolver)
|
170
170
|
loader = self
|
171
171
|
fields.each do |field_hash|
|
172
|
+
unwrapped_field_hash = field_hash
|
173
|
+
while (of_type = unwrapped_field_hash["ofType"])
|
174
|
+
unwrapped_field_hash = of_type
|
175
|
+
end
|
176
|
+
type_name = unwrapped_field_hash["name"]
|
177
|
+
|
172
178
|
type_defn.field(
|
173
179
|
field_hash["name"],
|
174
180
|
type: type_resolver.call(field_hash["type"]),
|
@@ -176,6 +182,8 @@ module GraphQL
|
|
176
182
|
deprecation_reason: field_hash["deprecationReason"],
|
177
183
|
null: true,
|
178
184
|
camelize: false,
|
185
|
+
connection_extension: nil,
|
186
|
+
connection: type_name.end_with?("Connection"),
|
179
187
|
) do
|
180
188
|
if field_hash["args"].any?
|
181
189
|
loader.build_arguments(self, field_hash["args"], type_resolver)
|
@@ -113,27 +113,15 @@ module GraphQL
|
|
113
113
|
end
|
114
114
|
|
115
115
|
def visible?(context)
|
116
|
-
|
117
|
-
@mutation.visible?(context)
|
118
|
-
else
|
119
|
-
true
|
120
|
-
end
|
116
|
+
true
|
121
117
|
end
|
122
118
|
|
123
119
|
def accessible?(context)
|
124
|
-
|
125
|
-
@mutation.accessible?(context)
|
126
|
-
else
|
127
|
-
true
|
128
|
-
end
|
120
|
+
true
|
129
121
|
end
|
130
122
|
|
131
123
|
def authorized?(object, context)
|
132
|
-
|
133
|
-
@mutation.authorized?(object, context)
|
134
|
-
else
|
135
|
-
true
|
136
|
-
end
|
124
|
+
true
|
137
125
|
end
|
138
126
|
end
|
139
127
|
end
|
@@ -48,12 +48,26 @@ module GraphQL
|
|
48
48
|
# @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
|
49
49
|
# @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
|
50
50
|
def authorized_new(object, context)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
51
|
+
trace_payload = { context: context, type: self, object: object, path: context[:current_path] }
|
52
|
+
|
53
|
+
maybe_lazy_auth_val = context.query.trace("authorized", trace_payload) do
|
54
|
+
context.query.with_error_handling do
|
55
|
+
begin
|
56
|
+
authorized?(object, context)
|
57
|
+
rescue GraphQL::UnauthorizedError => err
|
58
|
+
context.schema.unauthorized_object(err)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
|
64
|
+
GraphQL::Execution::Lazy.new do
|
65
|
+
context.query.trace("authorized_lazy", trace_payload) do
|
66
|
+
context.schema.sync_lazy(maybe_lazy_auth_val)
|
67
|
+
end
|
56
68
|
end
|
69
|
+
else
|
70
|
+
maybe_lazy_auth_val
|
57
71
|
end
|
58
72
|
|
59
73
|
context.schema.after_lazy(auth_val) do |is_authorized|
|