graphql 1.12.8 → 1.12.13
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 +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/backtrace/tracer.rb +1 -1
- data/lib/graphql/dataloader.rb +59 -15
- data/lib/graphql/dataloader/null_dataloader.rb +1 -0
- data/lib/graphql/execution/execute.rb +1 -1
- data/lib/graphql/execution/interpreter.rb +4 -8
- data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -2
- data/lib/graphql/execution/interpreter/resolve.rb +6 -2
- data/lib/graphql/execution/interpreter/runtime.rb +496 -222
- data/lib/graphql/execution/lazy.rb +5 -1
- data/lib/graphql/introspection/schema_type.rb +1 -1
- data/lib/graphql/pagination/connections.rb +1 -1
- data/lib/graphql/query/null_context.rb +7 -1
- data/lib/graphql/rake_task.rb +3 -0
- data/lib/graphql/schema.rb +44 -218
- 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/enum.rb +10 -1
- data/lib/graphql/schema/input_object.rb +13 -17
- 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/printer.rb +11 -16
- data/lib/graphql/schema/resolver.rb +52 -25
- data/lib/graphql/schema/scalar.rb +3 -1
- 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/serialize.rb +8 -1
- data/lib/graphql/types/relay/has_node_field.rb +1 -1
- data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
- data/lib/graphql/types/relay/node_field.rb +2 -2
- data/lib/graphql/types/relay/nodes_field.rb +2 -2
- data/lib/graphql/version.rb +1 -1
- data/readme.md +0 -3
- metadata +7 -21
- data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
@@ -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].final_result
|
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
|
data/lib/graphql/schema/enum.rb
CHANGED
@@ -24,6 +24,15 @@ module GraphQL
|
|
24
24
|
extend GraphQL::Schema::Member::ValidatesInput
|
25
25
|
|
26
26
|
class UnresolvedValueError < GraphQL::EnumType::UnresolvedValueError
|
27
|
+
def initialize(value:, enum:, context:)
|
28
|
+
fix_message = ", but this isn't a valid value for `#{enum.graphql_name}`. Update the field or resolver to return one of `#{enum.graphql_name}`'s values instead."
|
29
|
+
message = if (cp = context[:current_path]) && (cf = context[:current_field])
|
30
|
+
"`#{cf.path}` returned `#{value.inspect}` at `#{cp.join(".")}`#{fix_message}"
|
31
|
+
else
|
32
|
+
"`#{value.inspect}` was returned for `#{enum.graphql_name}`#{fix_message}"
|
33
|
+
end
|
34
|
+
super(message)
|
35
|
+
end
|
27
36
|
end
|
28
37
|
|
29
38
|
class << self
|
@@ -100,7 +109,7 @@ module GraphQL
|
|
100
109
|
if enum_value
|
101
110
|
enum_value.graphql_name
|
102
111
|
else
|
103
|
-
raise
|
112
|
+
raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx)
|
104
113
|
end
|
105
114
|
end
|
106
115
|
|
@@ -11,6 +11,14 @@ module GraphQL
|
|
11
11
|
|
12
12
|
include GraphQL::Dig
|
13
13
|
|
14
|
+
# @return [GraphQL::Query::Context] The context for this query
|
15
|
+
attr_reader :context
|
16
|
+
# @return [GraphQL::Query::Arguments, GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance
|
17
|
+
attr_reader :arguments
|
18
|
+
|
19
|
+
# Ruby-like hash behaviors, read-only
|
20
|
+
def_delegators :@ruby_style_hash, :keys, :values, :each, :map, :any?, :empty?
|
21
|
+
|
14
22
|
def initialize(arguments = nil, ruby_kwargs: nil, context:, defaults_used:)
|
15
23
|
@context = context
|
16
24
|
if ruby_kwargs
|
@@ -54,19 +62,8 @@ module GraphQL
|
|
54
62
|
@maybe_lazies = maybe_lazies
|
55
63
|
end
|
56
64
|
|
57
|
-
# @return [GraphQL::Query::Context] The context for this query
|
58
|
-
attr_reader :context
|
59
|
-
|
60
|
-
# @return [GraphQL::Query::Arguments, GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance
|
61
|
-
attr_reader :arguments
|
62
|
-
|
63
|
-
# Ruby-like hash behaviors, read-only
|
64
|
-
def_delegators :@ruby_style_hash, :keys, :values, :each, :map, :any?, :empty?
|
65
|
-
|
66
65
|
def to_h
|
67
|
-
@ruby_style_hash
|
68
|
-
h.merge(key => unwrap_value(value))
|
69
|
-
end
|
66
|
+
unwrap_value(@ruby_style_hash)
|
70
67
|
end
|
71
68
|
|
72
69
|
def to_hash
|
@@ -91,8 +88,8 @@ module GraphQL
|
|
91
88
|
when Array
|
92
89
|
value.map { |item| unwrap_value(item) }
|
93
90
|
when Hash
|
94
|
-
value.
|
95
|
-
h.merge(key => unwrap_value(value))
|
91
|
+
value.reduce({}) do |h, (key, value)|
|
92
|
+
h.merge!(key => unwrap_value(value))
|
96
93
|
end
|
97
94
|
when InputObject
|
98
95
|
value.to_h
|
@@ -162,7 +159,6 @@ module GraphQL
|
|
162
159
|
# @api private
|
163
160
|
INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object responding to `to_h` or `to_unsafe_h`."
|
164
161
|
|
165
|
-
|
166
162
|
def validate_non_null_input(input, ctx)
|
167
163
|
result = GraphQL::Query::InputValidationResult.new
|
168
164
|
|
@@ -226,8 +222,8 @@ module GraphQL
|
|
226
222
|
# It's funny to think of a _result_ of an input object.
|
227
223
|
# This is used for rendering the default value in introspection responses.
|
228
224
|
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 }
|
225
|
+
# Allow the application to provide values as :snake_symbols, and convert them to the camelStrings
|
226
|
+
value = value.reduce({}) { |memo, (k, v)| memo[Member::BuildType.camelize(k.to_s)] = v; memo }
|
231
227
|
|
232
228
|
result = {}
|
233
229
|
|
@@ -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|
|
@@ -4,37 +4,32 @@ module GraphQL
|
|
4
4
|
# Used to convert your {GraphQL::Schema} to a GraphQL schema string
|
5
5
|
#
|
6
6
|
# @example print your schema to standard output (via helper)
|
7
|
-
# MySchema = GraphQL::Schema.define(query: QueryType)
|
8
7
|
# puts GraphQL::Schema::Printer.print_schema(MySchema)
|
9
8
|
#
|
10
9
|
# @example print your schema to standard output
|
11
|
-
# MySchema = GraphQL::Schema.define(query: QueryType)
|
12
10
|
# puts GraphQL::Schema::Printer.new(MySchema).print_schema
|
13
11
|
#
|
14
12
|
# @example print a single type to standard output
|
15
|
-
#
|
16
|
-
# name "Query"
|
13
|
+
# class Types::Query < GraphQL::Schema::Object
|
17
14
|
# description "The query root of this schema"
|
18
15
|
#
|
19
|
-
# field :post
|
20
|
-
# type post_type
|
21
|
-
# resolve ->(obj, args, ctx) { Post.find(args["id"]) }
|
22
|
-
# end
|
16
|
+
# field :post, Types::Post, null: true
|
23
17
|
# end
|
24
18
|
#
|
25
|
-
#
|
26
|
-
# name "Post"
|
19
|
+
# class Types::Post < GraphQL::Schema::Object
|
27
20
|
# description "A blog post"
|
28
21
|
#
|
29
|
-
# field :id,
|
30
|
-
# field :title,
|
31
|
-
# field :body,
|
22
|
+
# field :id, ID, null: false
|
23
|
+
# field :title, String, null: false
|
24
|
+
# field :body, String, null: false
|
32
25
|
# end
|
33
26
|
#
|
34
|
-
# MySchema
|
27
|
+
# class MySchema < GraphQL::Schema
|
28
|
+
# query(Types::Query)
|
29
|
+
# end
|
35
30
|
#
|
36
31
|
# printer = GraphQL::Schema::Printer.new(MySchema)
|
37
|
-
# puts printer.print_type(
|
32
|
+
# puts printer.print_type(Types::Post)
|
38
33
|
#
|
39
34
|
class Printer < GraphQL::Language::Printer
|
40
35
|
attr_reader :schema, :warden
|
@@ -87,7 +82,7 @@ module GraphQL
|
|
87
82
|
|
88
83
|
# Return a GraphQL schema string for the defined types in the schema
|
89
84
|
def print_schema
|
90
|
-
print(@document)
|
85
|
+
print(@document) + "\n"
|
91
86
|
end
|
92
87
|
|
93
88
|
def print_type(type)
|
@@ -307,10 +307,15 @@ module GraphQL
|
|
307
307
|
arguments: arguments,
|
308
308
|
null: null,
|
309
309
|
complexity: complexity,
|
310
|
-
extensions: extensions,
|
311
310
|
broadcastable: broadcastable?,
|
312
311
|
}
|
313
312
|
|
313
|
+
# If there aren't any, then the returned array is `[].freeze`,
|
314
|
+
# but passing that along breaks some user code.
|
315
|
+
if (exts = extensions).any?
|
316
|
+
field_opts[:extensions] = exts
|
317
|
+
end
|
318
|
+
|
314
319
|
if has_max_page_size?
|
315
320
|
field_opts[:max_page_size] = max_page_size
|
316
321
|
end
|
@@ -333,30 +338,32 @@ module GraphQL
|
|
333
338
|
arg_defn = super(*args, from_resolver: true, **kwargs)
|
334
339
|
own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
|
335
340
|
|
336
|
-
if
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
341
|
+
if !method_defined?(:"load_#{arg_defn.keyword}")
|
342
|
+
if loads && arg_defn.type.list?
|
343
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
344
|
+
def load_#{arg_defn.keyword}(values)
|
345
|
+
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
346
|
+
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
347
|
+
context.schema.after_lazy(values) do |values2|
|
348
|
+
GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value, context) })
|
349
|
+
end
|
343
350
|
end
|
351
|
+
RUBY
|
352
|
+
elsif loads
|
353
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
354
|
+
def load_#{arg_defn.keyword}(value)
|
355
|
+
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
356
|
+
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
357
|
+
load_application_object(argument, lookup_as_type, value, context)
|
358
|
+
end
|
359
|
+
RUBY
|
360
|
+
else
|
361
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
362
|
+
def load_#{arg_defn.keyword}(value)
|
363
|
+
value
|
364
|
+
end
|
365
|
+
RUBY
|
344
366
|
end
|
345
|
-
RUBY
|
346
|
-
elsif loads
|
347
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
348
|
-
def load_#{arg_defn.keyword}(value)
|
349
|
-
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
350
|
-
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
351
|
-
load_application_object(argument, lookup_as_type, value, context)
|
352
|
-
end
|
353
|
-
RUBY
|
354
|
-
else
|
355
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
356
|
-
def load_#{arg_defn.keyword}(value)
|
357
|
-
value
|
358
|
-
end
|
359
|
-
RUBY
|
360
367
|
end
|
361
368
|
|
362
369
|
arg_defn
|
@@ -372,16 +379,36 @@ module GraphQL
|
|
372
379
|
# @param extension [Class] Extension class
|
373
380
|
# @param options [Hash] Optional extension options
|
374
381
|
def extension(extension, **options)
|
375
|
-
|
382
|
+
@own_extensions ||= []
|
383
|
+
@own_extensions << {extension => options}
|
376
384
|
end
|
377
385
|
|
378
386
|
# @api private
|
379
387
|
def extensions
|
380
|
-
|
388
|
+
own_exts = @own_extensions
|
389
|
+
# Jump through some hoops to avoid creating arrays when we don't actually need them
|
390
|
+
if superclass.respond_to?(:extensions)
|
391
|
+
s_exts = superclass.extensions
|
392
|
+
if own_exts
|
393
|
+
if s_exts.any?
|
394
|
+
own_exts + s_exts
|
395
|
+
else
|
396
|
+
own_exts
|
397
|
+
end
|
398
|
+
else
|
399
|
+
s_exts
|
400
|
+
end
|
401
|
+
else
|
402
|
+
own_exts || EMPTY_ARRAY
|
403
|
+
end
|
381
404
|
end
|
382
405
|
|
383
406
|
private
|
384
407
|
|
408
|
+
def own_extensions
|
409
|
+
@own_extensions
|
410
|
+
end
|
411
|
+
|
385
412
|
def own_arguments_loads_as_type
|
386
413
|
@own_arguments_loads_as_type ||= {}
|
387
414
|
end
|