graphql 1.11.1 → 1.11.6
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/core.rb +8 -0
- data/lib/generators/graphql/templates/base_argument.erb +2 -0
- data/lib/generators/graphql/templates/base_enum.erb +2 -0
- data/lib/generators/graphql/templates/base_field.erb +2 -0
- data/lib/generators/graphql/templates/base_input_object.erb +2 -0
- data/lib/generators/graphql/templates/base_interface.erb +2 -0
- data/lib/generators/graphql/templates/base_mutation.erb +2 -0
- data/lib/generators/graphql/templates/base_object.erb +2 -0
- data/lib/generators/graphql/templates/base_scalar.erb +2 -0
- data/lib/generators/graphql/templates/base_union.erb +2 -0
- data/lib/generators/graphql/templates/enum.erb +2 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +13 -9
- data/lib/generators/graphql/templates/interface.erb +2 -0
- data/lib/generators/graphql/templates/loader.erb +2 -0
- data/lib/generators/graphql/templates/mutation.erb +2 -0
- data/lib/generators/graphql/templates/mutation_type.erb +2 -0
- data/lib/generators/graphql/templates/object.erb +2 -0
- data/lib/generators/graphql/templates/query_type.erb +2 -0
- data/lib/generators/graphql/templates/scalar.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +2 -0
- data/lib/generators/graphql/templates/union.erb +3 -1
- data/lib/graphql.rb +16 -0
- data/lib/graphql/argument.rb +3 -3
- data/lib/graphql/backtrace/tracer.rb +2 -1
- data/lib/graphql/define/assign_global_id_field.rb +2 -2
- data/lib/graphql/directive.rb +4 -0
- data/lib/graphql/execution/interpreter.rb +10 -0
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/runtime.rb +59 -45
- data/lib/graphql/field.rb +4 -0
- data/lib/graphql/input_object_type.rb +4 -0
- data/lib/graphql/introspection.rb +96 -0
- data/lib/graphql/introspection/field_type.rb +7 -3
- data/lib/graphql/introspection/input_value_type.rb +6 -0
- data/lib/graphql/introspection/introspection_query.rb +6 -92
- data/lib/graphql/introspection/type_type.rb +7 -3
- data/lib/graphql/language/block_string.rb +24 -5
- data/lib/graphql/language/lexer.rb +7 -3
- data/lib/graphql/language/lexer.rl +7 -3
- data/lib/graphql/language/nodes.rb +2 -1
- data/lib/graphql/language/parser.rb +107 -103
- data/lib/graphql/language/parser.y +4 -0
- data/lib/graphql/language/sanitized_printer.rb +59 -26
- data/lib/graphql/language/visitor.rb +2 -2
- data/lib/graphql/name_validator.rb +6 -7
- data/lib/graphql/pagination/connection.rb +6 -8
- data/lib/graphql/pagination/connections.rb +23 -3
- data/lib/graphql/query.rb +2 -2
- data/lib/graphql/query/context.rb +30 -3
- data/lib/graphql/query/fingerprint.rb +2 -0
- data/lib/graphql/query/validation_pipeline.rb +3 -0
- data/lib/graphql/relay/range_add.rb +14 -5
- data/lib/graphql/schema.rb +40 -31
- data/lib/graphql/schema/argument.rb +56 -5
- data/lib/graphql/schema/build_from_definition.rb +67 -38
- data/lib/graphql/schema/build_from_definition/resolve_map.rb +3 -1
- data/lib/graphql/schema/directive/deprecated.rb +1 -1
- data/lib/graphql/schema/enum_value.rb +1 -0
- data/lib/graphql/schema/field.rb +17 -10
- data/lib/graphql/schema/field/connection_extension.rb +44 -34
- data/lib/graphql/schema/input_object.rb +21 -18
- data/lib/graphql/schema/interface.rb +1 -1
- data/lib/graphql/schema/late_bound_type.rb +2 -2
- data/lib/graphql/schema/loader.rb +20 -1
- data/lib/graphql/schema/member/build_type.rb +14 -4
- data/lib/graphql/schema/member/has_arguments.rb +19 -1
- data/lib/graphql/schema/member/has_fields.rb +17 -7
- data/lib/graphql/schema/member/type_system_helpers.rb +2 -2
- data/lib/graphql/schema/mutation.rb +4 -0
- data/lib/graphql/schema/relay_classic_mutation.rb +3 -1
- data/lib/graphql/schema/resolver.rb +6 -0
- data/lib/graphql/schema/resolver/has_payload_type.rb +2 -1
- data/lib/graphql/schema/subscription.rb +2 -12
- data/lib/graphql/schema/timeout.rb +29 -15
- data/lib/graphql/schema/union.rb +29 -0
- data/lib/graphql/schema/unique_within_type.rb +1 -2
- data/lib/graphql/schema/validation.rb +8 -0
- data/lib/graphql/schema/warden.rb +8 -3
- data/lib/graphql/static_validation/literal_validator.rb +7 -7
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -2
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +4 -2
- data/lib/graphql/static_validation/validator.rb +7 -4
- data/lib/graphql/subscriptions.rb +32 -22
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +45 -20
- data/lib/graphql/subscriptions/serialize.rb +22 -4
- data/lib/graphql/tracing/appoptics_tracing.rb +10 -2
- data/lib/graphql/types/iso_8601_date_time.rb +2 -1
- data/lib/graphql/types/relay/base_connection.rb +6 -5
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- metadata +3 -3
@@ -126,9 +126,11 @@ module GraphQL
|
|
126
126
|
argument_defn = super(*args, **kwargs, &block)
|
127
127
|
# Add a method access
|
128
128
|
method_name = argument_defn.keyword
|
129
|
-
|
130
|
-
|
131
|
-
|
129
|
+
class_eval <<-RUBY, __FILE__, __LINE__
|
130
|
+
def #{method_name}
|
131
|
+
self[#{method_name.inspect}]
|
132
|
+
end
|
133
|
+
RUBY
|
132
134
|
end
|
133
135
|
|
134
136
|
def to_graphql
|
@@ -166,10 +168,7 @@ module GraphQL
|
|
166
168
|
return result
|
167
169
|
end
|
168
170
|
|
169
|
-
|
170
|
-
# using these methods to make sure that the object will
|
171
|
-
# behave like a hash below, when we call `each` on it.
|
172
|
-
begin
|
171
|
+
input = begin
|
173
172
|
input.to_h
|
174
173
|
rescue
|
175
174
|
begin
|
@@ -182,21 +181,25 @@ module GraphQL
|
|
182
181
|
end
|
183
182
|
end
|
184
183
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
if visible_arguments_map[name].nil?
|
190
|
-
result.add_problem("Field is not defined on #{self.graphql_name}", [name])
|
184
|
+
# Inject missing required arguments
|
185
|
+
missing_required_inputs = self.arguments.reduce({}) do |m, (argument_name, argument)|
|
186
|
+
if !input.key?(argument_name) && argument.type.non_null? && warden.get_argument(self, argument_name)
|
187
|
+
m[argument_name] = nil
|
191
188
|
end
|
189
|
+
|
190
|
+
m
|
192
191
|
end
|
193
192
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
result.
|
193
|
+
input.merge(missing_required_inputs).each do |argument_name, value|
|
194
|
+
argument = warden.get_argument(self, argument_name)
|
195
|
+
# Items in the input that are unexpected
|
196
|
+
unless argument
|
197
|
+
result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
|
198
|
+
next
|
199
199
|
end
|
200
|
+
# Items in the input that are expected, but have invalid values
|
201
|
+
argument_result = argument.type.validate_input(value, ctx)
|
202
|
+
result.merge_result!(argument_name, argument_result) unless argument_result.valid?
|
200
203
|
end
|
201
204
|
|
202
205
|
result
|
@@ -30,7 +30,7 @@ module GraphQL
|
|
30
30
|
|
31
31
|
# The interface is accessible if any of its possible types are accessible
|
32
32
|
def accessible?(context)
|
33
|
-
context.schema.possible_types(self).each do |type|
|
33
|
+
context.schema.possible_types(self, context).each do |type|
|
34
34
|
if context.schema.accessible?(type, context)
|
35
35
|
return true
|
36
36
|
end
|
@@ -16,11 +16,11 @@ module GraphQL
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def to_non_null_type
|
19
|
-
GraphQL::NonNullType.new(of_type: self)
|
19
|
+
@to_non_null_type ||= GraphQL::NonNullType.new(of_type: self)
|
20
20
|
end
|
21
21
|
|
22
22
|
def to_list_type
|
23
|
-
GraphQL::ListType.new(of_type: self)
|
23
|
+
@to_list_type ||= GraphQL::ListType.new(of_type: self)
|
24
24
|
end
|
25
25
|
|
26
26
|
def inspect
|
@@ -25,8 +25,15 @@ module GraphQL
|
|
25
25
|
types[type["name"]] = type_object
|
26
26
|
end
|
27
27
|
|
28
|
+
directives = []
|
29
|
+
schema.fetch("directives", []).each do |directive|
|
30
|
+
next if GraphQL::Schema.default_directives.include?(directive.fetch("name"))
|
31
|
+
directives << define_directive(directive, type_resolver)
|
32
|
+
end
|
33
|
+
|
28
34
|
Class.new(GraphQL::Schema) do
|
29
35
|
orphan_types(types.values)
|
36
|
+
directives(directives)
|
30
37
|
|
31
38
|
def self.resolve_type(*)
|
32
39
|
raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects")
|
@@ -98,7 +105,7 @@ module GraphQL
|
|
98
105
|
value(
|
99
106
|
enum_value["name"],
|
100
107
|
description: enum_value["description"],
|
101
|
-
deprecation_reason: enum_value["
|
108
|
+
deprecation_reason: enum_value["deprecationReason"],
|
102
109
|
)
|
103
110
|
end
|
104
111
|
end
|
@@ -147,6 +154,16 @@ module GraphQL
|
|
147
154
|
end
|
148
155
|
end
|
149
156
|
|
157
|
+
def define_directive(directive, type_resolver)
|
158
|
+
loader = self
|
159
|
+
Class.new(GraphQL::Schema::Directive) do
|
160
|
+
graphql_name(directive["name"])
|
161
|
+
description(directive["description"])
|
162
|
+
locations(*directive["locations"].map(&:to_sym))
|
163
|
+
loader.build_arguments(self, directive["args"], type_resolver)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
150
167
|
public
|
151
168
|
|
152
169
|
def build_fields(type_defn, fields, type_resolver)
|
@@ -156,6 +173,7 @@ module GraphQL
|
|
156
173
|
field_hash["name"],
|
157
174
|
type: type_resolver.call(field_hash["type"]),
|
158
175
|
description: field_hash["description"],
|
176
|
+
deprecation_reason: field_hash["deprecationReason"],
|
159
177
|
null: true,
|
160
178
|
camelize: false,
|
161
179
|
) do
|
@@ -171,6 +189,7 @@ module GraphQL
|
|
171
189
|
kwargs = {
|
172
190
|
type: type_resolver.call(arg["type"]),
|
173
191
|
description: arg["description"],
|
192
|
+
deprecation_reason: arg["deprecationReason"],
|
174
193
|
required: false,
|
175
194
|
method_access: false,
|
176
195
|
camelize: false,
|
@@ -4,6 +4,10 @@ module GraphQL
|
|
4
4
|
class Member
|
5
5
|
# @api private
|
6
6
|
module BuildType
|
7
|
+
if !String.method_defined?(:match?)
|
8
|
+
using GraphQL::StringMatchBackport
|
9
|
+
end
|
10
|
+
|
7
11
|
LIST_TYPE_ERROR = "Use an array of [T] or [T, null: true] for list types; other arrays are not supported"
|
8
12
|
|
9
13
|
module_function
|
@@ -162,10 +166,16 @@ module GraphQL
|
|
162
166
|
end
|
163
167
|
|
164
168
|
def underscore(string)
|
165
|
-
string
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
+
if string.match?(/\A[a-z_]+\Z/)
|
170
|
+
return string
|
171
|
+
end
|
172
|
+
string2 = string.dup
|
173
|
+
|
174
|
+
string2.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder
|
175
|
+
string2.gsub!(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing
|
176
|
+
string2.downcase!
|
177
|
+
|
178
|
+
string2
|
169
179
|
end
|
170
180
|
end
|
171
181
|
end
|
@@ -43,6 +43,7 @@ module GraphQL
|
|
43
43
|
# @param arg_defn [GraphQL::Schema::Argument]
|
44
44
|
# @return [GraphQL::Schema::Argument]
|
45
45
|
def add_argument(arg_defn)
|
46
|
+
@own_arguments ||= {}
|
46
47
|
own_arguments[arg_defn.name] = arg_defn
|
47
48
|
arg_defn
|
48
49
|
end
|
@@ -58,6 +59,22 @@ module GraphQL
|
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
62
|
+
# @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
|
63
|
+
def get_argument(argument_name)
|
64
|
+
a = own_arguments[argument_name]
|
65
|
+
|
66
|
+
if a || !self.is_a?(Class)
|
67
|
+
a
|
68
|
+
else
|
69
|
+
for ancestor in ancestors
|
70
|
+
if ancestor.respond_to?(:own_arguments) && a = ancestor.own_arguments[argument_name]
|
71
|
+
return a
|
72
|
+
end
|
73
|
+
end
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
61
78
|
# @param new_arg_class [Class] A class to use for building argument definitions
|
62
79
|
def argument_class(new_arg_class = nil)
|
63
80
|
self.class.argument_class(new_arg_class)
|
@@ -213,8 +230,9 @@ module GraphQL
|
|
213
230
|
end
|
214
231
|
end
|
215
232
|
|
233
|
+
NO_ARGUMENTS = {}.freeze
|
216
234
|
def own_arguments
|
217
|
-
@own_arguments
|
235
|
+
@own_arguments || NO_ARGUMENTS
|
218
236
|
end
|
219
237
|
end
|
220
238
|
end
|
@@ -47,20 +47,22 @@ module GraphQL
|
|
47
47
|
# A list of GraphQL-Ruby keywords.
|
48
48
|
#
|
49
49
|
# @api private
|
50
|
-
GRAPHQL_RUBY_KEYWORDS = [:context, :object, :
|
50
|
+
GRAPHQL_RUBY_KEYWORDS = [:context, :object, :raw_value]
|
51
51
|
|
52
52
|
# A list of field names that we should advise users to pick a different
|
53
53
|
# resolve method name.
|
54
54
|
#
|
55
55
|
# @api private
|
56
|
-
CONFLICT_FIELD_NAMES = Set.new(GRAPHQL_RUBY_KEYWORDS + RUBY_KEYWORDS)
|
56
|
+
CONFLICT_FIELD_NAMES = Set.new(GRAPHQL_RUBY_KEYWORDS + RUBY_KEYWORDS + Object.instance_methods)
|
57
57
|
|
58
58
|
# Register this field with the class, overriding a previous one if needed.
|
59
59
|
# @param field_defn [GraphQL::Schema::Field]
|
60
60
|
# @return [void]
|
61
|
-
def add_field(field_defn)
|
62
|
-
|
63
|
-
|
61
|
+
def add_field(field_defn, method_conflict_warning: field_defn.method_conflict_warning?)
|
62
|
+
# Check that `field_defn.original_name` equals `resolver_method` and `method_sym` --
|
63
|
+
# that shows that no override value was given manually.
|
64
|
+
if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym
|
65
|
+
warn(conflict_field_name_warning(field_defn))
|
64
66
|
end
|
65
67
|
own_fields[field_defn.name] = field_defn
|
66
68
|
nil
|
@@ -80,9 +82,9 @@ module GraphQL
|
|
80
82
|
end
|
81
83
|
end
|
82
84
|
|
83
|
-
def global_id_field(field_name)
|
85
|
+
def global_id_field(field_name, **kwargs)
|
84
86
|
id_resolver = GraphQL::Relay::GlobalIdResolve.new(type: self)
|
85
|
-
field field_name, "ID", null: false
|
87
|
+
field field_name, "ID", **kwargs, null: false
|
86
88
|
define_method(field_name) do
|
87
89
|
id_resolver.call(object, {}, context)
|
88
90
|
end
|
@@ -92,6 +94,14 @@ module GraphQL
|
|
92
94
|
def own_fields
|
93
95
|
@own_fields ||= {}
|
94
96
|
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# @param [GraphQL::Schema::Field]
|
101
|
+
# @return [String] A warning to give when this field definition might conflict with a built-in method
|
102
|
+
def conflict_field_name_warning(field_defn)
|
103
|
+
"#{self.graphql_name}'s `field :#{field_defn.original_name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.resolver_method}` and `def resolve_#{field_defn.resolver_method}`). Or use `method_conflict_warning: false` to suppress this warning."
|
104
|
+
end
|
95
105
|
end
|
96
106
|
end
|
97
107
|
end
|
@@ -6,12 +6,12 @@ module GraphQL
|
|
6
6
|
module TypeSystemHelpers
|
7
7
|
# @return [Schema::NonNull] Make a non-null-type representation of this type
|
8
8
|
def to_non_null_type
|
9
|
-
GraphQL::Schema::NonNull.new(self)
|
9
|
+
@to_non_null_type ||= GraphQL::Schema::NonNull.new(self)
|
10
10
|
end
|
11
11
|
|
12
12
|
# @return [Schema::List] Make a list-type representation of this type
|
13
13
|
def to_list_type
|
14
|
-
GraphQL::Schema::List.new(self)
|
14
|
+
@to_list_type ||= GraphQL::Schema::List.new(self)
|
15
15
|
end
|
16
16
|
|
17
17
|
# @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable.
|
@@ -78,6 +78,10 @@ module GraphQL
|
|
78
78
|
|
79
79
|
private
|
80
80
|
|
81
|
+
def conflict_field_name_warning(field_defn)
|
82
|
+
"#{self.graphql_name}'s `field :#{field_defn.name}` conflicts with a built-in method, use `hash_key:` or `method:` to pick a different resolve behavior for this field (for example, `hash_key: :#{field_defn.resolver_method}_value`, and modify the return hash). Or use `method_conflict_warning: false` to suppress this warning."
|
83
|
+
end
|
84
|
+
|
81
85
|
# Override this to attach self as `mutation`
|
82
86
|
def generate_payload_type
|
83
87
|
payload_class = super
|
@@ -122,7 +122,9 @@ module GraphQL
|
|
122
122
|
graphql_name("#{mutation_name}Input")
|
123
123
|
description("Autogenerated input type of #{mutation_name}")
|
124
124
|
mutation(mutation_class)
|
125
|
-
|
125
|
+
mutation_args.each do |_name, arg|
|
126
|
+
add_argument(arg)
|
127
|
+
end
|
126
128
|
argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
|
127
129
|
end
|
128
130
|
end
|
@@ -40,6 +40,7 @@ module GraphQL
|
|
40
40
|
@arguments_by_keyword[arg.keyword] = arg
|
41
41
|
end
|
42
42
|
@arguments_loads_as_type = self.class.arguments_loads_as_type
|
43
|
+
@prepared_arguments = nil
|
43
44
|
end
|
44
45
|
|
45
46
|
# @return [Object] The application object this field is being resolved on
|
@@ -51,6 +52,10 @@ module GraphQL
|
|
51
52
|
# @return [GraphQL::Schema::Field]
|
52
53
|
attr_reader :field
|
53
54
|
|
55
|
+
def arguments
|
56
|
+
@prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
|
57
|
+
end
|
58
|
+
|
54
59
|
# This method is _actually_ called by the runtime,
|
55
60
|
# it does some preparation and then eventually calls
|
56
61
|
# the user-defined `#resolve` method.
|
@@ -74,6 +79,7 @@ module GraphQL
|
|
74
79
|
# for that argument, or may return a lazy object
|
75
80
|
load_arguments_val = load_arguments(args)
|
76
81
|
context.schema.after_lazy(load_arguments_val) do |loaded_args|
|
82
|
+
@prepared_arguments = loaded_args
|
77
83
|
# Then call `authorized?`, which may raise or may return a lazy object
|
78
84
|
authorized_val = if loaded_args.any?
|
79
85
|
authorized?(**loaded_args)
|
@@ -58,7 +58,8 @@ module GraphQL
|
|
58
58
|
resolver_fields.each do |name, f|
|
59
59
|
# Reattach the already-defined field here
|
60
60
|
# (The field's `.owner` will still point to the mutation, not the object type, I think)
|
61
|
-
|
61
|
+
# Don't re-warn about a method conflict. Since this type is generated, it should be fixed in the resolver instead.
|
62
|
+
add_field(f, method_conflict_warning: false)
|
62
63
|
end
|
63
64
|
end
|
64
65
|
end
|
@@ -12,16 +12,6 @@ module GraphQL
|
|
12
12
|
#
|
13
13
|
# Also, `#unsubscribe` terminates the subscription.
|
14
14
|
class Subscription < GraphQL::Schema::Resolver
|
15
|
-
class EarlyTerminationError < StandardError
|
16
|
-
end
|
17
|
-
|
18
|
-
# Raised when `unsubscribe` is called; caught by `subscriptions.rb`
|
19
|
-
class UnsubscribedError < EarlyTerminationError
|
20
|
-
end
|
21
|
-
|
22
|
-
# Raised when `no_update` is returned; caught by `subscriptions.rb`
|
23
|
-
class NoUpdateError < EarlyTerminationError
|
24
|
-
end
|
25
15
|
extend GraphQL::Schema::Resolver::HasPayloadType
|
26
16
|
extend GraphQL::Schema::Member::HasFields
|
27
17
|
|
@@ -65,7 +55,7 @@ module GraphQL
|
|
65
55
|
def resolve_update(**args)
|
66
56
|
ret_val = args.any? ? update(**args) : update
|
67
57
|
if ret_val == :no_update
|
68
|
-
|
58
|
+
throw :graphql_no_subscription_update
|
69
59
|
else
|
70
60
|
ret_val
|
71
61
|
end
|
@@ -90,7 +80,7 @@ module GraphQL
|
|
90
80
|
|
91
81
|
# Call this to halt execution and remove this subscription from the system
|
92
82
|
def unsubscribe
|
93
|
-
|
83
|
+
throw :graphql_subscription_unsubscribed
|
94
84
|
end
|
95
85
|
|
96
86
|
READING_SCOPE = ::Object.new
|
@@ -7,7 +7,7 @@ module GraphQL
|
|
7
7
|
# to the `errors` key. Any already-resolved fields will be in the `data` key, so
|
8
8
|
# you'll get a partial response.
|
9
9
|
#
|
10
|
-
# You can subclass `GraphQL::Schema::Timeout` and override
|
10
|
+
# You can subclass `GraphQL::Schema::Timeout` and override `max_seconds` and/or `handle_timeout`
|
11
11
|
# to provide custom logic when a timeout error occurs.
|
12
12
|
#
|
13
13
|
# Note that this will stop a query _in between_ field resolutions, but
|
@@ -33,8 +33,6 @@ module GraphQL
|
|
33
33
|
# end
|
34
34
|
#
|
35
35
|
class Timeout
|
36
|
-
attr_reader :max_seconds
|
37
|
-
|
38
36
|
def self.use(schema, **options)
|
39
37
|
tracer = new(**options)
|
40
38
|
schema.tracer(tracer)
|
@@ -48,32 +46,39 @@ module GraphQL
|
|
48
46
|
def trace(key, data)
|
49
47
|
case key
|
50
48
|
when 'execute_multiplex'
|
51
|
-
timeout_state = {
|
52
|
-
timeout_at: Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) + max_seconds * 1000,
|
53
|
-
timed_out: false
|
54
|
-
}
|
55
|
-
|
56
49
|
data.fetch(:multiplex).queries.each do |query|
|
50
|
+
timeout_duration_s = max_seconds(query)
|
51
|
+
timeout_state = if timeout_duration_s == false
|
52
|
+
# if the method returns `false`, don't apply a timeout
|
53
|
+
false
|
54
|
+
else
|
55
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
56
|
+
timeout_at = now + (max_seconds(query) * 1000)
|
57
|
+
{
|
58
|
+
timeout_at: timeout_at,
|
59
|
+
timed_out: false
|
60
|
+
}
|
61
|
+
end
|
57
62
|
query.context.namespace(self.class)[:state] = timeout_state
|
58
63
|
end
|
59
64
|
|
60
65
|
yield
|
61
66
|
when 'execute_field', 'execute_field_lazy'
|
62
|
-
|
63
|
-
timeout_state =
|
64
|
-
|
67
|
+
query_context = data[:context] || data[:query].context
|
68
|
+
timeout_state = query_context.namespace(self.class).fetch(:state)
|
69
|
+
# If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
|
70
|
+
if timeout_state != false && Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
|
65
71
|
error = if data[:context]
|
66
|
-
|
67
|
-
GraphQL::Schema::Timeout::TimeoutError.new(context.parent_type, context.field)
|
72
|
+
GraphQL::Schema::Timeout::TimeoutError.new(query_context.parent_type, query_context.field)
|
68
73
|
else
|
69
74
|
field = data.fetch(:field)
|
70
75
|
GraphQL::Schema::Timeout::TimeoutError.new(field.owner, field)
|
71
76
|
end
|
72
77
|
|
73
78
|
# Only invoke the timeout callback for the first timeout
|
74
|
-
|
79
|
+
if !timeout_state[:timed_out]
|
75
80
|
timeout_state[:timed_out] = true
|
76
|
-
handle_timeout(error, query)
|
81
|
+
handle_timeout(error, query_context.query)
|
77
82
|
end
|
78
83
|
|
79
84
|
error
|
@@ -85,6 +90,15 @@ module GraphQL
|
|
85
90
|
end
|
86
91
|
end
|
87
92
|
|
93
|
+
# Called at the start of each query.
|
94
|
+
# The default implementation returns the `max_seconds:` value from installing this plugin.
|
95
|
+
#
|
96
|
+
# @param query [GraphQL::Query] The query that's about to run
|
97
|
+
# @return [Integer, false] The number of seconds after which to interrupt query execution and call {#handle_error}, or `false` to bypass the timeout.
|
98
|
+
def max_seconds(query)
|
99
|
+
@max_seconds
|
100
|
+
end
|
101
|
+
|
88
102
|
# Invoked when a query times out.
|
89
103
|
# @param error [GraphQL::Schema::Timeout::TimeoutError]
|
90
104
|
# @param query [GraphQL::Error]
|