graphql 2.0.1 → 2.0.4
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/graphql/analysis/ast/max_query_complexity.rb +0 -1
- data/lib/graphql/execution/errors.rb +12 -74
- data/lib/graphql/execution/interpreter/runtime.rb +20 -16
- data/lib/graphql/query/null_context.rb +0 -3
- data/lib/graphql/query.rb +2 -4
- data/lib/graphql/relay/range_add.rb +9 -16
- data/lib/graphql/schema/addition.rb +5 -0
- data/lib/graphql/schema/argument.rb +13 -4
- data/lib/graphql/schema/field.rb +121 -104
- data/lib/graphql/schema/member/has_directives.rb +1 -1
- data/lib/graphql/schema/member/has_interfaces.rb +1 -1
- data/lib/graphql/schema/member/relay_shortcuts.rb +28 -2
- data/lib/graphql/schema/object.rb +14 -9
- data/lib/graphql/schema/resolver/has_payload_type.rb +11 -1
- data/lib/graphql/schema/scalar.rb +3 -3
- data/lib/graphql/schema.rb +35 -6
- data/lib/graphql/tracing/data_dog_tracing.rb +3 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +0 -16
- data/lib/graphql/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 823ea48d61ed1b30e6ad87a6817f80206b9c5d8865f6350cbd142c9798162e37
|
|
4
|
+
data.tar.gz: 8e5f383cfe80418109b0f424d06ab7a78c72255d167f21f32b6c8f6bcef10610
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 95e9de185715e1b2a752ecaefdd75e8b4daad8afd4125429d58627972805d759c3a842a58bec29b0b5ee1aba5e1d06a6cb1aed9bd1881653d710f71fe4edfdc7
|
|
7
|
+
data.tar.gz: 99ff2f59d77ee74ba4c1af7fcf097afb5415b6d18a95c1a2527413d6f782f8fa6238aafa96430d2d7332f1177e64b70e5aa2eac1557d042ede9eb4a33466b8b5
|
|
@@ -2,51 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module GraphQL
|
|
4
4
|
module Execution
|
|
5
|
-
# A plugin that wraps query execution with error handling. Installed by default.
|
|
6
|
-
#
|
|
7
|
-
# @example Handling ActiveRecord::NotFound
|
|
8
|
-
#
|
|
9
|
-
# class MySchema < GraphQL::Schema
|
|
10
|
-
# rescue_from(ActiveRecord::NotFound) do |err, obj, args, ctx, field|
|
|
11
|
-
# ErrorTracker.log("Not Found: #{err.message}")
|
|
12
|
-
# nil
|
|
13
|
-
# end
|
|
14
|
-
# end
|
|
15
|
-
#
|
|
16
5
|
class Errors
|
|
17
|
-
NEW_HANDLER_HASH = ->(h, k) {
|
|
18
|
-
h[k] = {
|
|
19
|
-
class: k,
|
|
20
|
-
handler: nil,
|
|
21
|
-
subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
def initialize(schema)
|
|
26
|
-
@schema = schema
|
|
27
|
-
@handlers = {
|
|
28
|
-
class: nil,
|
|
29
|
-
handler: nil,
|
|
30
|
-
subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
|
|
31
|
-
}
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# @api private
|
|
35
|
-
def each_rescue
|
|
36
|
-
handlers = @handlers.values
|
|
37
|
-
while (handler = handlers.shift) do
|
|
38
|
-
yield(handler[:class], handler[:handler])
|
|
39
|
-
handlers.concat(handler[:subclass_handlers].values)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
6
|
# Register this handler, updating the
|
|
44
7
|
# internal handler index to maintain least-to-most specific.
|
|
45
8
|
#
|
|
46
9
|
# @param error_class [Class<Exception>]
|
|
10
|
+
# @param error_handlers [Hash]
|
|
47
11
|
# @param error_handler [Proc]
|
|
48
12
|
# @return [void]
|
|
49
|
-
def
|
|
13
|
+
def self.register_rescue_from(error_class, error_handlers, error_handler)
|
|
50
14
|
subclasses_handlers = {}
|
|
51
15
|
this_level_subclasses = []
|
|
52
16
|
# During this traversal, do two things:
|
|
@@ -54,13 +18,12 @@ module GraphQL
|
|
|
54
18
|
# and gather them up to be inserted _under_ this class
|
|
55
19
|
# - Find the point in the index where this handler should be inserted
|
|
56
20
|
# (That is, _under_ any superclasses, or at top-level, if there are no superclasses registered)
|
|
57
|
-
|
|
58
|
-
while (handlers) do
|
|
21
|
+
while (error_handlers) do
|
|
59
22
|
this_level_subclasses.clear
|
|
60
23
|
# First, identify already-loaded handlers that belong
|
|
61
24
|
# _under_ this one. (That is, they're handlers
|
|
62
25
|
# for subclasses of `error_class`.)
|
|
63
|
-
|
|
26
|
+
error_handlers.each do |err_class, handler|
|
|
64
27
|
if err_class < error_class
|
|
65
28
|
subclasses_handlers[err_class] = handler
|
|
66
29
|
this_level_subclasses << err_class
|
|
@@ -68,13 +31,13 @@ module GraphQL
|
|
|
68
31
|
end
|
|
69
32
|
# Any handlers that we'll be moving, delete them from this point in the index
|
|
70
33
|
this_level_subclasses.each do |err_class|
|
|
71
|
-
|
|
34
|
+
error_handlers.delete(err_class)
|
|
72
35
|
end
|
|
73
36
|
|
|
74
37
|
# See if any keys in this hash are superclasses of this new class:
|
|
75
|
-
next_index_point =
|
|
38
|
+
next_index_point = error_handlers.find { |err_class, handler| error_class < err_class }
|
|
76
39
|
if next_index_point
|
|
77
|
-
|
|
40
|
+
error_handlers = next_index_point[1][:subclass_handlers]
|
|
78
41
|
else
|
|
79
42
|
# this new handler doesn't belong to any sub-handlers,
|
|
80
43
|
# so insert it in the current set of `handlers`
|
|
@@ -83,40 +46,15 @@ module GraphQL
|
|
|
83
46
|
end
|
|
84
47
|
# Having found the point at which to insert this handler,
|
|
85
48
|
# register it and merge any subclass handlers back in at this point.
|
|
86
|
-
this_class_handlers =
|
|
49
|
+
this_class_handlers = error_handlers[error_class]
|
|
87
50
|
this_class_handlers[:handler] = error_handler
|
|
88
51
|
this_class_handlers[:subclass_handlers].merge!(subclasses_handlers)
|
|
89
52
|
nil
|
|
90
53
|
end
|
|
91
54
|
|
|
92
|
-
# Call the given block with the schema's configured error handlers.
|
|
93
|
-
#
|
|
94
|
-
# If the block returns a lazy value, it's not wrapped with error handling. That area will have to be wrapped itself.
|
|
95
|
-
#
|
|
96
|
-
# @param ctx [GraphQL::Query::Context]
|
|
97
|
-
# @return [Object] Either the result of the given block, or some object to replace the result, in case of error handling.
|
|
98
|
-
def with_error_handling(ctx)
|
|
99
|
-
yield
|
|
100
|
-
rescue StandardError => err
|
|
101
|
-
handler = find_handler_for(err.class)
|
|
102
|
-
if handler
|
|
103
|
-
runtime_info = ctx.namespace(:interpreter) || {}
|
|
104
|
-
obj = runtime_info[:current_object]
|
|
105
|
-
args = runtime_info[:current_arguments]
|
|
106
|
-
args = args && args.keyword_arguments
|
|
107
|
-
field = runtime_info[:current_field]
|
|
108
|
-
if obj.is_a?(GraphQL::Schema::Object)
|
|
109
|
-
obj = obj.object
|
|
110
|
-
end
|
|
111
|
-
handler[:handler].call(err, obj, args, ctx, field)
|
|
112
|
-
else
|
|
113
|
-
raise err
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
55
|
# @return [Proc, nil] The handler for `error_class`, if one was registered on this schema or inherited
|
|
118
|
-
def find_handler_for(error_class)
|
|
119
|
-
handlers =
|
|
56
|
+
def self.find_handler_for(schema, error_class)
|
|
57
|
+
handlers = schema.error_handlers[:subclass_handlers]
|
|
120
58
|
handler = nil
|
|
121
59
|
while (handlers) do
|
|
122
60
|
_err_class, next_handler = handlers.find { |err_class, handler| error_class <= err_class }
|
|
@@ -131,8 +69,8 @@ module GraphQL
|
|
|
131
69
|
end
|
|
132
70
|
|
|
133
71
|
# check for a handler from a parent class:
|
|
134
|
-
if
|
|
135
|
-
parent_handler =
|
|
72
|
+
if schema.superclass.respond_to?(:error_handlers)
|
|
73
|
+
parent_handler = find_handler_for(schema.superclass, error_class)
|
|
136
74
|
end
|
|
137
75
|
|
|
138
76
|
# If the inherited handler is more specific than the one defined here,
|
|
@@ -498,13 +498,17 @@ module GraphQL
|
|
|
498
498
|
field_result = call_method_on_directives(:resolve, object, directives) do
|
|
499
499
|
# Actually call the field resolver and capture the result
|
|
500
500
|
app_result = begin
|
|
501
|
-
query.
|
|
502
|
-
|
|
503
|
-
field_defn.resolve(object, kwarg_arguments, context)
|
|
504
|
-
end
|
|
501
|
+
query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments}) do
|
|
502
|
+
field_defn.resolve(object, kwarg_arguments, context)
|
|
505
503
|
end
|
|
506
504
|
rescue GraphQL::ExecutionError => err
|
|
507
505
|
err
|
|
506
|
+
rescue StandardError => err
|
|
507
|
+
begin
|
|
508
|
+
query.handle_or_reraise(err)
|
|
509
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
510
|
+
ex_err
|
|
511
|
+
end
|
|
508
512
|
end
|
|
509
513
|
after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
|
|
510
514
|
continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
|
@@ -871,21 +875,21 @@ module GraphQL
|
|
|
871
875
|
# Wrap the execution of _this_ method with tracing,
|
|
872
876
|
# but don't wrap the continuation below
|
|
873
877
|
inner_obj = begin
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
|
|
878
|
-
schema.sync_lazy(lazy_obj)
|
|
879
|
-
end
|
|
880
|
-
else
|
|
881
|
-
schema.sync_lazy(lazy_obj)
|
|
882
|
-
end
|
|
883
|
-
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
|
884
|
-
err
|
|
878
|
+
if trace
|
|
879
|
+
query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
|
|
880
|
+
schema.sync_lazy(lazy_obj)
|
|
885
881
|
end
|
|
882
|
+
else
|
|
883
|
+
schema.sync_lazy(lazy_obj)
|
|
886
884
|
end
|
|
887
|
-
rescue GraphQL::ExecutionError => ex_err
|
|
885
|
+
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err
|
|
888
886
|
ex_err
|
|
887
|
+
rescue StandardError => err
|
|
888
|
+
begin
|
|
889
|
+
query.handle_or_reraise(err)
|
|
890
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
891
|
+
ex_err
|
|
892
|
+
end
|
|
889
893
|
end
|
|
890
894
|
yield(inner_obj)
|
|
891
895
|
end
|
data/lib/graphql/query.rb
CHANGED
|
@@ -331,10 +331,8 @@ module GraphQL
|
|
|
331
331
|
end
|
|
332
332
|
|
|
333
333
|
# @api private
|
|
334
|
-
def
|
|
335
|
-
schema.
|
|
336
|
-
yield
|
|
337
|
-
end
|
|
334
|
+
def handle_or_reraise(err)
|
|
335
|
+
schema.handle_or_reraise(context, err)
|
|
338
336
|
end
|
|
339
337
|
|
|
340
338
|
private
|
|
@@ -31,25 +31,18 @@ module GraphQL
|
|
|
31
31
|
|
|
32
32
|
# @param collection [Object] The list of items to wrap in a connection
|
|
33
33
|
# @param item [Object] The newly-added item (will be wrapped in `edge_class`)
|
|
34
|
+
# @param context [GraphQL::Query::Context] The surrounding `ctx`, will be passed to the connection
|
|
34
35
|
# @param parent [Object] The owner of `collection`, will be passed to the connection if provided
|
|
35
|
-
# @param context [GraphQL::Query::Context] The surrounding `ctx`, will be passed to the connection if provided (this is required for cursor encoders)
|
|
36
36
|
# @param edge_class [Class] The class to wrap `item` with (defaults to the connection's edge class)
|
|
37
|
-
def initialize(collection:, item:, parent: nil,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@
|
|
44
|
-
@connection.range_add_edge(item)
|
|
45
|
-
else
|
|
46
|
-
@connection.edge_class.new(item, @connection)
|
|
47
|
-
end
|
|
37
|
+
def initialize(collection:, item:, context:, parent: nil, edge_class: nil)
|
|
38
|
+
conn_class = context.schema.connections.wrapper_for(collection)
|
|
39
|
+
# The rest will be added by ConnectionExtension
|
|
40
|
+
@connection = conn_class.new(collection, parent: parent, context: context, edge_class: edge_class)
|
|
41
|
+
# Check if this connection supports it, to support old versions of GraphQL-Pro
|
|
42
|
+
@edge = if @connection.respond_to?(:range_add_edge)
|
|
43
|
+
@connection.range_add_edge(item)
|
|
48
44
|
else
|
|
49
|
-
|
|
50
|
-
@connection = connection_class.new(collection, {}, parent: parent, context: context)
|
|
51
|
-
edge_class ||= Relay::Edge
|
|
52
|
-
@edge = edge_class.new(item, @connection)
|
|
45
|
+
@connection.edge_class.new(item, @connection)
|
|
53
46
|
end
|
|
54
47
|
|
|
55
48
|
@parent = parent
|
|
@@ -107,6 +107,11 @@ module GraphQL
|
|
|
107
107
|
if !pt.include?(owner) && owner.is_a?(Class)
|
|
108
108
|
pt << owner
|
|
109
109
|
end
|
|
110
|
+
int.interfaces.each do |indirect_int|
|
|
111
|
+
if indirect_int.is_a?(LateBoundType) && (indirect_int_type = get_type(indirect_int.graphql_name))
|
|
112
|
+
update_type_owner(owner, indirect_int_type)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
110
115
|
end
|
|
111
116
|
end
|
|
112
117
|
when nil
|
|
@@ -75,7 +75,10 @@ module GraphQL
|
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
if validates && !validates.empty?
|
|
79
|
+
self.validates(validates)
|
|
80
|
+
end
|
|
81
|
+
|
|
79
82
|
if required == :nullable
|
|
80
83
|
self.owner.validates(required: { argument: arg_name })
|
|
81
84
|
end
|
|
@@ -247,24 +250,30 @@ module GraphQL
|
|
|
247
250
|
end
|
|
248
251
|
|
|
249
252
|
loaded_value = nil
|
|
250
|
-
coerced_value =
|
|
253
|
+
coerced_value = begin
|
|
251
254
|
type.coerce_input(value, context)
|
|
255
|
+
rescue StandardError => err
|
|
256
|
+
context.schema.handle_or_reraise(context, err)
|
|
252
257
|
end
|
|
253
258
|
|
|
254
259
|
# If this isn't lazy, then the block returns eagerly and assigns the result here
|
|
255
260
|
# If it _is_ lazy, then we write the lazy to the hash, then update it later
|
|
256
261
|
argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value|
|
|
257
262
|
if loads && !from_resolver?
|
|
258
|
-
loaded_value =
|
|
263
|
+
loaded_value = begin
|
|
259
264
|
load_and_authorize_value(owner, coerced_value, context)
|
|
265
|
+
rescue StandardError => err
|
|
266
|
+
context.schema.handle_or_reraise(context, err)
|
|
260
267
|
end
|
|
261
268
|
end
|
|
262
269
|
|
|
263
270
|
maybe_loaded_value = loaded_value || resolved_coerced_value
|
|
264
271
|
context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value|
|
|
265
272
|
owner.validate_directive_argument(self, resolved_loaded_value)
|
|
266
|
-
prepared_value =
|
|
273
|
+
prepared_value = begin
|
|
267
274
|
prepare_value(parent_object, resolved_loaded_value, context: context)
|
|
275
|
+
rescue StandardError => err
|
|
276
|
+
context.schema.handle_or_reraise(context, err)
|
|
268
277
|
end
|
|
269
278
|
|
|
270
279
|
# TODO code smell to access such a deeply-nested constant in a distant module
|
data/lib/graphql/schema/field.rb
CHANGED
|
@@ -145,8 +145,18 @@ module GraphQL
|
|
|
145
145
|
if !@scope.nil?
|
|
146
146
|
# The default was overridden
|
|
147
147
|
@scope
|
|
148
|
+
elsif @return_type_expr
|
|
149
|
+
# Detect a list return type, but don't call `type` since that may eager-load an otherwise lazy-loaded type
|
|
150
|
+
@return_type_expr.is_a?(Array) ||
|
|
151
|
+
(@return_type_expr.is_a?(String) && @return_type_expr.include?("[")) ||
|
|
152
|
+
connection?
|
|
153
|
+
elsif @resolver_class
|
|
154
|
+
resolver_type = @resolver_class.type_expr
|
|
155
|
+
resolver_type.is_a?(Array) ||
|
|
156
|
+
(resolver_type.is_a?(String) && resolver_type.include?("[")) ||
|
|
157
|
+
connection?
|
|
148
158
|
else
|
|
149
|
-
|
|
159
|
+
false
|
|
150
160
|
end
|
|
151
161
|
end
|
|
152
162
|
|
|
@@ -177,7 +187,7 @@ module GraphQL
|
|
|
177
187
|
# @param name [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API)
|
|
178
188
|
# @param type [Class, GraphQL::BaseType, Array] The return type of this field
|
|
179
189
|
# @param owner [Class] The type that this field belongs to
|
|
180
|
-
# @param null [Boolean] `true` if this field may return `null`, `false` if it is never `null`
|
|
190
|
+
# @param null [Boolean] (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null`
|
|
181
191
|
# @param description [String] Field description
|
|
182
192
|
# @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
|
|
183
193
|
# @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
|
|
@@ -201,7 +211,7 @@ module GraphQL
|
|
|
201
211
|
# @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
|
|
202
212
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
|
203
213
|
# @param validates [Array<Hash>] Configurations for validating this field
|
|
204
|
-
def initialize(type: nil, name: nil, owner: nil, null:
|
|
214
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, description: :not_given, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, &definition_block)
|
|
205
215
|
if name.nil?
|
|
206
216
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
|
207
217
|
end
|
|
@@ -214,7 +224,9 @@ module GraphQL
|
|
|
214
224
|
name_s = -name.to_s
|
|
215
225
|
@underscored_name = -Member::BuildType.underscore(name_s)
|
|
216
226
|
@name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
|
|
217
|
-
|
|
227
|
+
if description != :not_given
|
|
228
|
+
@description = description
|
|
229
|
+
end
|
|
218
230
|
self.deprecation_reason = deprecation_reason
|
|
219
231
|
|
|
220
232
|
if method && hash_key && dig
|
|
@@ -241,7 +253,13 @@ module GraphQL
|
|
|
241
253
|
@resolver_method = resolver_method
|
|
242
254
|
@complexity = complexity
|
|
243
255
|
@return_type_expr = type
|
|
244
|
-
@return_type_null = null
|
|
256
|
+
@return_type_null = if !null.nil?
|
|
257
|
+
null
|
|
258
|
+
elsif resolver_class
|
|
259
|
+
nil
|
|
260
|
+
else
|
|
261
|
+
true
|
|
262
|
+
end
|
|
245
263
|
@connection = connection
|
|
246
264
|
@has_max_page_size = max_page_size != :not_given
|
|
247
265
|
@max_page_size = max_page_size == :not_given ? nil : max_page_size
|
|
@@ -303,7 +321,9 @@ module GraphQL
|
|
|
303
321
|
end
|
|
304
322
|
end
|
|
305
323
|
|
|
306
|
-
|
|
324
|
+
if !validates.empty?
|
|
325
|
+
self.validates(validates)
|
|
326
|
+
end
|
|
307
327
|
|
|
308
328
|
if definition_block
|
|
309
329
|
if definition_block.arity == 1
|
|
@@ -335,10 +355,12 @@ module GraphQL
|
|
|
335
355
|
def description(text = nil)
|
|
336
356
|
if text
|
|
337
357
|
@description = text
|
|
358
|
+
elsif defined?(@description)
|
|
359
|
+
@description
|
|
338
360
|
elsif @resolver_class
|
|
339
361
|
@description || @resolver_class.description
|
|
340
362
|
else
|
|
341
|
-
|
|
363
|
+
nil
|
|
342
364
|
end
|
|
343
365
|
end
|
|
344
366
|
|
|
@@ -514,19 +536,15 @@ module GraphQL
|
|
|
514
536
|
attr_writer :type
|
|
515
537
|
|
|
516
538
|
def type
|
|
517
|
-
if @resolver_class
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
# Not enough info to determine type
|
|
522
|
-
message = "Can't determine the return type for #{self.path}"
|
|
523
|
-
if @resolver_class
|
|
524
|
-
message += " (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
|
|
525
|
-
end
|
|
526
|
-
raise MissingReturnTypeError, message
|
|
527
|
-
else
|
|
528
|
-
Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
|
|
539
|
+
if @resolver_class
|
|
540
|
+
return_type = @return_type_expr || @resolver_class.type_expr
|
|
541
|
+
if return_type.nil?
|
|
542
|
+
raise MissingReturnTypeError, "Can't determine the return type for #{self.path} (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
|
|
529
543
|
end
|
|
544
|
+
nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null
|
|
545
|
+
Member::BuildType.parse_type(return_type, null: nullable)
|
|
546
|
+
else
|
|
547
|
+
@type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
|
|
530
548
|
end
|
|
531
549
|
rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
|
|
532
550
|
# Let this propagate up
|
|
@@ -597,31 +615,94 @@ module GraphQL
|
|
|
597
615
|
# @param object [GraphQL::Schema::Object] An instance of some type class, wrapping an application object
|
|
598
616
|
# @param args [Hash] A symbol-keyed hash of Ruby keyword arguments. (Empty if no args)
|
|
599
617
|
# @param ctx [GraphQL::Query::Context]
|
|
600
|
-
def resolve(object, args,
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
618
|
+
def resolve(object, args, query_ctx)
|
|
619
|
+
# Unwrap the GraphQL object to get the application object.
|
|
620
|
+
application_object = object.object
|
|
621
|
+
method_receiver = nil
|
|
622
|
+
method_to_call = nil
|
|
623
|
+
method_args = nil
|
|
624
|
+
|
|
625
|
+
Schema::Validator.validate!(validators, application_object, query_ctx, args)
|
|
626
|
+
|
|
627
|
+
query_ctx.schema.after_lazy(self.authorized?(application_object, args, query_ctx)) do |is_authorized|
|
|
628
|
+
if is_authorized
|
|
629
|
+
with_extensions(object, args, query_ctx) do |obj, ruby_kwargs|
|
|
630
|
+
method_args = ruby_kwargs
|
|
631
|
+
if @resolver_class
|
|
632
|
+
if obj.is_a?(GraphQL::Schema::Object)
|
|
633
|
+
obj = obj.object
|
|
634
|
+
end
|
|
635
|
+
obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
|
|
636
|
+
end
|
|
607
637
|
|
|
608
|
-
|
|
638
|
+
# Find a way to resolve this field, checking:
|
|
639
|
+
#
|
|
640
|
+
# - A method on the type instance;
|
|
641
|
+
# - Hash keys, if the wrapped object is a hash;
|
|
642
|
+
# - A method on the wrapped object;
|
|
643
|
+
# - Or, raise not implemented.
|
|
644
|
+
#
|
|
645
|
+
if obj.respond_to?(resolver_method)
|
|
646
|
+
method_to_call = resolver_method
|
|
647
|
+
method_receiver = obj
|
|
648
|
+
# Call the method with kwargs, if there are any
|
|
649
|
+
if ruby_kwargs.any?
|
|
650
|
+
obj.public_send(resolver_method, **ruby_kwargs)
|
|
651
|
+
else
|
|
652
|
+
obj.public_send(resolver_method)
|
|
653
|
+
end
|
|
654
|
+
elsif obj.object.is_a?(Hash)
|
|
655
|
+
inner_object = obj.object
|
|
656
|
+
if @dig_keys
|
|
657
|
+
inner_object.dig(*@dig_keys)
|
|
658
|
+
elsif inner_object.key?(@method_sym)
|
|
659
|
+
inner_object[@method_sym]
|
|
660
|
+
else
|
|
661
|
+
inner_object[@method_str]
|
|
662
|
+
end
|
|
663
|
+
elsif obj.object.respond_to?(@method_sym)
|
|
664
|
+
method_to_call = @method_sym
|
|
665
|
+
method_receiver = obj.object
|
|
666
|
+
if ruby_kwargs.any?
|
|
667
|
+
obj.object.public_send(@method_sym, **ruby_kwargs)
|
|
668
|
+
else
|
|
669
|
+
obj.object.public_send(@method_sym)
|
|
670
|
+
end
|
|
671
|
+
else
|
|
672
|
+
raise <<-ERR
|
|
673
|
+
Failed to implement #{@owner.graphql_name}.#{@name}, tried:
|
|
609
674
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
675
|
+
- `#{obj.class}##{resolver_method}`, which did not exist
|
|
676
|
+
- `#{obj.object.class}##{@method_sym}`, which did not exist
|
|
677
|
+
- Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
|
|
678
|
+
|
|
679
|
+
To implement this field, define one of the methods above (and check for typos)
|
|
680
|
+
ERR
|
|
681
|
+
end
|
|
615
682
|
end
|
|
683
|
+
else
|
|
684
|
+
raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: query_ctx, field: self)
|
|
616
685
|
end
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
686
|
+
end
|
|
687
|
+
rescue GraphQL::UnauthorizedFieldError => err
|
|
688
|
+
err.field ||= self
|
|
689
|
+
begin
|
|
690
|
+
query_ctx.schema.unauthorized_field(err)
|
|
691
|
+
rescue GraphQL::ExecutionError => err
|
|
692
|
+
err
|
|
693
|
+
end
|
|
694
|
+
rescue GraphQL::UnauthorizedError => err
|
|
695
|
+
begin
|
|
696
|
+
query_ctx.schema.unauthorized_object(err)
|
|
697
|
+
rescue GraphQL::ExecutionError => err
|
|
698
|
+
err
|
|
699
|
+
end
|
|
700
|
+
rescue ArgumentError
|
|
701
|
+
if method_receiver && method_to_call
|
|
702
|
+
assert_satisfactory_implementation(method_receiver, method_to_call, method_args)
|
|
703
|
+
end
|
|
704
|
+
# if the line above doesn't raise, re-raise
|
|
705
|
+
raise
|
|
625
706
|
end
|
|
626
707
|
|
|
627
708
|
# @param ctx [GraphQL::Query::Context]
|
|
@@ -637,70 +718,6 @@ module GraphQL
|
|
|
637
718
|
|
|
638
719
|
private
|
|
639
720
|
|
|
640
|
-
def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
|
|
641
|
-
with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
|
|
642
|
-
begin
|
|
643
|
-
method_receiver = nil
|
|
644
|
-
method_to_call = nil
|
|
645
|
-
if @resolver_class
|
|
646
|
-
if obj.is_a?(GraphQL::Schema::Object)
|
|
647
|
-
obj = obj.object
|
|
648
|
-
end
|
|
649
|
-
obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
|
|
650
|
-
end
|
|
651
|
-
|
|
652
|
-
# Find a way to resolve this field, checking:
|
|
653
|
-
#
|
|
654
|
-
# - A method on the type instance;
|
|
655
|
-
# - Hash keys, if the wrapped object is a hash;
|
|
656
|
-
# - A method on the wrapped object;
|
|
657
|
-
# - Or, raise not implemented.
|
|
658
|
-
#
|
|
659
|
-
if obj.respond_to?(resolver_method)
|
|
660
|
-
method_to_call = resolver_method
|
|
661
|
-
method_receiver = obj
|
|
662
|
-
# Call the method with kwargs, if there are any
|
|
663
|
-
if ruby_kwargs.any?
|
|
664
|
-
obj.public_send(resolver_method, **ruby_kwargs)
|
|
665
|
-
else
|
|
666
|
-
obj.public_send(resolver_method)
|
|
667
|
-
end
|
|
668
|
-
elsif obj.object.is_a?(Hash)
|
|
669
|
-
inner_object = obj.object
|
|
670
|
-
if @dig_keys
|
|
671
|
-
inner_object.dig(*@dig_keys)
|
|
672
|
-
elsif inner_object.key?(@method_sym)
|
|
673
|
-
inner_object[@method_sym]
|
|
674
|
-
else
|
|
675
|
-
inner_object[@method_str]
|
|
676
|
-
end
|
|
677
|
-
elsif obj.object.respond_to?(@method_sym)
|
|
678
|
-
method_to_call = @method_sym
|
|
679
|
-
method_receiver = obj.object
|
|
680
|
-
if ruby_kwargs.any?
|
|
681
|
-
obj.object.public_send(@method_sym, **ruby_kwargs)
|
|
682
|
-
else
|
|
683
|
-
obj.object.public_send(@method_sym)
|
|
684
|
-
end
|
|
685
|
-
else
|
|
686
|
-
raise <<-ERR
|
|
687
|
-
Failed to implement #{@owner.graphql_name}.#{@name}, tried:
|
|
688
|
-
|
|
689
|
-
- `#{obj.class}##{resolver_method}`, which did not exist
|
|
690
|
-
- `#{obj.object.class}##{@method_sym}`, which did not exist
|
|
691
|
-
- Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
|
|
692
|
-
|
|
693
|
-
To implement this field, define one of the methods above (and check for typos)
|
|
694
|
-
ERR
|
|
695
|
-
end
|
|
696
|
-
rescue ArgumentError
|
|
697
|
-
assert_satisfactory_implementation(method_receiver, method_to_call, ruby_kwargs)
|
|
698
|
-
# if the line above doesn't raise, re-raise
|
|
699
|
-
raise
|
|
700
|
-
end
|
|
701
|
-
end
|
|
702
|
-
end
|
|
703
|
-
|
|
704
721
|
def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
|
|
705
722
|
method_defn = receiver.method(method_name)
|
|
706
723
|
unsatisfied_ruby_kwargs = ruby_kwargs.dup
|
|
@@ -8,7 +8,15 @@ module GraphQL
|
|
|
8
8
|
if new_edge_type_class
|
|
9
9
|
@edge_type_class = new_edge_type_class
|
|
10
10
|
else
|
|
11
|
-
|
|
11
|
+
# Don't call `ancestor.edge_type_class`
|
|
12
|
+
# because we don't want a fallback from any ancestors --
|
|
13
|
+
# only apply the fallback if _no_ ancestor has a configured value!
|
|
14
|
+
for ancestor in self.ancestors
|
|
15
|
+
if ancestor.respond_to?(:configured_edge_type_class, true) && (etc = ancestor.configured_edge_type_class)
|
|
16
|
+
return etc
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
Types::Relay::BaseEdge
|
|
12
20
|
end
|
|
13
21
|
end
|
|
14
22
|
|
|
@@ -16,7 +24,15 @@ module GraphQL
|
|
|
16
24
|
if new_connection_type_class
|
|
17
25
|
@connection_type_class = new_connection_type_class
|
|
18
26
|
else
|
|
19
|
-
|
|
27
|
+
# Don't call `ancestor.connection_type_class`
|
|
28
|
+
# because we don't want a fallback from any ancestors --
|
|
29
|
+
# only apply the fallback if _no_ ancestor has a configured value!
|
|
30
|
+
for ancestor in self.ancestors
|
|
31
|
+
if ancestor.respond_to?(:configured_connection_type_class, true) && (ctc = ancestor.configured_connection_type_class)
|
|
32
|
+
return ctc
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
Types::Relay::BaseConnection
|
|
20
36
|
end
|
|
21
37
|
end
|
|
22
38
|
|
|
@@ -41,6 +57,16 @@ module GraphQL
|
|
|
41
57
|
end
|
|
42
58
|
end
|
|
43
59
|
end
|
|
60
|
+
|
|
61
|
+
protected
|
|
62
|
+
|
|
63
|
+
def configured_connection_type_class
|
|
64
|
+
@connection_type_class
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def configured_edge_type_class
|
|
68
|
+
@edge_type_class
|
|
69
|
+
end
|
|
44
70
|
end
|
|
45
71
|
end
|
|
46
72
|
end
|
|
@@ -51,12 +51,12 @@ module GraphQL
|
|
|
51
51
|
trace_payload = { context: context, type: self, object: object, path: context[:current_path] }
|
|
52
52
|
|
|
53
53
|
maybe_lazy_auth_val = context.query.trace("authorized", trace_payload) do
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
begin
|
|
55
|
+
authorized?(object, context)
|
|
56
|
+
rescue GraphQL::UnauthorizedError => err
|
|
57
|
+
context.schema.unauthorized_object(err)
|
|
58
|
+
rescue StandardError => err
|
|
59
|
+
context.query.handle_or_reraise(err)
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
@@ -98,9 +98,14 @@ module GraphQL
|
|
|
98
98
|
class << self
|
|
99
99
|
# Set up a type-specific invalid null error to use when this object's non-null fields wrongly return `nil`.
|
|
100
100
|
# It should help with debugging and bug tracker integrations.
|
|
101
|
-
def
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
def const_missing(name)
|
|
102
|
+
if name == :InvalidNullError
|
|
103
|
+
custom_err_class = GraphQL::InvalidNullError.subclass_for(self)
|
|
104
|
+
const_set(:InvalidNullError, custom_err_class)
|
|
105
|
+
custom_err_class
|
|
106
|
+
else
|
|
107
|
+
super
|
|
108
|
+
end
|
|
104
109
|
end
|
|
105
110
|
|
|
106
111
|
def kind
|
|
@@ -20,7 +20,17 @@ module GraphQL
|
|
|
20
20
|
@payload_type ||= generate_payload_type
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
def type(new_type = nil, null: nil)
|
|
24
|
+
if new_type
|
|
25
|
+
payload_type(new_type)
|
|
26
|
+
if !null.nil?
|
|
27
|
+
self.null(null)
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
super()
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
24
34
|
alias :type_expr :payload_type
|
|
25
35
|
|
|
26
36
|
def field_class(new_class = nil)
|
|
@@ -42,11 +42,11 @@ module GraphQL
|
|
|
42
42
|
|
|
43
43
|
def validate_non_null_input(value, ctx)
|
|
44
44
|
coerced_result = begin
|
|
45
|
-
ctx
|
|
46
|
-
coerce_input(value, ctx)
|
|
47
|
-
end
|
|
45
|
+
coerce_input(value, ctx)
|
|
48
46
|
rescue GraphQL::CoercionError => err
|
|
49
47
|
err
|
|
48
|
+
rescue StandardError => err
|
|
49
|
+
ctx.query.handle_or_reraise(err)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
if coerced_result.nil?
|
data/lib/graphql/schema.rb
CHANGED
|
@@ -693,7 +693,41 @@ module GraphQL
|
|
|
693
693
|
|
|
694
694
|
def rescue_from(*err_classes, &handler_block)
|
|
695
695
|
err_classes.each do |err_class|
|
|
696
|
-
|
|
696
|
+
Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block)
|
|
697
|
+
end
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
NEW_HANDLER_HASH = ->(h, k) {
|
|
701
|
+
h[k] = {
|
|
702
|
+
class: k,
|
|
703
|
+
handler: nil,
|
|
704
|
+
subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
def error_handlers
|
|
709
|
+
@error_handlers ||= {
|
|
710
|
+
class: nil,
|
|
711
|
+
handler: nil,
|
|
712
|
+
subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
|
|
713
|
+
}
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
# @api private
|
|
717
|
+
def handle_or_reraise(context, err)
|
|
718
|
+
handler = Execution::Errors.find_handler_for(self, err.class)
|
|
719
|
+
if handler
|
|
720
|
+
runtime_info = context.namespace(:interpreter) || {}
|
|
721
|
+
obj = runtime_info[:current_object]
|
|
722
|
+
args = runtime_info[:current_arguments]
|
|
723
|
+
args = args && args.keyword_arguments
|
|
724
|
+
field = runtime_info[:current_field]
|
|
725
|
+
if obj.is_a?(GraphQL::Schema::Object)
|
|
726
|
+
obj = obj.object
|
|
727
|
+
end
|
|
728
|
+
handler[:handler].call(err, obj, args, context, field)
|
|
729
|
+
else
|
|
730
|
+
raise err
|
|
697
731
|
end
|
|
698
732
|
end
|
|
699
733
|
|
|
@@ -821,11 +855,6 @@ module GraphQL
|
|
|
821
855
|
ctx.errors.push(parse_err)
|
|
822
856
|
end
|
|
823
857
|
|
|
824
|
-
# @return [GraphQL::Execution::Errors]
|
|
825
|
-
def error_handler
|
|
826
|
-
@error_handler ||= GraphQL::Execution::Errors.new(self)
|
|
827
|
-
end
|
|
828
|
-
|
|
829
858
|
def lazy_resolve(lazy_class, value_method)
|
|
830
859
|
lazy_methods.set(lazy_class, value_method)
|
|
831
860
|
end
|
|
@@ -148,22 +148,6 @@ module GraphQL
|
|
|
148
148
|
obj_type.field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination."
|
|
149
149
|
end
|
|
150
150
|
end
|
|
151
|
-
|
|
152
|
-
# By default this calls through to the ConnectionWrapper's edge nodes method,
|
|
153
|
-
# but sometimes you need to override it to support the `nodes` field
|
|
154
|
-
def nodes
|
|
155
|
-
@object.edge_nodes
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def edges
|
|
159
|
-
if @object.is_a?(GraphQL::Pagination::Connection)
|
|
160
|
-
@object.edges
|
|
161
|
-
else
|
|
162
|
-
context.schema.after_lazy(object.edge_nodes) do |nodes|
|
|
163
|
-
nodes.map { |n| self.class.edge_class.new(n, object) }
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
151
|
end
|
|
168
152
|
end
|
|
169
153
|
end
|
data/lib/graphql/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graphql
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Mosolgo
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-
|
|
11
|
+
date: 2022-03-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: benchmark-ips
|
|
@@ -581,7 +581,7 @@ metadata:
|
|
|
581
581
|
source_code_uri: https://github.com/rmosolgo/graphql-ruby
|
|
582
582
|
bug_tracker_uri: https://github.com/rmosolgo/graphql-ruby/issues
|
|
583
583
|
mailing_list_uri: https://tinyletter.com/graphql-ruby
|
|
584
|
-
post_install_message:
|
|
584
|
+
post_install_message:
|
|
585
585
|
rdoc_options: []
|
|
586
586
|
require_paths:
|
|
587
587
|
- lib
|
|
@@ -596,8 +596,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
596
596
|
- !ruby/object:Gem::Version
|
|
597
597
|
version: '0'
|
|
598
598
|
requirements: []
|
|
599
|
-
rubygems_version: 3.
|
|
600
|
-
signing_key:
|
|
599
|
+
rubygems_version: 3.2.32
|
|
600
|
+
signing_key:
|
|
601
601
|
specification_version: 4
|
|
602
602
|
summary: A GraphQL language and runtime for Ruby
|
|
603
603
|
test_files: []
|