graphql 1.12.10 → 1.12.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql/backtrace/table.rb +14 -2
- data/lib/graphql/backtrace/tracer.rb +7 -4
- data/lib/graphql/cop/nullability.rb +28 -0
- data/lib/graphql/cop/resolve_methods.rb +28 -0
- data/lib/graphql/dataloader.rb +27 -14
- 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 +482 -203
- data/lib/graphql/execution/lazy.rb +5 -1
- data/lib/graphql/introspection/schema_type.rb +1 -1
- data/lib/graphql/query.rb +1 -1
- data/lib/graphql/schema.rb +34 -200
- data/lib/graphql/schema/addition.rb +238 -0
- data/lib/graphql/schema/argument.rb +56 -39
- data/lib/graphql/schema/build_from_definition.rb +8 -2
- data/lib/graphql/schema/directive/transform.rb +13 -1
- data/lib/graphql/schema/enum.rb +10 -1
- data/lib/graphql/schema/input_object.rb +11 -15
- data/lib/graphql/schema/member/build_type.rb +1 -0
- data/lib/graphql/schema/printer.rb +11 -16
- data/lib/graphql/schema/resolver.rb +28 -3
- 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 +5 -17
- data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
@@ -240,62 +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
|
-
|
269
|
-
|
270
|
-
|
271
|
-
end
|
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
|
270
|
+
owner.load_application_object(self, loads, coerced_value, context)
|
272
271
|
end
|
273
272
|
end
|
273
|
+
end
|
274
274
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
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)
|
279
287
|
end
|
280
288
|
|
281
|
-
#
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
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
|
288
297
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
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)
|
299
316
|
end
|
300
317
|
end
|
301
318
|
|
@@ -47,10 +47,16 @@ module GraphQL
|
|
47
47
|
# _while_ building the schema.
|
48
48
|
# It will dig for a type if it encounters a custom type. This could be a problem if there are cycles.
|
49
49
|
directive_type_resolver = nil
|
50
|
-
directive_type_resolver = build_resolve_type(
|
50
|
+
directive_type_resolver = build_resolve_type(types, directives, ->(type_name) {
|
51
51
|
types[type_name] ||= begin
|
52
52
|
defn = document.definitions.find { |d| d.respond_to?(:name) && d.name == type_name }
|
53
|
-
|
53
|
+
if defn
|
54
|
+
build_definition_from_node(defn, directive_type_resolver, default_resolve)
|
55
|
+
elsif (built_in_defn = GraphQL::Schema::BUILT_IN_TYPES[type_name])
|
56
|
+
built_in_defn
|
57
|
+
else
|
58
|
+
raise "No definition for #{type_name.inspect} found in schema document or built-in types. Add a definition for it or remove it."
|
59
|
+
end
|
54
60
|
end
|
55
61
|
})
|
56
62
|
|
@@ -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
|
|
@@ -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
|
@@ -374,16 +379,36 @@ module GraphQL
|
|
374
379
|
# @param extension [Class] Extension class
|
375
380
|
# @param options [Hash] Optional extension options
|
376
381
|
def extension(extension, **options)
|
377
|
-
|
382
|
+
@own_extensions ||= []
|
383
|
+
@own_extensions << {extension => options}
|
378
384
|
end
|
379
385
|
|
380
386
|
# @api private
|
381
387
|
def extensions
|
382
|
-
|
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
|
383
404
|
end
|
384
405
|
|
385
406
|
private
|
386
407
|
|
408
|
+
def own_extensions
|
409
|
+
@own_extensions
|
410
|
+
end
|
411
|
+
|
387
412
|
def own_arguments_loads_as_type
|
388
413
|
@own_arguments_loads_as_type ||= {}
|
389
414
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module GraphQL
|
4
4
|
module Types
|
5
5
|
module Relay
|
6
|
+
# Include this module to your root Query type to get a Relay-compliant `node(id: ID!): Node` field that uses the schema's `object_from_id` hook.
|
6
7
|
module HasNodeField
|
7
8
|
def self.included(child_class)
|
8
9
|
child_class.field(**field_options, &field_block)
|
@@ -12,7 +13,6 @@ module GraphQL
|
|
12
13
|
def field_options
|
13
14
|
{
|
14
15
|
name: "node",
|
15
|
-
owner: nil,
|
16
16
|
type: GraphQL::Types::Relay::Node,
|
17
17
|
null: true,
|
18
18
|
description: "Fetches an object given its ID.",
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module GraphQL
|
4
4
|
module Types
|
5
5
|
module Relay
|
6
|
+
# Include this module to your root Query type to get a Relay-style `nodes(id: ID!): [Node]` field that uses the schema's `object_from_id` hook.
|
6
7
|
module HasNodesField
|
7
8
|
def self.included(child_class)
|
8
9
|
child_class.field(**field_options, &field_block)
|
@@ -12,7 +13,6 @@ module GraphQL
|
|
12
13
|
def field_options
|
13
14
|
{
|
14
15
|
name: "nodes",
|
15
|
-
owner: nil,
|
16
16
|
type: [GraphQL::Types::Relay::Node, null: true],
|
17
17
|
null: false,
|
18
18
|
description: "Fetches a list of objects given a list of IDs.",
|
@@ -6,7 +6,7 @@ module GraphQL
|
|
6
6
|
# or use it for inspiration for your own field definition.
|
7
7
|
#
|
8
8
|
# @example Adding this field directly
|
9
|
-
#
|
9
|
+
# include GraphQL::Types::Relay::HasNodeField
|
10
10
|
#
|
11
11
|
# @example Implementing a similar field in your own Query root
|
12
12
|
#
|
@@ -19,7 +19,7 @@ module GraphQL
|
|
19
19
|
# context.schema.object_from_id(id, context)
|
20
20
|
# end
|
21
21
|
#
|
22
|
-
NodeField = GraphQL::Schema::Field.new(**HasNodeField.field_options, &HasNodeField.field_block)
|
22
|
+
NodeField = GraphQL::Schema::Field.new(owner: nil, **HasNodeField.field_options, &HasNodeField.field_block)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -6,7 +6,7 @@ module GraphQL
|
|
6
6
|
# or use it for inspiration for your own field definition.
|
7
7
|
#
|
8
8
|
# @example Adding this field directly
|
9
|
-
#
|
9
|
+
# include GraphQL::Types::Relay::HasNodesField
|
10
10
|
#
|
11
11
|
# @example Implementing a similar field in your own Query root
|
12
12
|
#
|
@@ -21,7 +21,7 @@ module GraphQL
|
|
21
21
|
# end
|
22
22
|
# end
|
23
23
|
#
|
24
|
-
NodesField = GraphQL::Schema::Field.new(**HasNodesField.field_options, &HasNodesField.field_block)
|
24
|
+
NodesField = GraphQL::Schema::Field.new(owner: nil, **HasNodesField.field_options, &HasNodesField.field_block)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
data/lib/graphql/version.rb
CHANGED
data/readme.md
CHANGED
@@ -2,9 +2,6 @@
|
|
2
2
|
|
3
3
|
[![CI Suite](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml/badge.svg)](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/graphql.svg)](https://rubygems.org/gems/graphql)
|
5
|
-
[![Code Climate](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/gpa.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
|
6
|
-
[![Test Coverage](https://codeclimate.com/github/rmosolgo/graphql-ruby/badges/coverage.svg)](https://codeclimate.com/github/rmosolgo/graphql-ruby)
|
7
|
-
[![built with love](https://cloud.githubusercontent.com/assets/2231765/6766607/d07992c6-cfc9-11e4-813f-d9240714dd50.png)](https://rmosolgo.github.io/react-badges/)
|
8
5
|
|
9
6
|
A Ruby implementation of [GraphQL](https://graphql.org/).
|
10
7
|
|